DDD死党:单引擎查询利器( 五 )

  • QueryRepository接口:提供一组标准的 API,实现常见的 get、list、count 和 page 查询;
  • 注解
    注解配置于 QueryObject 之上,以声明化的方式,对过滤功能进行描述 。
    常见注解如下:
    注解
    含义
    FieldEqualTo
    等于
    FieldGreaterThan
    大于
    FieldGreaterThanOrEqualTo
    大于等于
    FieldIn
    in 操作
    FieldIsNull
    是否为 null
    FieldLessThan
    小于
    FieldLessThanOrEqualTo
    小于等于
    FieldNotEqualTo
    不等于
    FieldNotIn
    not in
    EmbeddedFilter
    嵌入查询对象
    针对之前的 User 查询实例,对应的 查询对象定义如下:
    @Datapublic class QueryUserByStatus {// 状态相等@FieldEqualTo("status")@NotNullprivate Integer status;// 手机号相等@FieldEqualTo("mobile")private String mobile;// 生日比该值大@FieldGreaterThan("birthAt")private Date birthAfter;// 生日比该值小@FieldLessThan("birthAt")private Date birthBefore;// 自动具备分页能力private Pageable pageable;}接口有了 QueryObject 之后,需要一组查询 API 以满足各个场景需求,标准的 API 接口定义如下:
    public interface QueryObjectRepository<E> {// 检查查询对象的有效性void checkForQueryObject(Class cls);// 单条查询<Q> E get(Q query);// 分页查询default <Q, R> R get(Q query, Function<E, R> converter) {E entity = this.get(query);return entitynull ? null : converter.apply(entity);}// 统计查询<Q> Long countOf(Q query);// 列表查询default <Q, R> List<R> listOf(Q query, Function<E, R> converter) {List<E> entities = this.listOf(query);return CollectionUtils.isEmpty(entities) ? Collections.emptyList() : (List)entities.stream().filter(Objects::nonNull).map(converter).filter(Objects::nonNull).collect(Collectors.toList());}// 列表查询<Q> List<E> listOf(Q query);// 分页查询default <Q, R> Page<R> pageOf(Q query, Function<E, R> converter) {Page<E> entityPage = this.pageOf(query);return entityPagenull ? null : entityPage.convert(converter);}// 分页查询<Q> Page<E> pageOf(Q query);}集成示例有了 QueryObject 和 API 之后,便可以轻松完成各种查询:
    public class SingleQueryService {@Autowiredprivate QueryObjectRepository<JpaUser> repository;public List<JpaUser> listByStatus(QueryUserByStatus query){return repository.listOf(query);}public Long countByStatus(QueryUserByStatus query){return this.repository.countOf(query);}public Page<JpaUser> pageByStatus(QueryUserByStatus query){return this.repository.pageOf(query);}}万事具备,只欠最后的 QueryObjectRepository 实现,针对不同的 ORM 提供不同的实现 。
    (2)MyBatis 支持
    基于 MyBatis Generator 的 Example 机制实现,需要配置相关的 Generator 以生成 EntityExample 对象 。
    直接继承BaseReflectBasedExampleSingleQueryRepository,注入 Mapper 实现,指定好 Example 类即可,具体如下:
    @Servicepublic class MyBatisBasedQueryRepository extends BaseReflectBasedExampleSingleQueryRepository {// 注入 MyBatis 的 Mapper 类public MyBatisBasedQueryRepository(MyBatisUserMapper mapper) {// 指定查询所需的 Example 类super(mapper, MyBatisUserExample.class);}}整体架构如下:
    DDD死党:单引擎查询利器

    文章插图
    核心流程如下:
    • ExampleConverter 将输入的 QueryObject 转换为 XXXExample 实例 。
    • 使用 XXXExample 实例 调用 XXXMapper 的 selectByExample 方法获取返回值 。
    • 返回值通过 ResultConverter 将 Entity 转换为最终结果 。
    其中,从 QueryObject 到 Example 实例的转换为框架的核心,主要包括如下几部分:
    • Pageable 。从 QueryObject 中读取 Pageable 属性 , 并设置 Example 对象的 offset 和 rows 属性 。
    • Sort 。从 QueryObject 中读取 Sort 属性,并设置 Example 对象的 orderByClause 属性 。
    • 过滤注解 。遍历 QueryObject 中属性,根据注解查找到注解处理器,由注解处理器为 Example 添加 Criteria,以进行数据过滤 。
    (3)Jpa 支持
    基于 JPA 框架的 JpaSpecificationExecutor 实现,EntityRepository 需继承 JpaSpecificationExecutor 接口 。


    推荐阅读