转载自:https://www.jianshu.com/p/69dcb1b85bbb
com.querydsl
querydsl-jpa
com.querydsl
querydsl-apt
provided
添加这个插件是为了让程序自动生成query type(查询实体,命名方式为:"Q"+对应实体名)。
上文引入的依赖中querydsl-apt
即是为此插件服务的。
注:在使用过程中,如果遇到query type无法自动生成的情况,用maven更新一下项目即可解决(右键项目->Maven->Update Project)。
com.mysema.maven
apt-maven-plugin
1.1.3
process
target/generatedsources/java
com.querydsl.apt.jpa.JPAAnnotationProcessor
在Spring环境下,我们可以通过两种风格来使用QueryDSL。
一种是使用JPAQueryFactory
的原生QueryDSL风格,
另一种是基于Spring Data提供的QueryDslPredicateExecutor
的Spring-data风格。
使用QueryDslPredicateExecutor
可以简化一些代码,使得查询更加优雅。
而JPAQueryFactory
的优势则体现在其功能的强大,支持更复杂的查询业务。甚至可以用来进行更新和删除操作。
下面分别介绍两种风格的使用方式。
JPAQueryFactory使用逻辑类似于HQL/SQL语法,不再额外说明。
QueryDSL在支持JPA的同时,也提供了对Hibernate的支持。可以通过HibernateQueryFactory
来使用。
装配
@Bean
public JPAQueryFactory jpaQuery(EntityManager entityManager) {
return new JPAQueryFactory(entityManager);
}
注入
@Autowired
JPAQueryFactory queryFactory;
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 select()和fetch()的几种常用写法
QMemberDomain qm = QMemberDomain.memberDomain;
//查询字段-select()
List nameList = queryFactory.select(qm.name).from(qm).fetch();
//查询实体-selectFrom()
List memberList = queryFactory.selectFrom(qm).fetch();
//查询并将结果封装至dto中
List dtoList = queryFactory.select(Projections.constructor(MemberFavoriteDto.class,qm.name,qf.favoriteStoreCode)).from(qm).leftJoin(qm.favoriteInfoDomains,qf).fetch();
//去重查询-selectDistinct()
List distinctNameList = queryFactory.selectDistinct(qm.name).from(qm).fetch();
//获取首个查询结果-fetchFirst()
MemberDomain firstMember = queryFactory.selectFrom(qm).fetchFirst();
//获取唯一查询结果-fetchOne()
//当fetchOne()根据查询条件从数据库中查询到多条匹配数据时,会抛`NonUniqueResultException`。
MemberDomain anotherFirstMember = queryFactory.selectFrom(qm).fetchOne();
1.2.2 where子句查询条件的几种常用写法
//查询条件示例
List 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();
1.2.3 多表查询
//以左关联为例-left join
QMemberDomain qm = QMemberDomain.memberDomain;
QFavoriteInfoDomain qf= QFavoriteInfoDomain.favoriteInfoDomain;
List leftJoinList = queryFactory.selectFrom(qm).leftJoin(qm.favoriteInfoDomains,qf).where(qf.favoriteStoreCode.eq("0721")).fetch();
1.2.4 使用Mysql聚合函数
//聚合函数-avg()
Double averageAge = queryFactory.select(qm.age.avg()).from(qm).fetchOne();
//聚合函数-concat()
String concat = queryFactory.select(qm.name.concat(qm.address)).from(qm).fetchOne();
//聚合函数-date_format()
String date = queryFactory.select(Expressions.stringTemplate("DATE_FORMAT({0},'%Y-%m-%d')", qm.registerDate)).from(qm).fetchOne();
当用到DATE_FORMAT这类QueryDSL似乎没有提供支持的Mysql函数时,我们可以手动拼一个String表达式。这样就可以无缝使用Mysql中的函数了。
1.2.5 使用子查询
下面的用法中子查询没有什么实际意义,只是作为一个写法示例。
//子查询
List subList = queryFactory.selectFrom(qm).where(qm.status.in(JPAExpressions.select(qm.status).from(qm))).fetch();
1.2.6 排序
//排序
List orderList = queryFactory.selectFrom(qm).orderBy(qm.name.asc()).fetch();
1.2.7 分页的两种写法
QMemberDomain qm = QMemberDomain.memberDomain;
//写法一
JPAQuery query = queryFactory.selectFrom(qm).orderBy(qm.age.asc());
long total = query.fetchCount();//hfetchCount的时候上面的orderBy不会被执行
List list0= query.offset(2).limit(5).fetch();
//写法二
QueryResults results =
queryFactory.selectFrom(qm).orderBy(qm.age.asc()).offset(2).limit(5)
.fetchResults();
List list = results.getResults();
logger.debug("total:"+results.getTotal());
logger.debug("limit:"+results.getLimit());
logger.debug("offset:"+results.getOffset());
写法一和二都会发出两条sql进行查询,一条查询count,一条查询具体数据。
写法二的getTotal()
等价于写法一的fetchCount
。
无论是哪种写法,在查询count的时候,orderBy、limit、offset这三个都不会被执行。可以大胆使用。
1.2.8 使用Template实现QueryDSL未支持的语法
其实Template我们在1.2.4 使用Mysql聚合函数
中已经使用过了。QueryDSL并没有对Mysql的所有函数提供支持,好在它给我们提供了Template特性。我们可以使用Template来实现各种QueryDSL未直接支持的语法。
示例如下。
QMemberDomain qm = QMemberDomain.memberDomain;
//使用booleanTemplate充当where子句或where子句的一部分
List list = queryFactory.selectFrom(qm).where(Expressions.booleanTemplate("{} = \"tofu\"", qm.name)).fetch();
//上面的写法,当booleanTemplate中需要用到多个占位时
List list1 = queryFactory.selectFrom(qm).where(Expressions.booleanTemplate("{0} = \"tofu\" and {1} = \"Amoy\"", qm.name,qm.address)).fetch();
//使用stringTemplate充当查询语句的某一部分
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好用归好用,但也有其局限性。
例如当我们需要用到复杂的正则表达式匹配的时候,就有些捉襟见肘了。这是由于Template中使用了{}
来作为占位符,而正则表达式中也可能使用了{}
,因而会产生冲突。
(如果你还不了解null,empty,blank的区别,请先自行搜索了解)
QueryDSL为String类型的字段提供了.isEmpty()
,isNull()
,.isNotEmpty()
,isNotNull()
这四个函数支持,唯独没有对blank提供支持。经过测试,我发现可以通过这种方式来实现对blank的使用:.eq("")
,.ne("")