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
對這塊需求的補充和完善。