这是一个非常好的问题,本次我们就办理与 Spring Data 集成问题。
1.2. 目标实现 QueryObjectRepository 与 Spring Data Jpa 的集成,无需实现新的 Repository,只需按 spring data 规范完成接口定义,由框架天生的 proxy 实现所有的逻辑。
首先,引入 Spring data jpa 干系依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
其次,新建 JpaUser Entity 类:
@Data@Entity@Table(name="t_user")publicclassJpaUserimplementsUser{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)privateLongid;privateStringname;privateIntegerstatus;privateDatebirthAt;privateStringmobile;}
新建 JpaUserRepository
publicinterfaceJpaUserRepositoryextendsRepository<JpaUser,Long>,JpaSpecificationExecutor<JpaUser>{}
JpaUserRepository 继续两个接口:
Repository,标记为一个仓库,由 spring data 为其创建代理类;JpaSpecificationExecutor,使其具备 Specification 查询能力;2.1.2. 引入 singlequery在 pom 中增加 singlequery 干系依赖:
<dependency><groupId>com.geekhalo.lego</groupId><artifactId>lego-starter-singlequery</artifactId><version>0.1.7-query-SNAPSHOT</version></dependency>
Starter 中的 JpaBasedSingleQueryConfiguration 将为我们完玉成体配置。
2.2. 【旧】自定义 QueryObjectRepository 方案打仗过旧版本的读者,可以跳过,直接看下一章“spring data jpa 集成”
2.2.1. 定义 Repository创建 JpaUserSingleQueryService,继续自 BaseSpecificationQueryObjectRepository,详细如下:
@RepositorypublicclassJpaUserSingleQueryServiceextendsBaseSpecificationQueryObjectRepositoryimplementsUserSingleQueryService{publicJpaUserSingleQueryService(JpaUserRepositoryspecificationExecutor){super(specificationExecutor,JpaUser.class);}}
个中,布局参数 JpaUserRepository 为 spring data jpa 为我们天生的 Proxy;BaseSpecificationQueryObjectRepository 为我们供应基本的查询能力;
2.2.2. 创建查询工具,添加查询表明定义查询工具,详细如下:
@DatapublicclassQueryByIdIn{@FieldIn(value="id",fieldType=Long.class)privateList<Long>ids;}
个中,@FieldIn 表明过滤字段和过滤办法;
2.2.3. 运行单元测试编写测试用例如下:
@TestvoidgetByIds(){List<Long>ids=Arrays.asList(1L,2L,3L,4L,5L,6L,7L,8L,9L,10L);QueryByIdInqueryByIdIn=newQueryByIdIn();queryByIdIn.setIds(ids);{List<User>users=this.getSingleQueryService().listOf(queryByIdIn);Assertions.assertNotNull(users);Assertions.assertTrue(CollectionUtils.isNotEmpty(users));Assertions.assertEquals(10,users.size());}{Longcount=this.getSingleQueryService().countOf(queryByIdIn);Assertions.assertEquals(10L,count);}}
运行用例,掌握台打印 SQL 如下:
Hibernate:selectjpauser0_.idasid1_0_,jpauser0_.birth_atasbirth_at2_0_,jpauser0_.mobileasmobile3_0_,jpauser0_.nameasname4_0_,jpauser0_.statusasstatus5_0_fromt_userjpauser0_wherejpauser0_.idin(?,?,?,?,?,?,?,?,?,?)Hibernate:selectcount(jpauser0_.id)ascol_0_0_fromt_userjpauser0_wherejpauser0_.idin(?,?,?,?,?,?,?,?,?,?)
当前支持的过滤表明包括:
表明
含义
FieldEqualTo
即是
FieldGreaterThan
大于
FieldGreaterThanOrEqualTo
大于即是
FieldIn
in 操作
FieldIsNull
是否为 null
FieldLessThan
小于
FieldLessThanOrEqualTo
小于即是
FieldNotEqualTo
不即是
FieldNotIn
not in
EmbeddedFilter
嵌入查询工具
2.2.4. 嵌入工具查询新建 嵌入工具 QueryByStatusAndBirth,在类上增加过滤表明,详细如下:
@DatapublicclassQueryByStatusAndBirth{@FieldEqualTo("status")privateIntegerstatus;@FieldGreaterThan("birthAt")privateDatebirthAfter;}
新建查询工具 QueryByEmbeddedFilter,利用 @EmbeddedFilter 标注嵌入工具,详细如下:
@DatapublicclassQueryByEmbeddedFilter{@FieldGreaterThan("id")privateLongid;@EmbeddedFilterprivateQueryByStatusAndBirthstatusAndBirth;}
编写测试用例:
@TestvoidqueryByEmbeddedFilter()throwsException{QueryByEmbeddedFilterquery=newQueryByEmbeddedFilter();query.setId(0L);QueryByStatusAndBirthqueryByStatusAndBirth=newQueryByStatusAndBirth();query.setStatusAndBirth(queryByStatusAndBirth);queryByStatusAndBirth.setStatus(1);queryByStatusAndBirth.setBirthAfter(DateUtils.parseDate("2018-10-01","yyyy-MM-dd"));List<User>users=getSingleQueryService().listOf(query);Assertions.assertTrue(CollectionUtils.isNotEmpty(users));}
运行测试,获取如下结果:
Hibernate:selectjpauser0_.idasid1_0_,jpauser0_.birth_atasbirth_at2_0_,jpauser0_.mobileasmobile3_0_,jpauser0_.nameasname4_0_,jpauser0_.statusasstatus5_0_fromt_userjpauser0_wherejpauser0_.id>0andjpauser0_.status=1andjpauser0_.birth_at>?
2.2.5. 排序&分页
新建的查询工具 PageByIdGreater,详细如下:
@DatapublicclassPageByIdGreater{@FieldGreaterThan("id")privateLongstartId;privatePageablepageable;privateSortsort;}
除过滤表明外,新增 Pageable 和 Sort 两个属性。
添加 单元测试 如下:
@TestvoidpageOf(){{PageByIdGreaterpageByIdGreater=newPageByIdGreater();pageByIdGreater.setStartId(0L);Pageablepageable=newPageable();pageByIdGreater.setPageable(pageable);pageable.setPageNo(0);pageable.setPageSize(5);Sortsort=newSort();pageByIdGreater.setSort(sort);Sort.Orderorder=Sort.Order.<Orders>builder().orderField(Orders.ID).direction(Sort.Direction.ASC).build();sort.getOrders().add(order);Page<User>userPage=this.getSingleQueryService().pageOf(pageByIdGreater);Assertions.assertTrue(userPage.hasContent());Assertions.assertEquals(5,userPage.getContent().size());Assertions.assertEquals(0,userPage.getCurrentPage());Assertions.assertEquals(5,userPage.getPageSize());Assertions.assertEquals(3,userPage.getTotalPages());Assertions.assertEquals(13,userPage.getTotalElements());Assertions.assertTrue(userPage.isFirst());Assertions.assertFalse(userPage.hasPrevious());Assertions.assertTrue(userPage.hasNext());Assertions.assertFalse(userPage.isLast());}}
运行单元测试,获取如下结果:
Hibernate:selectjpauser0_.idasid1_0_,jpauser0_.birth_atasbirth_at2_0_,jpauser0_.mobileasmobile3_0_,jpauser0_.nameasname4_0_,jpauser0_.statusasstatus5_0_fromt_userjpauser0_wherejpauser0_.id>0orderbyjpauser0_.idasclimit?Hibernate:selectcount(jpauser0_.id)ascol_0_0_fromt_userjpauser0_wherejpauser0_.id>0
先通过 count 查询获取总量,然后通过 limit 进行分页查询获取数据,终极将两者封装成 Page 工具。
2.2.6. 最大返回值管理单次返回太多值是数据库性能杀手,框架通过 @MaxResult 对其进行部分支持。
目前支持包括:
策略
含义
LOG
返回结果超过配置值后,打印日志,进行跟踪
ERROR
返回结果超过配置值后,直接抛出非常
SET_LIMIT
将 limit 最大值设置为 配置值,对返回值进行限定
新建查询工具 QueryByIdGreaterWithMaxResult,在类上增加 @MaxResult 表明,详细如下:
@Data@MaxResult(max=10,strategy=MaxResultCheckStrategy.LOG)publicclassQueryByIdGreaterWithMaxResult{@FieldGreaterThan(value="id")privateLongstartUserId;}
个中,max 指最大返回值,strategy 为 日志,运行结果如下:
Hibernate:selectjpauser0_.idasid1_0_,jpauser0_.birth_atasbirth_at2_0_,jpauser0_.mobileasmobile3_0_,jpauser0_.nameasname4_0_,jpauser0_.statusasstatus5_0_fromt_userjpauser0_wherejpauser0_.id>0【LOG】resultsizeis13morethan10,daoisorg.springframework.data.jpa.repository.support.SimpleJpaRepository@77d959f1paramisQueryByIdGreaterWithMaxResult(startUserId=0)
将 strategy 修正为 ERROR,运行测试,抛出非常:
com.geekhalo.lego.core.singlequery.ManyResultExceptionatcom.geekhalo.lego.core.singlequery.support.AbstractQueryRepository.processForMaxResult(AbstractQueryRepository.java:34)atcom.geekhalo.lego.core.singlequery.jpa.support.AbstractSpecificationQueryRepository.listOf(AbstractSpecificationQueryRepository.java:107)
将 strategy 修正为 SET_LIMIT,运行测试,不雅观察 SQL,通过 limit 自动对返回值进行限定。
Hibernate:selectjpauser0_.idasid1_0_,jpauser0_.birth_atasbirth_at2_0_,jpauser0_.mobileasmobile3_0_,jpauser0_.nameasname4_0_,jpauser0_.statusasstatus5_0_fromt_userjpauser0_wherejpauser0_.id>0limit?【SET_LIMIT】resultsizeis10morethan10,pleasefindandfix,daoisorg.springframework.data.jpa.repository.support.SimpleJpaRepository@35841d6paramisQueryByIdGreaterWithMaxResult(startUserId=0)
2.3. Spring data jpa 集成2.3.1. 自定义 repositoryFactoryBean
首先,须要对 SimpleJpaRepository 实现进行功能扩展,并让框架实现自定义的 Repository 实现。
详细操作如下:
@SpringBootApplication@EnableAspectJAutoProxy(proxyTargetClass=true)@EnableJpaRepositories(basePackages={"com.geekhalo.lego.singlequery.jpa","com.geekhalo.lego.validator","com.geekhalo.lego.query"},repositoryFactoryBeanClass=JpaBasedQueryObjectRepositoryFactoryBean.class)publicclassDemoApplication{publicstaticvoidmain(String[]args){SpringApplication.run(DemoApplication.class,args);}}
通过指定 @EnableJpaRepositories 的 repositoryFactoryBeanClass 属性,使框架利用自定义的 JpaBasedQueryObjectRepository 作为 Repository 的默认实现。
2.3.2. 自定义 JpaRepository按 spring data 标准,只需定义 Repository 接口,无需实现。详细示例如下:
publicinterfaceJpaUserRepositoryV2extendsJpaRepository<JpaUser,Long>,QueryObjectRepository<JpaUser>{}
个中:
JpaRepository 为 spring data jpa 供应的 Repository 扩展;QueryObjectRepository 为 QueryObject 扩展;2.3.3. 实现效果以最繁芜的“最大返回值管理”为例,测试代码如下:
@TestvoidqueryByIdGreaterWithMaxResult(){QueryByIdGreaterWithMaxResultquery=newQueryByIdGreaterWithMaxResult();query.setStartUserId(0L);List<?extendsUser>users=getSingleQueryService().listOf(query);Assertions.assertTrue(CollectionUtils.isNotEmpty(users));}
结果如下:
Hibernate:selectjpauser0_.idasid1_2_,jpauser0_.birth_atasbirth_at2_2_,jpauser0_.mobileasmobile3_2_,jpauser0_.nameasname4_2_,jpauser0_.statusasstatus5_2_fromt_userjpauser0_wherejpauser0_.id>0limit?Hibernate:selectcount(jpauser0_.id)ascol_0_0_fromt_userjpauser0_wherejpauser0_.id>0c.g.l.c.s.s.AbstractQueryRepository:【SET_LIMIT】resultsizeis10morethan10,pleasefindandfix,daoiscom.geekhalo.lego.core.singlequery.jpa.support.JpaBasedQueryObjectRepository@50008974paramisQueryByIdGreaterWithMaxResult(startUserId=0)
3. 小结
两者方案比拟,与 Spring data 集成后,利用变得非常大略:
符合 Spring data 的利用风格,降落利用门槛;无需定义新的 Repository 体系,避免逻辑分离;最关键的是,所供应的能力没有任何减少;4. 设计&扩展4.1. 功能扩展spring data jpa 具有非常强的扩展性,整体如下:
image
详细扩展点包括:
QueryObjectRepository 定义一套基于 “查询工具” 的查询接口,包括 get、listOf、pageOf、countOf;SpecificationQueryObjectRepository 扩展自 QueryObjectRepository,基于 Specification 实现查询;JpaBasedQueryObjectRepository 实现 SpecificationQueryObjectRepository 接口,扩展自 SimpleJpaRepository,在 SimpleJpaRepository 根本上供应 SpecificationQueryObjectRepository 的通用实现;4.2. 框架集成image
与框架集成的核心在 JpaBasedQueryObjectRepositoryFactoryBean,也便是在 @EnableJpaRepositories 的 repositoryFactoryBeanClass 指定的类,该类紧张用于为每个 Repository 供应 RepositoryFactory,有 RepositoryFactory 为其天生代理。
5. 项目信息项目仓库地址:https://gitee.com/litao851025/lego
项目文档地址:https://gitee.com/litao851025/lego/wikis/support/SingleQuery-spring-data-jpa