在前面的文章中,咱們介紹了 JPA 的基礎使用方式, 《Spring Boot (三): ORM 框架 JPA 與鏈接池 Hikari》,本篇文章,咱們由入門至進階的介紹一下爲 JPA 插上翅膀的 QueryDSL。
不能否認的是 JPA 使用是很是方便的,極簡化的配置,只須要使用註解,無需任何 xml 的配置文件,語義簡單易懂,可是,以上的一切都創建在單表查詢的前提下的,咱們可使用 JPA 默認提供的方法,簡單加輕鬆的完成 CRUD 操做。html
可是若是涉及到多表動態查詢, JPA 的功能就顯得有些捉襟見肘了,雖然咱們可使用註解 @Query
,在這個註解中寫 SQL 或者 HQL 都是在拼接字符串,而且拼接後的字符串可讀性很是的差,固然 JPA 還爲咱們提供了 Specification
來作這件事情,從我我的使用體驗上來說,可讀性雖然還不錯,可是在初學者上手的時候, Predicate
和 CriteriaBuilder
使用方式估計能勸退很多人,並且若是直接執行 SQL 連表查詢,得到是一個 Object[]
,類型是什麼?字段名是什麼?這些都沒法直觀的得到,還需咱們手動將 Object[]
映射到咱們須要的 Model
類裏面去,這種使用體驗無疑是極其糟糕的。java
這一切都在 QueryDSL 出世之後終結了, QueryDSL 語法與 SQL 很是類似,代碼可讀性很是強,異常簡介優美,,而且與 JPA 高度集成,無需多餘的配置,從筆者我的使用體驗上來說是很是棒的。能夠這麼說,只要會寫 SQL ,基本上只須要看一下示例代碼徹底能夠達到入門的級別。mysql
QueryDSL 是一個很是活躍的開源項目,目前在 Github 上的發佈的 Release 版本已經多達 251 個版本,目前最新版是 4.2.1 ,而且由 Querydsl Google組 和 StackOverflow 兩個團隊提供支持。git
QueryDSL 是一個框架,可用於構造靜態類型的相似SQL的查詢。能夠經過諸如 QueryDSL 之類的 API 構造查詢,而不是將查詢編寫爲內聯字符串或將其外部化爲XML文件。github
例如,與簡單字符串相比,使用 API 的好處是spring
代碼清單:spring-boot-jpa-querydsl/pom.xmlsql
<!--QueryDSL支持--> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <scope>provided</scope> </dependency> <!--QueryDSL支持--> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> </dependency>
spring-boot-dependencies
工程中定義。添加這個插件是爲了讓程序自動生成 query type (查詢實體,命名方式爲:"Q"+對應實體名)。
上文引入的依賴中 querydsl-apt 便是爲此插件服務的。安全
注:在使用過程當中,若是遇到 query type 沒法自動生成的狀況,用maven更新一下項目便可解決(右鍵項目 -> Maven -> Update Folders)。springboot
代碼清單:spring-boot-jpa-querydsl/pom.xml框架
<plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>com.mysema.maven</groupId> <artifactId>apt-maven-plugin</artifactId> <version>1.1.3</version> <executions> <execution> <goals> <goal>process</goal> </goals> <configuration> <outputDirectory>target/generated-sources/java</outputDirectory> <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor> </configuration> </execution> </executions> </plugin> </plugins>
在 JPA 中已經爲咱們提供了很是簡便的更新和刪除的使用方式,咱們徹底沒有必要使用 QueryDSL 的更新和刪除,不過這裏仍是給出用法,供你們參考:
代碼清單:spring-boot-jpa-querydsl/src/main/java/com/springboot/springbootjpaquerydsl/service/impl/UserServiceImpl.java
@Override public Long update(String id, String nickName) { QUserModel userModel = QUserModel.userModel; // 更新 return queryFactory.update(userModel).set(userModel.nickName, nickName).where(userModel.id.eq(id)).execute(); } @Override public Long delete(String id) { QUserModel userModel = QUserModel.userModel; // 刪除 return queryFactory.delete(userModel).where(userModel.id.eq(id)).execute(); }
QueryDSL 在查詢這方面能夠說玩的很是花了,好比一些有關 select()
和 fetch()
經常使用的寫法以下:
代碼清單:spring-boot-jpa-querydsl/src/main/java/com/springboot/springbootjpaquerydsl/service/impl/UserServiceImpl.java
@Override public List<String> selectAllNameList() { QUserModel userModel = QUserModel.userModel; // 查詢字段 return queryFactory.select(userModel.nickName).from(userModel).fetch(); } @Override public List<UserModel> selectAllUserModelList() { QUserModel userModel = QUserModel.userModel; // 查詢實體 return queryFactory.selectFrom(userModel).fetch(); } @Override public List<UserDTO> selectAllUserDTOList() { QUserModel userModel = QUserModel.userModel; QLessonModel lessonModel = QLessonModel.lessonModel; // 連表查詢實體並將結果封裝至DTO return queryFactory .select( Projections.bean(UserDTO.class, userModel.nickName, userModel.age, lessonModel.startDate, lessonModel.address, lessonModel.name) ) .from(userModel) .leftJoin(lessonModel) .on(userModel.id.eq(lessonModel.userId)) .fetch(); } @Override public List<String> selectDistinctNameList() { QUserModel userModel = QUserModel.userModel; // 去重查詢 return queryFactory.selectDistinct(userModel.nickName).from(userModel).fetch(); } @Override public UserModel selectFirstUser() { QUserModel userModel = QUserModel.userModel; // 查詢首個實體 return queryFactory.selectFrom(userModel).fetchFirst(); } @Override public UserModel selectUser(String id) { QUserModel userModel = QUserModel.userModel; // 查詢單個實體,若是結果有多個,會拋`NonUniqueResultException`。 return queryFactory.selectFrom(userModel).fetchOne(); }
上面列舉了簡單的查詢,但實際咱們會遇到至關複雜的操做,好比子查詢,多條件查詢,多表連查,使用示例以下:
代碼清單:spring-boot-jpa-querydsl/src/main/java/com/springboot/springbootjpaquerydsl/service/impl/LessonServiceImpl.java
@Service public class LessonServiceImpl implements LessonService { @Autowired JPAQueryFactory queryFactory; @Override public List<LessonModel> findLessonList(String name, Date startDate, String address, String userId) throws ParseException { QLessonModel lessonModel = QLessonModel.lessonModel; SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); // 多條件查詢示例 return queryFactory.selectFrom(lessonModel) .where( lessonModel.name.like("%" + name + "%") .and(lessonModel.address.contains(address)) .and(lessonModel.userId.eq(userId)) .and(lessonModel.startDate.between(simpleDateFormat.parse("2018-12-31 00:00:00"), new Date())) ) .fetch(); } @Override public List<LessonModel> findLessonDynaList(String name, Date startDate, String address, String userId) throws ParseException { QLessonModel lessonModel = QLessonModel.lessonModel; SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); // 動態查詢示例 BooleanBuilder builder = new BooleanBuilder(); if (!StringUtils.isEmpty(name)){ builder.and(lessonModel.name.like("%" + name + "%")); } if (startDate != null) { builder.and(lessonModel.startDate.between(simpleDateFormat.parse("2018-12-31 00:00:00"), new Date())); } if (!StringUtils.isEmpty(address)) { builder.and(lessonModel.address.contains(address)); } if (!StringUtils.isEmpty(userId)) { builder.and(lessonModel.userId.eq(userId)); } return queryFactory.selectFrom(lessonModel).where(builder).fetch(); } @Override public List<LessonModel> findLessonSubqueryList(String name, String address) { QLessonModel lessonModel = QLessonModel.lessonModel; // 子查詢示例,並沒有實際意義 return queryFactory.selectFrom(lessonModel) .where(lessonModel.name.in( JPAExpressions .select(lessonModel.name) .from(lessonModel) .where(lessonModel.address.eq(address)) )) .fetch(); } }
QueryDSL 已經內置了一些經常使用的 Mysql 的聚合函數,若是遇到 QueryDSL 沒有提供的聚合函數也無需慌張, QueryDSL 爲咱們提供了 Expressions
這個類,咱們可使用這個類手動拼接一個就好,以下示例:
代碼清單:spring-boot-jpa-querydsl/src/main/java/com/springboot/springbootjpaquerydsl/service/impl/UserServiceImpl.java
@Override public String mysqlFuncDemo(String id, String nickName, int age) { QUserModel userModel = QUserModel.userModel; // Mysql 聚合函數示例 // 聚合函數-avg() Double averageAge = queryFactory.select(userModel.age.avg()).from(userModel).fetchOne(); // 聚合函數-sum() Integer sumAge = queryFactory.select(userModel.age.sum()).from(userModel).fetchOne(); // 聚合函數-concat() String concat = queryFactory.select(userModel.nickName.concat(nickName)).from(userModel).fetchOne(); // 聚合函數-contains() Boolean contains = queryFactory.select(userModel.nickName.contains(nickName)).from(userModel).where(userModel.id.eq(id)).fetchOne(); // 聚合函數-DATE_FORMAT() String date = queryFactory.select(Expressions.stringTemplate("DATE_FORMAT({0},'%Y-%m-%d')", userModel.createDate)).from(userModel).fetchOne(); return null; }
有關 QueryDSL 的介紹到這裏就結束了,不知道各位讀者看了上面的示例,有沒有一種直接讀 SQL 的感受,並且這種 SQL 仍是使用 OOM 的思想,將本來 Hibernate 沒有作好的事情給出了一個至關完美的解決方案,上手簡單易操做,而又無需寫 SQL ,實際上咱們操做的仍是對象類。
若是個人文章對您有幫助,請掃碼關注下做者的公衆號:獲取最新干貨推送:)