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, 假设这代表查询createTime2021-01-01T08:00:002022-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);
  }
}

具体更多使用的细节这里不多赘述了, 可以自行查阅官方文档

posted @ 2022-04-17 19:32  无以铭川  阅读(255)  评论(0)    收藏  举报