Spring JPA
是目前比较经常使用的ORM
解决方案,可是其对于某些场景并非特别的方便,例如查询部分字段,联表查询,子查询等。java
而接下来我会介绍与JPA
造成互补,同时也是与JPA
兼容得很好的框架QueryDSL
。mysql
同时因为目前主流使用Spring Boot
,因此本文也会基于Spring Boot
来进行演示git
若是对于长文无感,可是又但愿了解QueryDSL
能够直接查看文章最后的总结github
如下为示例的关键环境信息spring
JDK 1.8
maven 3.6.1
SpringBoot 2.2.0.RELEASE
IntelliJ IDEA 2019.2.3
lombok
mysql-5.7
https://github.com/spring-bas...sql
QueryDSL
自己定位就是对某些技术的补充或者说是完善,其提供了对JPA
、JDBC
、JDO
等技术的支持。这里引入的是QueryDSL-JPA
,须要注意必定要引入querydsl代码生成器插件。数据库
<properties> <java.version>1.8</java.version> <querydsl.version>4.2.1</querydsl.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!--使用版本较老的mysql驱动包,用于链接mysql-5.7--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <!--引入querydsl-jpa依赖--> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> <version>${querydsl.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!--引入querydsl代码生成器插件--> <plugin> <groupId>com.mysema.maven</groupId> <artifactId>apt-maven-plugin</artifactId> <version>1.1.3</version> <dependencies> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <version>${querydsl.version}</version> </dependency> </dependencies> <executions> <!--设置插件生效的maven生命周期--> <execution> <goals> <goal>process</goal> </goals> <configuration> <!--配置生成文件的目录--> <outputDirectory>src/generated-sources/java/</outputDirectory> <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor> </configuration> </execution> </executions> </plugin> </plugins> </build>
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
,因此须要自行准备一个配置类,代码以下所示express
import com.querydsl.jpa.impl.JPAQueryFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; /** * QueryDSL配置类 * @author Null * @date 2019-10-24 */ @Configuration public class QuerydslConfig { @Autowired @PersistenceContext private EntityManager entityManager; @Bean public JPAQueryFactory queryFactory(){ return new JPAQueryFactory(entityManager); } }
启动类很简单,只须要使用@SpringBootApplication
便可app
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class QuerydslJpaDemoApplication { public static void main(String[] args) { SpringApplication.run(QuerydslJpaDemoApplication.class, args); } }
主要有讲师和课程,每一个课程都有一个讲师,每一个讲师有多个课程,即讲师与课程的关系为一对多框架
import lombok.Data; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; /** * 课程,一个课程对应一个讲师 * @author Null * @date 2019-10-24 */ @Data @Entity public class Course { /** * 课程ID */ @Id @GeneratedValue(strategy= GenerationType.IDENTITY) private Long id; /** * 课程名称 */ private String name; /** * 对应讲师的ID */ private Long lecturerId; }
import lombok.Data; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; /** * 讲师,一个讲师有多个课程 * @author Null * @date 2019-10-24 */ @Data @Entity public class Lecturer { /** * 讲师ID */ @Id @GeneratedValue(strategy= GenerationType.IDENTITY) private Long id; /** * 讲师名字 */ private String name; /** * 性别,true(1)为男性,false(0)为女性 */ private Boolean sex; }
若是要使用QuerDSL
须要Repository
接口除了继承JpaRepository
接口(此接口为Spring-JPA
提供的接口)外,还须要继承QuerydslPredicateExecutor
接口。关键示例以下:
import com.example.querydsl.jpa.entity.Course; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.querydsl.QuerydslPredicateExecutor; /** * 课程Repository * * @author Null * @date 2019-10-24 */ public interface CourseRepository extends JpaRepository<Course, Integer>, QuerydslPredicateExecutor<Course> { }
import com.example.querydsl.jpa.entity.Lecturer; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.querydsl.QuerydslPredicateExecutor; /** * 讲师Repository * @author Null * @date 2019-10-24 */ public interface LecturerRepository extends JpaRepository<Lecturer,Integer>, QuerydslPredicateExecutor<Lecturer> { }
前面配置QueryDSL
代码生成器就是用于这一步,==每次实体类有变动最好重复执行本步骤从新生成新的代码==。因为我的习惯使用IDEA
,因此以IDEA
做为演示。
src/generated-sources
目录能够看到生成的代码,包名与实体包名一致,可是类名为Q
开头的文件IDEA
识别为普通文件了,因此咱们须要标记src/generated-sources/java
目录的用途,以下图所示标记后,效果以下,能够看到代码被正确识别了
到了这一步其实已经完成整合了,下面就开始验证是否正确整合以及展现QueryDSL
的优点了
下面我会使用单元测试来验证QueryDSL
是否正确整合以及演示一下QueryDSL
的优点
这里主要是单元测试类的关键内容,须要注意@BeforeEach
是Junit5
的注解,表示每一个单元测试用例执行前会执行的方法其实对应Junit4
的@Before
/** * @SpringBootTest 默认不支持事务且自动回滚 * 使用@Transactional 开启事务, * 使用@Rollback(false) 关闭自动回滚 * @author Null * @date 2019-10-24 */ @SpringBootTest class QuerydslJpaDemoApplicationTests { @Autowired private CourseRepository courseRepository; @Autowired private LecturerRepository lecturerRepository; @Autowired private JPAQueryFactory queryFactory; /** * 初始化数据 */ @BeforeEach public void initData(){ // 清空数据表 courseRepository.deleteAll(); lecturerRepository.deleteAll(); // 初始化讲师 Lecturer tom=new Lecturer(); tom.setName("Tom"); tom.setSex(true); lecturerRepository.save(tom); Lecturer marry=new Lecturer(); marry.setName("Marry"); marry.setSex(false); lecturerRepository.save(marry); // 初始化课程 Course chinese=new Course(); chinese.setName("Chinese"); chinese.setLecturerId(tom.getId()); courseRepository.save(chinese); Course physics=new Course(); physics.setName("Physics"); physics.setLecturerId(tom.getId()); courseRepository.save(physics); Course english=new Course(); english.setName("English"); english.setLecturerId(marry.getId()); courseRepository.save(english); } ...省略各个用例 }
/** * 根据课程名称模糊查询课程 */ @Test public void testSelectCourseByNameLike() { // 组装查询条件 QCourse qCourse = QCourse.course; // %要自行组装 BooleanExpression expression = qCourse.name.like("P%"); System.out.println(courseRepository.findAll(expression)); }
/** * 根据讲师姓名查课程 */ @Test public void testSelectCourseByLecturerName(){ QCourse qCourse = QCourse.course; QLecturer qLecturer = QLecturer.lecturer; // 这里包含了组装查询条件和执行查询的逻辑,组装好条件后记得执行fetch() List<Course> courses=queryFactory.select(qCourse) .from(qCourse) .leftJoin(qLecturer) .on(qCourse.lecturerId.eq(qLecturer.id)) .where(qLecturer.name.eq("Tom")) .fetch(); System.out.println(courses); }
/** * 根据姓名更新讲师性别<br/> * 使用@Transactional开启事务<br/> * 使用@Rollback(false)关闭自动回滚<br/> */ @Test @Transactional @Rollback(false) public void testUpdateLecturerSexByName(){ QLecturer qLecturer = QLecturer.lecturer; // 更新Tom的性别为女性,返回的是影响记录条数 long num=queryFactory.update(qLecturer) .set(qLecturer.sex,false) .where(qLecturer.name.eq("Tom")) .execute(); // 这里输出被更新的记录数 System.out.println(num); }
/** * 根据根据性别删除讲师 */ @Test @Transactional @Rollback(false) public void testDeleteLecturerBySex(){ QLecturer qLecturer = QLecturer.lecturer; // 删除性别为男性的讲师 long num=queryFactory.delete(qLecturer) .where(qLecturer.sex.eq(true)) .execute(); // 输出被删除的记录数 System.out.println(num); }
从用例中能够看出其实QueryDSL
的API
更加切合原生的SQL
,基本上从代码上就能够看出你但愿执行的SQL
了。
细心的朋友会发现QueryDSL
是没有insert
方法,由于JPA
提供的save()
方法已经足够处理了。
同时要记得要组装好你的SQL
后别忘记调用fetch()
或者execute()
方法。
Spring Boot JPA
整合QueryDSL
的关键步骤
Repository
继承QuerydslPredicateExecutor
QueryDSL
的API
相似原生SQL
,API
风格相似StringBuilder
的API
(Fluent API
风格)。可是不提供insert
对应的操做。QueryDSL
对于复杂的SQL
的支持十分友好,算是对于JPA
对这块需求的补充和完善。