QueryDsl web support 简化查询
QueryDsl web support 简化查询
1. 问题
在RESTful定义中, 通过GET请求来进行查询, 使用query string来描述查询的数据.
通常来说, 我们会使用类来接收查询的参数, 类中包含有多个可以用于查询的字段.
许多实践中, 使用实体类对象来接收, 特别是基于mybatis的各种框架中.
然而, 虽然查询中一般要查询的字段与实体类大体相同, 但是实体类很难描述查询方式, 特别是在进行范围查询, in查询等.
例如, 查询的query string是?createTime=2021-01-01T08:00:00&createTime=2022-01-01T08:00:00, 假设这代表查询createTime在2021-01-01T08:00:00与2022-01-01T08:00:00之间的数据. 此时, springboot会将createTime解析成Collection, 而实体类中的createTime是各种日期类型. 实体类不再能完全满足查询.
2. 常见方法
为了解决这个问题, 可以在实体类中增加一些额外用于查询的字段, 正如ruoyi做的那样. 在实体类的公共父类中增加用于查询的字段, 然后在mapper.xml中使用.
这种做法简单, 却粗暴, 让实体类变得臃肿, 进一步削弱了实体类与数据间的映射关系, 还带来了过高的耦合度. 笔者个人并不喜欢这种方式.
另一种方式是, 引入额外的查询类, 在这个查询类上描述查询, 正如eladmin做的那样.
这种方法解耦查询与实体类, 但仍有缺点: 每个实体类都需要查询类, 这将增加大量的查询类, 而且其查询表达能力仍显不足.
这仍然不能说是一种好的方案.
3. 思路
是否可以将url的查询解析成某个查询类, 然后再由service或者dao层解析成查询语句进行查询呢?
与eladmin的方法不同的是, 此处我们并不定义许多查询类, 只使用一个类, 并且可以通过某些配置来自定义查询的方式.
具备一定spring基础的朋友想到, 或者曾经设想过, 可以自定义org.springframework.web.method.support.HandlerMethodArgumentResolver来对url参数进行解析, 将其解析成查询类.
这个查询类应当具备较高的查询表达能力, 最好还无需额外的解析.
有几个候选对象:
spring data jpa中的org.springframework.data.domain.Example类, 然而Example类的表达能力较弱, 只能完成基本的单表查询, 而且也只能完成最简单的查询方式的定义.
然后是org.springframework.data.jpa.domain.Specification类, Example也是使用该类实现的. 然而构造Specification代码冗长, 且想要对其查询方式的定义也较为麻烦.
这仍然不够好, 而spring提供了另一种更为优雅方便的方法.
4. 方式
对querydsl的略有了解的应该知道, querydsl可以为spring data jpa提供非常灵活的查询方式.
例如下面这样的查询
?firstname=Dave&lastname=Matthews
相当于
where firstname='Dave' and lastname='Matthews'
在querydsl的写法即是
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
而我们在第一节中提到的查询
?createTime=2021-01-01T08:00:00&createTime=2022-01-01T08:00:00
就可以使用如下的语法
QUser.user.between('2021-01-01T08:00:00', '2022-01-01T08:00:00')
querydsl的强大远不止如此, 这里不多阐述.
最大的好消息是, 解析查询字符串到查询类的代码并不需要你来编写.
你只需要引入querydsl, 然后在controller上使用@QuerydslPredicate修饰Predicate类即可. 就像下面这样:
@RestController
@RequestMapping("/user")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping
public Page<User> page(@QuerydslPredicate Predicate predicate, Pageable pageable) {
return userRepository.findAll(predicate, pageable);
}
}
spring官方提供了org.springframework.data.web.querydsl.QuerydslPredicateArgumentResolver类解析查询字符串到Predicate.
自定义查询的逻辑也非常简单, 为Repository类实现QuerydslBinderCustomizer接口即可.
public interface UserRepository
extends JpaRepository<User, Long>,
QuerydslPredicateExecutor<User>,
QuerydslBinderCustomizer<QUser> {
@Override
@Transactional
default void customize(QuerydslBindings bindings, QUser root) {
bindings.excluding(root.password);
}
}
具体更多使用的细节这里不多赘述了, 可以自行查阅官方文档

浙公网安备 33010602011771号