Spring JPA整合QueryDSL的示例代码
前言
SpringJPA是目前比较常用的ORM解决方案,但是其对于某些场景并不是特别的方便,例如查询部分字段,联表查询,子查询等。
而接下来我会介绍与JPA形成互补,同时也是与JPA兼容得很好的框架QueryDSL。
同时由于目前主流使用SpringBoot,所以本文也会基于SpringBoot来进行演示
如果对于长文无感,但是又希望了解QueryDSL可以直接查看文章最后的总结
环境信息
以下为示例的关键环境信息
- JDK1.8
- maven3.6.1
- SpringBoot2.2.0.RELEASE
- IntelliJIDEA2019.2.3
- lombok
- mysql-5.7
源码地址
https://github.com/spring-based-solutions/querydsl-jpa-demo
项目整合
pom文件配置
QueryDSL本身定位就是对某些技术的补充或者说是完善,其提供了对JPA、JDBC、JDO等技术的支持。这里引入的是QueryDSL-JPA,需要注意一定要引入querydsl代码生成器插件。
1.8 4.2.1 org.springframework.boot spring-boot-starter-data-jpa mysql mysql-connector-java 5.1.48 org.springframework.boot spring-boot-configuration-processor true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine com.querydsl querydsl-jpa ${querydsl.version} org.springframework.boot spring-boot-maven-plugin com.mysema.maven apt-maven-plugin 1.1.3 com.querydsl querydsl-apt ${querydsl.version} process src/generated-sources/java/ com.querydsl.apt.jpa.JPAAnnotationProcessor
application配置文件
spring: datasource: ##数据库相关配置 url:jdbc:mysql://127.0.0.1:3306/example?useSSL=false username:root password:root driver-class-name:com.mysql.jdbc.Driver#指定驱动类 jpa: hibernate: ddl-auto:update#自动创建表以及更新表结构,生产环境慎用 show-sql:true#打印执行的SQL
配置类
由于QueryDSL不提供starter,所以需要自行准备一个配置类,代码如下所示
importcom.querydsl.jpa.impl.JPAQueryFactory; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.context.annotation.Bean; importorg.springframework.context.annotation.Configuration; importjavax.persistence.EntityManager; importjavax.persistence.PersistenceContext; /** *QueryDSL配置类 *@authorNull *@date2019-10-24 */ @Configuration publicclassQuerydslConfig{ @Autowired @PersistenceContext privateEntityManagerentityManager; @Bean publicJPAQueryFactoryqueryFactory(){ returnnewJPAQueryFactory(entityManager); } }
启动类
启动类很简单,只需要使用@SpringBootApplication即可
importorg.springframework.boot.SpringApplication; importorg.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication publicclassQuerydslJpaDemoApplication{ publicstaticvoidmain(String[]args){ SpringApplication.run(QuerydslJpaDemoApplication.class,args); } }
实体类
主要有讲师和课程,每个课程都有一个讲师,每个讲师有多个课程,即讲师与课程的关系为一对多
课程
importlombok.Data; importjavax.persistence.Entity; importjavax.persistence.GeneratedValue; importjavax.persistence.GenerationType; importjavax.persistence.Id; /** *课程,一个课程对应一个讲师 *@authorNull *@date2019-10-24 */ @Data @Entity publicclassCourse{ /** *课程ID */ @Id @GeneratedValue(strategy=GenerationType.IDENTITY) privateLongid; /** *课程名称 */ privateStringname; /** *对应讲师的ID */ privateLonglecturerId; }
讲师
importlombok.Data; importjavax.persistence.Entity; importjavax.persistence.GeneratedValue; importjavax.persistence.GenerationType; importjavax.persistence.Id; /** *讲师,一个讲师有多个课程 *@authorNull *@date2019-10-24 */ @Data @Entity publicclassLecturer{ /** *讲师ID */ @Id @GeneratedValue(strategy=GenerationType.IDENTITY) privateLongid; /** *讲师名字 */ privateStringname; /** *性别,true(1)为男性,false(0)为女性 */ privateBooleansex; }
Repository接口
如果要使用QuerDSL需要Repository接口除了继承JpaRepository接口(此接口为Spring-JPA提供的接口)外,还需要继承QuerydslPredicateExecutor接口。关键示例如下:
课程Repository
importcom.example.querydsl.jpa.entity.Course; importorg.springframework.data.jpa.repository.JpaRepository; importorg.springframework.data.querydsl.QuerydslPredicateExecutor; /** *课程Repository * *@authorNull *@date2019-10-24 */ publicinterfaceCourseRepositoryextends JpaRepository, QuerydslPredicateExecutor { }
讲师Repository
importcom.example.querydsl.jpa.entity.Lecturer; importorg.springframework.data.jpa.repository.JpaRepository; importorg.springframework.data.querydsl.QuerydslPredicateExecutor; /** *讲师Repository *@authorNull *@date2019-10-24 */ publicinterfaceLecturerRepositoryextends JpaRepository, QuerydslPredicateExecutor { }
代码生成
前面配置QueryDSL代码生成器就是用于这一步,==每次实体类有变更最好重复执行本步骤重新生成新的代码==。由于个人习惯使用IDEA,所以以IDEA作为演示。
双击下图内容即可生成代码了,
然后就会在src/generated-sources目录可以看到生成的代码,包名与实体包名一致,但是类名为Q开头的文件
上一步的截图我们可以看到其实生成的代码被IDEA识别为普通文件了,所以我们需要标记src/generated-sources/java目录的用途,如下图所示
标记后,效果如下,可以看到代码被正确识别了
验证整合与演示
下面我会使用单元测试来验证QueryDSL是否正确整合以及演示一下QueryDSL的优势
单元测试类
这里主要是单元测试类的关键内容,需要注意@BeforeEach是Junit5的注解,表示每个单元测试用例执行前会执行的方法其实对应Junit4的@Before
/** *@SpringBootTest默认不支持事务且自动回滚 *使用@Transactional开启事务, *使用@Rollback(false)关闭自动回滚 *@authorNull *@date2019-10-24 */ @SpringBootTest classQuerydslJpaDemoApplicationTests{ @Autowired privateCourseRepositorycourseRepository; @Autowired privateLecturerRepositorylecturerRepository; @Autowired privateJPAQueryFactoryqueryFactory; /** *初始化数据 */ @BeforeEach publicvoidinitData(){ //清空数据表 courseRepository.deleteAll(); lecturerRepository.deleteAll(); //初始化讲师 Lecturertom=newLecturer(); tom.setName("Tom"); tom.setSex(true); lecturerRepository.save(tom); Lecturermarry=newLecturer(); marry.setName("Marry"); marry.setSex(false); lecturerRepository.save(marry); //初始化课程 Coursechinese=newCourse(); chinese.setName("Chinese"); chinese.setLecturerId(tom.getId()); courseRepository.save(chinese); Coursephysics=newCourse(); physics.setName("Physics"); physics.setLecturerId(tom.getId()); courseRepository.save(physics); Courseenglish=newCourse(); english.setName("English"); english.setLecturerId(marry.getId()); courseRepository.save(english); } ...省略各个用例 }
单表模糊查询
/** *根据课程名称模糊查询课程 */ @Test publicvoidtestSelectCourseByNameLike(){ //组装查询条件 QCourseqCourse=QCourse.course; //%要自行组装 BooleanExpressionexpression=qCourse.name.like("P%"); System.out.println(courseRepository.findAll(expression)); }
联表查询
/** *根据讲师姓名查课程 */ @Test publicvoidtestSelectCourseByLecturerName(){ QCourseqCourse=QCourse.course; QLecturerqLecturer=QLecturer.lecturer; //这里包含了组装查询条件和执行查询的逻辑,组装好条件后记得执行fetch() Listcourses=queryFactory.select(qCourse) .from(qCourse) .leftJoin(qLecturer) .on(qCourse.lecturerId.eq(qLecturer.id)) .where(qLecturer.name.eq("Tom")) .fetch(); System.out.println(courses); }
更新
/** *根据姓名更新讲师性别
*使用@Transactional开启事务
*使用@Rollback(false)关闭自动回滚
*/ @Test @Transactional @Rollback(false) publicvoidtestUpdateLecturerSexByName(){ QLecturerqLecturer=QLecturer.lecturer; //更新Tom的性别为女性,返回的是影响记录条数 longnum=queryFactory.update(qLecturer) .set(qLecturer.sex,false) .where(qLecturer.name.eq("Tom")) .execute(); //这里输出被更新的记录数 System.out.println(num); }
删除
/** *根据根据性别删除讲师 */ @Test @Transactional @Rollback(false) publicvoidtestDeleteLecturerBySex(){ QLecturerqLecturer=QLecturer.lecturer; //删除性别为男性的讲师 longnum=queryFactory.delete(qLecturer) .where(qLecturer.sex.eq(true)) .execute(); //输出被删除的记录数 System.out.println(num); }
用例分析
从用例中可以看出其实QueryDSL的API更加切合原生的SQL,基本上从代码上就可以看出你希望执行的SQL了。
细心的朋友会发现QueryDSL是没有insert方法,因为JPA提供的save()方法已经足够处理了。
同时要记得要组装好你的SQL后别忘记调用fetch()或者execute()方法。
总结
SpringBootJPA整合QueryDSL的关键步骤
- 引入依赖和插件
- 编写配置类
- 使用插件生成代码
- 标记生成文件为代码
- Repository继承QuerydslPredicateExecutor
QueryDSL的API类似原生SQL,API风格类似StringBuilder的API(FluentAPI风格)。但是不提供insert对应的操作。
QueryDSL对于复杂的SQL的支持十分友好,算是对于JPA对这块需求的补充和完善。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。