閱讀本文需要Mysql,Maven和SpringBoot基礎(chǔ)知識擅编。
更新日志
- 2018.03.19更新:增加
二、1.2.7 分頁的兩種寫法
和二燥透、1.2.8 使用Template實現(xiàn)QueryDSL未支持的語法
- 2018.01.25更新:增加使用心得(查詢條件中字段為String時關(guān)于null,empty,blank的表達)
- 2018.01.24更新:增加mysql聚合函數(shù)CONCAT,DATE_FORMAT的使用示例
本文由作者三汪首發(fā)于簡書沙咏。
Demo已上傳github
一、環(huán)境配置
1. 引入maven依賴
<!-- querydsl -->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<scope>provided</scope>
</dependency>
2. 添加maven插件
添加這個插件是為了讓程序自動生成query type(查詢實體班套,命名方式為:"Q"+對應(yīng)實體名)肢藐。
上文引入的依賴中querydsl-apt
即是為此插件服務(wù)的。
注:在使用過程中,如果遇到query type無法自動生成的情況吱韭,用maven更新一下項目即可解決(右鍵項目->Maven->Update Project)吆豹。
<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>
補充:
QueryDSL默認使用HQL發(fā)出查詢語句。但也支持原生SQL查詢理盆。
若要使用原生SQL查詢痘煤,你需要使用下面這個maven插件生成相應(yīng)的query type。
<project>
<build>
<plugins>
...
<plugin>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-maven-plugin</artifactId>
<version>${querydsl.version}</version>
<executions>
<execution>
<goals>
<goal>export</goal>
</goals>
</execution>
</executions>
<configuration>
<jdbcDriver>org.apache.derby.jdbc.EmbeddedDriver</jdbcDriver>
<jdbcUrl>jdbc:derby:target/demoDB;create=true</jdbcUrl>
<packageName>com.mycompany.mydomain</packageName>
<targetFolder>${project.basedir}/target/generated-sources/java</targetFolder>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>${derby.version}</version>
</dependency>
</dependencies>
</plugin>
...
</plugins>
</build>
</project>
二猿规、使用
在Spring環(huán)境下衷快,我們可以通過兩種風(fēng)格來使用QueryDSL。
一種是使用JPAQueryFactory
的原生QueryDSL風(fēng)格姨俩,
另一種是基于Spring Data提供的QueryDslPredicateExecutor<T>
的Spring-data風(fēng)格蘸拔。
使用QueryDslPredicateExecutor<T>
可以簡化一些代碼,使得查詢更加優(yōu)雅环葵。
而JPAQueryFactory
的優(yōu)勢則體現(xiàn)在其功能的強大调窍,支持更復(fù)雜的查詢業(yè)務(wù)。甚至可以用來進行更新和刪除操作张遭。
下面分別介紹兩種風(fēng)格的使用方式邓萨。
1. JPAQueryFactory
JPAQueryFactory使用邏輯類似于HQL/SQL語法,不再額外說明。
QueryDSL在支持JPA的同時缔恳,也提供了對Hibernate的支持宝剖。可以通過HibernateQueryFactory
來使用歉甚。
裝配
@Bean
@Autowired
public JPAQueryFactory jpaQuery(EntityManager entityManager) {
return new JPAQueryFactory(entityManager);
}
注入
@Autowired
JPAQueryFactory queryFactory;
1.1 更新/刪除
Update
QMemberDomain qm = QMemberDomain.memberDomain;
queryFactory.update(qm).set(qm.status, "0012").where(qm.status.eq("0011")).execute();
Delete
QMemberDomain qm = QMemberDomain.memberDomain;
queryFactory.delete(qm).where(qm.status.eq("0012")).execute();
1.2 查詢
查詢簡直可以玩出花來诈闺。
1.2.1 select()和fetch()的幾種常用寫法
QMemberDomain qm = QMemberDomain.memberDomain;
//查詢字段-select()
List<String> nameList = queryFactory.select(qm.name).from(qm).fetch();
//查詢實體-selectFrom()
List<MemberDomain> memberList = queryFactory.selectFrom(qm).fetch();
//查詢并將結(jié)果封裝至dto中
List<MemberFavoriteDto> dtoList = queryFactory.select(Projections.constructor(MemberFavoriteDto.class,qm.name,qf.favoriteStoreCode)).from(qm).leftJoin(qm.favoriteInfoDomains,qf).fetch();
//去重查詢-selectDistinct()
List<String> distinctNameList = queryFactory.selectDistinct(qm.name).from(qm).fetch();
//獲取首個查詢結(jié)果-fetchFirst()
MemberDomain firstMember = queryFactory.selectFrom(qm).fetchFirst();
//獲取唯一查詢結(jié)果-fetchOne()
//當(dāng)fetchOne()根據(jù)查詢條件從數(shù)據(jù)庫中查詢到多條匹配數(shù)據(jù)時,會拋`NonUniqueResultException`铃芦。
MemberDomain anotherFirstMember = queryFactory.selectFrom(qm).fetchOne();
1.2.2 where子句查詢條件的幾種常用寫法
//查詢條件示例
List<MemberDomain> memberConditionList = queryFactory.selectFrom(qm)
//like示例
.where(qm.name.like('%'+"Jack"+'%')
//contain示例
.and(qm.address.contains("廈門"))
//equal示例
.and(qm.status.eq("0013"))
//between
.and(qm.age.between(20, 30)))
.fetch();
如果你覺得上面的寫法不夠優(yōu)雅,我們可以使用QueryDSL提供的BooleanBuilder
來進行查詢條件管理襟雷。
如下
BooleanBuilder builder = new BooleanBuilder();
//like
builder.and(qm.name.like('%'+"Jack"+'%'));
//contain
builder.and(qm.address.contains("廈門"));
//equal示例
builder.and(qm.status.eq("0013"));
//between
builder.and(qm.age.between(20, 30));
List<MemberDomain> memberConditionList = queryFactory.selectFrom(qm).where(builder).fetch();
使用BooleanBuilder
刃滓,更復(fù)雜的查詢關(guān)系也不怕。
例如
BooleanBuilder builder = new BooleanBuilder();
builder.and(qm.address.contains("廈門"));
BooleanBuilder builder2 = new BooleanBuilder();
builder2.or(qm.status.eq("0013"));
builder2.or(qm.status.eq("0014"));
builder.and(builder2);
List<MemberDomain> memberComplexConditionList = queryFactory.selectFrom(qm).where(builder).fetch();
1.2.3 多表查詢
//以左關(guān)聯(lián)為例-left join
QMemberDomain qm = QMemberDomain.memberDomain;
QFavoriteInfoDomain qf= QFavoriteInfoDomain.favoriteInfoDomain;
List<MemberDomain> leftJoinList = queryFactory.selectFrom(qm).leftJoin(qm.favoriteInfoDomains,qf).where(qf.favoriteStoreCode.eq("0721")).fetch();
1.2.4 使用Mysql聚合函數(shù)
//聚合函數(shù)-avg()
Double averageAge = queryFactory.select(qm.age.avg()).from(qm).fetchOne();
//聚合函數(shù)-concat()
String concat = queryFactory.select(qm.name.concat(qm.address)).from(qm).fetchOne();
//聚合函數(shù)-date_format()
String date = queryFactory.select(Expressions.stringTemplate("DATE_FORMAT({0},'%Y-%m-%d')", qm.registerDate)).from(qm).fetchOne();
當(dāng)用到DATE_FORMAT這類QueryDSL似乎沒有提供支持的Mysql函數(shù)時耸弄,我們可以手動拼一個String表達式咧虎。這樣就可以無縫使用Mysql中的函數(shù)了。
1.2.5 使用子查詢
下面的用法中子查詢沒有什么實際意義计呈,只是作為一個寫法示例砰诵。
//子查詢
List<MemberDomain> subList = queryFactory.selectFrom(qm).where(qm.status.in(JPAExpressions.select(qm.status).from(qm))).fetch();
1.2.6 排序
//排序
List<MemberDomain> orderList = queryFactory.selectFrom(qm).orderBy(qm.name.asc()).fetch();
1.2.7 分頁的兩種寫法
QMemberDomain qm = QMemberDomain.memberDomain;
//寫法一
JPAQuery<MemberDomain> query = queryFactory.selectFrom(qm).orderBy(qm.age.asc());
long total = query.fetchCount();//hfetchCount的時候上面的orderBy不會被執(zhí)行
List<MemberDomain> list0= query.offset(2).limit(5).fetch();
//寫法二
QueryResults<MemberDomain> results = queryFactory.selectFrom(qm).orderBy(qm.age.asc()).offset(2).limit(5).fetchResults();
List<MemberDomain> list = results.getResults();
logger.debug("total:"+results.getTotal());
logger.debug("limit:"+results.getLimit());
logger.debug("offset:"+results.getOffset());
寫法一和二都會發(fā)出兩條sql進行查詢,一條查詢count捌显,一條查詢具體數(shù)據(jù)茁彭。
寫法二的getTotal()
等價于寫法一的fetchCount
。
無論是哪種寫法扶歪,在查詢count的時候理肺,orderBy、limit善镰、offset這三個都不會被執(zhí)行妹萨。可以大膽使用炫欺。
1.2.8 使用Template實現(xiàn)QueryDSL未支持的語法
其實Template我們在1.2.4 使用Mysql聚合函數(shù)
中已經(jīng)使用過了乎完。QueryDSL并沒有對Mysql的所有函數(shù)提供支持,好在它給我們提供了Template特性品洛。我們可以使用Template來實現(xiàn)各種QueryDSL未直接支持的語法树姨。
示例如下。
QMemberDomain qm = QMemberDomain.memberDomain;
//使用booleanTemplate充當(dāng)where子句或where子句的一部分
List<MemberDomain> list = queryFactory.selectFrom(qm).where(Expressions.booleanTemplate("{} = \"tofu\"", qm.name)).fetch();
//上面的寫法毫别,當(dāng)booleanTemplate中需要用到多個占位時
List<MemberDomain> list1 = queryFactory.selectFrom(qm).where(Expressions.booleanTemplate("{0} = \"tofu\" and {1} = \"Amoy\"", qm.name,qm.address)).fetch();
//使用stringTemplate充當(dāng)查詢語句的某一部分
String date = queryFactory.select(Expressions.stringTemplate("DATE_FORMAT({0},'%Y-%m-%d')", qm.registerDate)).from(qm).fetchFirst();
//在where子句中使用stringTemplate
String id = queryFactory.select(qm.id).from(qm).where(Expressions.stringTemplate("DATE_FORMAT({0},'%Y-%m-%d')", qm.registerDate).eq("2018-03-19")).fetchFirst();
不過Template好用歸好用娃弓,但也有其局限性。
例如當(dāng)我們需要用到復(fù)雜的正則表達式匹配的時候岛宦,就有些捉襟見肘了台丛。這是由于Template中使用了{}
來作為占位符,而正則表達式中也可能使用了{}
,因而會產(chǎn)生沖突挽霉。
2. QueryDslPredicateExecutor
我們通常使用Repository來繼承QueryDslPredicateExecutor<T>
接口防嗡。通過注入Repository來使用。
繼承
@Repository
public interface IMemberDomainRepository extends JpaRepository<MemberDomain,String>,QueryDslPredicateExecutor<MemberDomain> {
}
注入
@Autowired
IMemberDomainRepository memberRepo;
2.1 查詢
簡單查詢
QMemberDomain qm = QMemberDomain.memberDomain;
Iterable<MemberDomain> iterable = memberRepo.findAll(qm.status.eq("0013"));
也可以使用更優(yōu)雅的BooleanBuilder
來進行條件分支管理
BooleanBuilder builder = new BooleanBuilder();
builder.and(qm.address.contains("廈門"));
builder.and(qm.status.eq("0013"));
Iterable<MemberDomain> iterable2 = memberRepo.findAll(builder);
QueryDslPredicateExecutor<T>
接口提供了findOne()
,findAll()
,count()
,exists()
四個方法來支持查詢侠坎。
count()
會返回滿足查詢條件的數(shù)據(jù)行的數(shù)量,exists()
會根據(jù)所要查詢的數(shù)據(jù)是否存在返回一個boolean值蚁趁,都很簡單,因此不再贅述实胸。
下面著重進行介紹findOne()
和findAll()
兩個關(guān)鍵查詢方法他嫡。
2.1.1 findOne()
findOne,顧名思義庐完,從數(shù)據(jù)庫中查出一條數(shù)據(jù)钢属。沒有重載方法。
和JPAQuery
的fetchOne()
一樣门躯,當(dāng)根據(jù)查詢條件從數(shù)據(jù)庫中查詢到多條匹配數(shù)據(jù)時淆党,會拋NonUniqueResultException
。使用的時候需要慎重讶凉。
2.1.2 findAll()
findAll是從數(shù)據(jù)庫中查出匹配的所有數(shù)據(jù)染乌。提供了以下幾個重載方法。
- findAll(Predicate predicate)
- findAll(OrderSpecifier<?>... orders)
- findAll(Predicate predicate,OrderSpecifier<?>... orders)
- findAll(Predicate predicate,Sort sort)
第一個重載方法是不帶排序的懂讯,第二個重載方法是只帶QueryDSL提供的OrderSpecifier方式實現(xiàn)排序而不帶查詢條件的荷憋,而第三個方法則是既有條件又有排序的。
因此我們直接來看第三個方法的使用示例域醇。
QMemberDomain qm = QMemberDomain.memberDomain;
OrderSpecifier<Integer> order = new OrderSpecifier<>(Order.DESC, qm.age);
Iterable<MemberDomain> iterable = memberRepo.findAll(qm.status.eq("0013"),order);
除了QueryDSL提供的排序?qū)崿F(xiàn)台谊,我們還有支持Spring Data提供的Sort的第四個重載方法。示例如下
QMemberDomain qm = QMemberDomain.memberDomain;
Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC, "age"));
Iterable<MemberDomain> iterable = memberRepo.findAll(qm.status.eq("0013"), sort);
三譬挚、使用心得
1. 查詢條件中字段為String時關(guān)于null,empty,blank的表達
(如果你還不了解null,empty,blank的區(qū)別锅铅,請先自行搜索了解)
QueryDSL為String類型的字段提供了.isEmpty()
,isNull()
,.isNotEmpty()
,isNotNull()
這四個函數(shù)支持,唯獨沒有對blank提供支持。經(jīng)過測試减宣,我發(fā)現(xiàn)可以通過這種方式來實現(xiàn)對blank的使用:.eq("")
,.ne("")
四盐须、參考
五、擴展閱讀
以上贼邓。
希望我的文章對你能有所幫助。
我不能保證文中所有說法的百分百正確闷尿,
但我能保證它們都是我的理解和感悟以及拒絕直接復(fù)制黏貼(確實需要引用的部分我會附上源地址)塑径。
有什么意見、見解或疑惑填具,歡迎留言討論统舀。