JPA 之 QueryDSL-JPA 使用指南
概述及依赖、插件、生成查询实体1.Querydsl支持代码自动完成,因为是纯Java API编写查询,因此主流Java IDE对起的代码自动完成功能支持几乎可以发挥到极致(因为是纯Java代码,所以支持很好)
2.Querydsl几乎可以避免所有的SQL语法错误(当然用错了Querydsl API除外,因为不写SQL了,因此想用错也难)
3.Querydsl采用Domain类型的对象和属性来构建查询,因此查询绝对是类型安全的,不会因为条件类型而出现问题
4.Querydsl采用纯Java API的作为SQL构建的实现可以让代码重构发挥到另一个高度
5.Querydsl的领一个优势就是可以更轻松的进行增量查询的定义
使用
在Spring环境下,可以通过两种风格来使用QueryDSL。
一种是使用JPAQueryFactory的原生QueryDSL风格, 另一种是基于Spring Data提供的QueryDslPredicateExecutor
使用QueryDslPredicateExecutor
依赖
1234567891011
添加maven插件(pom.xml)
添加这个插件是为了让程序自动生成query type(查询实体,命名方式为:“Q”+对应实体名)。
上文引入的依赖中querydsl-apt即是为此插件服务的。
注:在使用过程中,如果遇到query type无法自动生成的情况,用maven更新一下项目即可解决(右键项目->Maven->Update Project)。
123456789101112131415161718192021222324补充:
QueryDSL默认使用HQL发出查询语句。但也支持原生SQL查询。
若要使用原生SQL查询,你需要使用下面这个maven插件生成相应的query type。
12345678910111213141516171819202122232425262728293031
生成查询实体
idea 工具 为maven project 自动添加了对应的功能。添加好依赖和 plugin 插件后,就可以生成查询实体了。
打开右侧的 Maven Projects,如下图所示:
在这里插入图片描述
双击 clean 清除已编译的 target
双击 compile 命令执行,执行完成后会在 pom.xml 配置文件内配置生成目录内生成对应实体的 QueryDSL 查询实体。
生成的查询实体如下图所示:
在这里插入图片描述
JPAQueryFactory 风格QueryDSL 在支持JPA的同时,也提供了对 Hibernate 的支持。可以通过 HibernateQueryFactory 来使用。
装配 与 注入SpringBoot注解方式装配
/**
方式一。使用Spring的@Configuration注解注册实例进行容器托管
*/
@Configuration
public class QueryDslConfig {
@Bean
public JPAQueryFactory jpaQueryFactory(EntityManager em){
return new JPAQueryFactory(em);
}
}/**
方式二。在Dao类中初始化*/// 实体管理@Autowiredprivate EntityManager entityManager;// 查询工厂private JPAQueryFactory queryFactory;// 初始化JPA查询工厂@PostConstruct // Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)public void init(){queryFactory = new JPAQueryFactory(entityManager);}1234567891011121314151617181920212223242526注入
@Autowired
private JPAQueryFactory queryFactory;
12
更新、删除JPAQueryFactory 更新
在Querydsl JPA中,更新语句是简单的 update-set/where-execute 形式。
execute()执行后返回的是被更新的实体的数量。
注意:使用QueryDsl更新实体时需要添加事务
@Test@Transactionalpublic void testUpdate() {QStudent qStudent = QStudent.student;Long result = queryFactory.update(qStudent).set(qStudent.name, "haha") // 可以用if条件判断更新值来确定字段是否.set().setnull(qStudent.age) // 设置null值.where(qStudent.id.eq(111L)).execute();assertThat(result, equalTo(1L));}12345678910
JPAQueryFactory 删除
删除语句是简单的 delete-where-execute 形式。
注意:使用QueryDsl删除实体时需要添加事务
@Test@Transactionalpublic void testDelete() {QStudent qStudent = QStudent.student;//删除指定条件的记录Long result = queryFactory.delete(qStudent).where(qStudent.id.eq(111L)).execute();assertThat(result, equalTo(1L));
//删除所有记录。即不加where条件
Long totalResult = queryFactory.delete(qStudent).execute();
System.out.println(totalResult);
}1234567891011121314
查询表达式工具类Expressions 表达式工具类
// when-then 条件表达式函数。when传参必须为名为eqTrue或eqFalse的PredicateT cases().when(Predicate).then(T a).otherwise(T b)
DateExpression
// exprs 均为名为eqTrue的Predicate ,则返回名为eqTrue的Predicate,否则返回eqFalse的PredicateBooleanExpression allOf(BooleanExpression... exprs)// exprs 至少存在一个名为eqTrue的Predicate,则返回名为eqTrue的Predicate,否则返回eqFalse的PredicateBooleanExpression anyOf(BooleanExpression... exprs)
// 转类型为 BooleanExpression。特别注意:asBoolean(Boolean).isTrue() 才是可用PredicateBooleanExpression asBoolean(Boolean) // asBoolean(true) <==等价==> booleanPath("true")NumberExpression asNumber(T)StringrExpression asString(T)DateExpression asDate(T)TimeExpression asTime(T)DateTimeExpression asDateTime(T)
// 自定义语法StringTemplate stringTemplate(String template, Object... args)NumberTemplate
123456789101112131415161718192021222324MathExpressions 数学表达式工具类
NumberExpression round(Expression num) // 四舍五入取整NumberExpression round(Expression num, int s) // 四舍五入保留 s 位小数
NumberExpression
// 慎用!qdsl-jpa底层是调用random()函数,MySQL没有该函数,只有rand()函数,会报错,解决方案为使用QDSL-SQL查询NumberExpression
表达式方法注意:在select()中查询出的结果使用表达式方法处理过后,若要封装到实体类中,则都需要使用 .as(alias) 起别名指定封装到实体类中的哪个字段。
SimpleExpression 简单表达式 extends DslExpression extends Expression
// 给查询字段取别名T as(alias)
BooleanExpression eq(T right) // 等于 equalBooleanExpression eqAll(T... right)BooleanExpression eqAny(T... right)BooleanExpression ne(T right) // 不等于 not equalBooleanExpression neAll(T... right)BooleanExpression neAny(T... right)
BooleanExpression in(T... right)BooleanExpression notIn(T... right)
BooleanExpression isNotNull()BooleanExpression isNull()
// 相当于java中的switch语句。两种写法T when(A).then(B).otherwise(C)
// 该字段的查询结果等于参数则返回null,不等于则返回查询结果。Field == A ? null : FieldSimpleExpression
// 符合过滤条件的的总条数。 select count(table.id) from tableNumberExpression
123456789101112131415161718192021222324ComparableExpressionBase extends SimpleExpression
// 设置默认值。返回 Field, A, B ... 顺序中第一个非null的值,若都为null则返回null// 注意:使用该方法兜底Oracle数据库的null为空字符串时会失效,因为Oracle会把空字符串当作nullT coalesce(A, B ...)123NumberExpression 数值表达式 extends ComparableExpressionBase
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
StringExpression stringValue() // 返回字符串表达式
// 数据类型转换为数字类型。type为数字基本类型的包装类.class。实体类接收字段需与type的类型一致。NumberExpression
123456789101112131415161718192021222324ComparableExpression extends ComparableExpressionBase
BooleanExpression lt(T right) // 小于 less thanBooleanExpression ltAll(T... right)BooleanExpression ltAny(T... right)BooleanExpression gt(T right) // 大于 greater thanBooleanExpression gtAll(T... right)BooleanExpression gtAny(T... right)
BooleanExpression loe(T right) // 小于等于 less than or equalBooleanExpression loeAll(T... right)BooleanExpression loeAny(T... right)BooleanExpression goe(T right) // 大于等于 greater than or equalBooleanExpression goeAll(T... right)BooleanExpression goeAny(T... right)
BooleanExpression between(from, to) // from和to之间 [from, to]BooleanExpression notBetween(from, to)
12345678910111213141516BooleanExpression 布尔表达式 extends LiteralExpression (extends ComparableExpression) implements Predicate
BooleanExpression isTrue() // 计算结果若为true,则返回名为eqTrue的Predicate,否则返回名为eqFalse的PredicateBooleanExpression isFalse() // 计算结果若为false,则返回名为eqTrue的Predicate,否则返回名为eqFalse的PredicateBooleanExpression not() // 返回相反的结果
BooleanExpression eq(Boolean right)
BooleanExpression and(Predicate right)BooleanExpression andAnyOf(Predicate... predicates)BooleanExpression or(Predicate right)BooleanExpression orAllOf(Predicate... predicates)12345678910StringExpressions 字符串表达式 extends LiteralExpression extends ComparableExpression
StringExpression contains(String str) // 包含参数字符串
BooleanExpression isEmpty() // 判断是否为空BooleanExpression isNotEmpty()
// 正则匹配查询BooleanExpression matches(Expression
// 将字母转换大小写StringExpression toLowerCase()StringExpression toUpperCase()StringExpression lower()StringExpression upper()
StringExpression trim() // 去掉字符串两端的空格StringExpression substring(int beginIndex) // 截取子字符串从索引位置至末尾StringExpression concat(str) // 拼接 strStringExpression append(str) // 在末尾添加 strStringExpression prepend(str) // 在前面添加 str
NumberExpression
1234567891011121314151617181920212223242526272829
select() 和 fetch() 的常用写法注意:使用fetch()查询时,数据库没有符合该条件的数据时,返回的是空集合,而不是null。
QMemberDomain qm = QMemberDomain.memberDomain;//查询字段-select()List
//查询实体-selectFrom()List
//查询并将结果封装至dto中List
//去重查询-selectDistinct()List
//获取首个查询结果-fetchFirst()MemberDomain firstMember = queryFactory.selectFrom(qm).fetchFirst();
//获取唯一查询结果-fetchOne()//当fetchOne()根据查询条件从数据库中查询到多条匹配数据时,会抛NonUniqueResultException。MemberDomain anotherFirstMember = queryFactory.selectFrom(qm).fetchOne();
1234567891011121314151617181920212223242526272829303132333435363738
where 子句查询条件的常用写法//查询条件示例List
.fetch();1234567891011使用QueryDSL提供的BooleanBuilder来进行查询条件管理。
BooleanBuilder builder = new BooleanBuilder();// likebuilder.and(qm.name.like('%'+"Jack"+'%'));// containbuilder.and(qm.address.contains("厦门"));// equal示例builder.and(qm.status.eq("0013"));// betweenbuilder.and(qm.age.between(20, 30));List
// 复杂的查询关系BooleanBuilder builder2 = new BooleanBuilder();builder2.or(qm.status.eq("0013"));builder2.or(qm.status.eq("0014"));builder.and(builder2);List
123456789101112131415161718
自定义封装查询的结果集方法一:使用Projections的Bean方法
JPAQueryFactory查询工厂的select方法可以将Projections方法返回的QBean作为参数,通过Projections的bean方法来构建返回的结果集映射到实体内,有点像Mybatis内的ResultMap的形式,不过内部处理机制肯定是有着巨大差别的!
bean方法第一个参数需要传递一个实体的泛型类型作为返回集合内的单个对象类型,如果QueryDSL查询实体内的字段与DTO实体的字段名字不一样时,可以采用as方法来处理,为查询的结果集指定的字段添加别名,这样就会自动映射到DTO实体内。
return queryFactory.select(Projections.bean(PersonIDCardDto.class,QIDCard.iDCard.idNo,QPerson.person.address,QPerson.person.name.as("userName"))) // 使用别名对应dto内的userName.from(QIDCard.iDCard, QPerson.person).where(predicate).fetch();123456789底层原理:
使用数据封装类的无参构造方法创建对象(如果类上有使用@Builder注解导致@Data无参构造方法被覆盖,则会报错,可以再加上 @AllArgsConstructor,@NoArgsConstructor 注解)使用setter方法封装数据给字段(会校验数据封装字段和Entity对应字段的数据类型是否一致,不一致则会报错)常见问题:
Entity中时间字段的数据类型为 java.util.Date,数据封装类中时间字段的数据类型为 java.sql.Date 或具有指定时间格式的String类型,数据类型不一致,导致数据无法封装成功方案1:修改数据封装类或Entity中时间的数据类型,使其类型一致方案2:数据封装类中新增util.Date类型字段,手动重写其setter方法,处理数据后赋值到原sql.Date类型字段上。注意:查询封装数据到字段时 as(“新增的util.Date字段”)方案3:数据封装类中新增util.Date类型字段,先将数据封装到该字段,再通过getter、setter方法处理数据后赋值到原sql.Date类型字段上。
方法二:使用Projections的fields方法
return queryFactory.select(Projections.fields(PersonIDCardDto.class,QIDCard.iDCard.idNo,QPerson.person.address,QPerson.person.name)).from(QIDCard.iDCard, QPerson.person).where(predicate).fetch();123456789
方法三:使用Projections的constructor方法,注意构造方法中参数的顺序
return queryFactory.select(Projections.constructor(PersonIDCardDto.class,QPerson.person.name,QPerson.person.address,QIDCard.iDCard.idNo)).from(QIDCard.iDCard, QPerson.person).where(predicate).fetch();123456789
方式四:使用集合的stream转换
从方法开始到fetch()结束完全跟QueryDSL没有任何区别,采用了最原始的方式进行返回结果集,但是从fetch()获取到结果集后处理的方式就有所改变了。
fetch()方法返回的类型是泛型List(List),List继承了Collection,完全存在使用Collection内非私有方法的权限,通过调用stream方法可以将集合转换成Stream泛型对象,该对象的map方法可以操作集合内单个对象的转换,具体的转换代码可以根据业务逻辑进行编写。
在map方法内有个lambda表达式参数tuple,通过tuple对象get方法就可以获取对应select方法内的查询字段。
注意:tuple只能获取select内存在的字段,如果select内为一个实体对象,tuple无法获取指定字段的值。
/**
* 使用java8新特性Collection内stream方法转换dto
*/
public List
//商品基本信息
QGoodInfoBean goodBean = QGoodInfoBean.goodInfoBean;
//商品类型
QGoodTypeBean goodTypeBean = QGoodTypeBean.goodTypeBean;
return queryFactory
.select(
goodBean.id,
goodBean.price,
goodTypeBean.name,
goodTypeBean.id)
.from(goodBean,goodTypeBean) //构建两表笛卡尔集
.where(goodBean.typeId.eq(goodTypeBean.id)) //关联两表
.orderBy(goodBean.order.desc()) //倒序
.fetch()
.stream()
//转换集合内的数据
.map(tuple -> {
//创建商品dto
GoodDTO dto = new GoodDTO();
//设置商品编号
dto.setId(tuple.get(goodBean.id));
//设置商品价格
dto.setPrice(tuple.get(goodBean.price));
//设置类型编号
dto.setTypeId(tuple.get(goodTypeBean.id));
//设置类型名称
dto.setTypeName(tuple.get(goodTypeBean.name));
//返回本次构建的dto
return dto;
})
//返回集合并且转换为List
.collect(Collectors.toList());
}
12345678910111213141516171819202122232425262728293031323334353637
排序、分页排序
.asc() // 升序.desc() // 降序.asc().nullsFirst() // 升序,空值放前面.asc().nullsLast() // 降序,空值放前面1234//排序List
分页
.limit(long limit) // 限制查询结果返回的数量。即一页多少条记录(pageSize).offset(long offset) // 跳过多少行。offset = ( pageNum - 1 ) * pageSize // pageNum:第几页12QMemberDomain qm = QMemberDomain.memberDomain;//写法一JPAQuery
//写法二。fetchResults()自动实现count查询和结果查询,并封装到QueryResults
QueryResults
.selectFrom(qm)
.orderBy(qm.age.asc())
.offset(2)
.limit(5)
.fetchResults();
List
logger.debug("total:"+results.getTotal()); // 符合过滤条件的的总条数
logger.debug("offset:"+results.getOffset()); // 跳过多少条符合过滤条件的查询结果
logger.debug("limit:"+results.getLimit()); // 限制查询结果返回的条数
123456789101112131415161718192021写法一和二都会发出两条sql进行查询,一条查询count,一条查询具体数据。
写法二的getTotal()等价于写法一的fetchCount。
无论是哪种写法,在查询count的时候,orderBy、limit、offset这三个都不会被执行。可以大胆使用。
子查询// 子查询作为where条件内容@Testpublic void selectJPAExpressions() {List
// 子查询作为select查询字段
@Test
public void selectJPAExpressions() {
QUserAddress ua = QUserAddress.userAddress;
QUser u = QUser.user;
List
.select(
Projections.bean(UserAddressDTO.class
, ua.addressee
, Expressions.asNumber(
JPAExpressions
.select(u.id.count())
.from(u)
.where(u.id.ne(ua.userId))
)
.longValue() // asNumber接收子查询结果后需要指定数值的数据类型
.as("lon")
// , Expressions.asString( // asString接收子查询结果后不用指定数据类型// JPAExpressions.// select(u.username)// .from(u)// .where(u.id.eq(ua.userId))// )// .as("password"))).from(ua).where(ua.id.eq(38)).fetch();}
1234567891011121314151617181920212223242526272829303132333435363738394041
联表动态查询// JPA查询工厂@Autowiredprivate JPAQueryFactory queryFactory;
/**
* 关联查询示例,查询出城市和对应的旅店
*/
@Test
public void findCityAndHotel() {
QTCity qtCity = QTCity.tCity;
QTHotel qtHotel = QTHotel.tHotel;
JPAQuery
.select(qtCity, qtHotel)
.from(qtCity)
.leftJoin(qtHotel)
.on(qtHotel.city.longValue().eq(qtCity.id.longValue()));
// 分离式 添加查询条件
jpaQuery.where(QTCity.tCity.name.like("shanghai"));
// 获取查询结果
List
// 对多元组取出数据,这个和select时的数据相匹配
for (Tuple row : result) {
System.out.println("qtCity:" + row.get(qtCity));
System.out.println("qtHotel:" + row.get(qtHotel));
System.out.println("--------------------");
}
}
12345678910111213141516171819202122232425262728293031
联表一对多查询封装方式一:查询结果返回类型为List
List
1234567891011121314151617
方式二:查询结果返回类型为Map
map的key为分组字段,一般为主键ID
Map
12345678910111213141516
实体类:
@Data@Builder@AllArgsConstructor@NoArgsConstructorpublic class UserAddressDTO {
private Integer id;
private String username;
private String password;
private String phone;
private List
}12345678910111213
使用聚合函数//聚合函数-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();
12345678910111213141516171819当用到DATE_FORMAT这类QueryDSL似乎没有提供支持的Mysql函数时,可以手动拼一个String表达式。这样就可以无缝使用Mysql中的函数了。
使用 Template 实现自定义语法QueryDSL并没有对数据库的所有函数提供支持,好在它提供了Template特性。
可以使用Template来实现各种QueryDSL未直接支持的语法。
Template的局限性:
由于Template中使用了{}来作为占位符(内部序号从0开始),而正则表达式中也可能使用了{},因而会产生冲突。
QMemberDomain qm = QMemberDomain.memberDomain;
//使用booleanTemplate充当where子句或where子句的一部分List
//上面的写法,当booleanTemplate中需要用到多个占位时List
//使用stringTemplate充当查询语句的某一部分String date = queryFactory.select(Expressions.stringTemplate("DATE_FORMAT({0},'%Y-%m-%d')",qm.registerDate)).from(qm).fetchFirst();
//在where子句中使用stringTemplateString id = queryFactory.select(qm.id).from(qm).where(Expressions.stringTemplate("DATE_FORMAT({0},'%Y-%m-%d')",qm.registerDate).eq("2018-03-19")).fetchFirst();
123456789101112131415161718192021222324252627282930313233
QueryDslPredicateExecutor 风格通常使用Repository来继承QueryDslPredicateExecutor
Repository 接口
Spring Data JPA中提供了QueryDslPredicateExecutor接口,用于支持QueryDSL的查询操作。
public interface tityRepository extends JpaRepository
QueryDslPredicateExecutor
count()会返回满足查询条件的数据行的数量exists()会根据所要查询的数据是否存在返回一个boolean值
findOne()、findAll()findOne
从数据库中查出一条数据。没有重载方法。
Optional
findAll()
findAll是从数据库中查出匹配的所有数据。提供了以下几个重载方法。
Iterable
Iterable
Iterable
Iterable
Page
QMemberDomain qm = QMemberDomain.memberDomain;// QueryDSL 提供的排序实现OrderSpecifier
QMemberDomain qm = QMemberDomain.memberDomain;// Spring Data 提供的排序实现Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC, "age"));Iterable
单表动态分页查询单表动态查询示例:
//动态条件QTCity qtCity = QTCity.tCity; //SDL实体类//该Predicate为querydsl下的类,支持嵌套组装复杂查询条件Predicate predicate = qtCity.id.longValue().lt(3).and(qtCity.name.like("shanghai"));//分页排序Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC,"id"));PageRequest pageRequest = new PageRequest(0,10,sort);//查找结果Page
Querydsl SQL 查询Querydsl SQL 模块提供与 JDBC API 的集成。可以使用更多的 JDBC SQL方法。比如可以实现 from 的查询主体为子查询出来的临时表、union、union All 等Querydsl-JPA限制的相关操作。还可以根据 JDBC API 获取数据库的类型使用不同的数据库语法模板。
依赖及配置依赖:
1234567891011yaml配置:
logging:level:com.querydsl.sql: debug # 打印日志123
SQLQuery 的 Q 类需要自己创建编写(可以基于apt 插件生成的 JPA 的Q类改造),并放到主目录(src)启动类下的包里。
使用 extends RelationalPathBase
需要将数据库表名传入构造方法的table参数里,path 可以传别名,所有的property参数为实体类的属性名(驼峰命名),addMetadata() 中ColumnMetadata.named("FeildNmae") 的 FeildNmae 为数据库字段名。
使用该Q类查询所有字段数据时(即select(Q类))可以自动映射封装结果集。
使用extends EntityPathBase
需要将传入构造方法的variable参数改成数据库表名,并且将所有的property参数改成相对应的数据库字段名。
**注意:**使用 extends EntityPathBase
/**
extends RelationalPathBase
extends EntityPathBase
SQLQueryFactory 方式装配及基本使用装配
@Configuration@Slf4jpublic class QueryDslConfig {@Beanpublic SQLQueryFactory sqlQueryFactory(DataSource druidDataSource){SQLTemplates t;try(Connection connection = druidDataSource.getConnection()){t = new SQLTemplatesRegistry().getTemplates(connection.getMetaData());}catch (Exception e){log.error("", e);t = SQLTemplates.DEFAULT;}com.querydsl.sql.Configuration configuration = new com.querydsl.sql.Configuration(t);configuration.addListener(new SQLBaseListener(){@Overridepublic void end(SQLListenerContext context) {if (context != null && !DataSourceUtils.isConnectionTransactional(context.getConnection(), druidDataSource)){// 若非事务连接SQLCloseListener.DEFAULT.end(context);}}});configuration.setExceptionTranslator(new SpringExceptionTranslator());// 创建SQLQueryFactory,且数据库连接由spring管理return new SQLQueryFactory(configuration, () -> DataSourceUtils.getConnection(druidDataSource));}}
123456789101112131415161718192021222324252627注入
@Autowired
private SQLQueryFactory sqlQueryFactory;
12SQLQueryFactory 基本使用
/**
* 子查询作为临时表传入from()中
*/
@Test
public void selectBySqlQueryFactory(){
// 使用 extends RelationalPathBase
QUserAddressSql uaSql = QUserAddressSql.userAddress;
// 子查询
SQLQuery
.select(
// 查询字段须是数据库表中的字段名(不是实体属性名),且类型一致
uaSql.addressee
, uaSql.userId
)
.from(uaSql);
List
.select(
// 查询字段须是临时表中的字段别名,且类型一致
Expressions.template(String.class, "q.addressee").as("addressee")
, Expressions.numberTemplate(Integer.class, "q.user_id").as("userId")
)
.from(q, Expressions.stringPath("q")) // 子查询作为临时表
.fetch();
System.out.println(fetch);
}
/**
* 子查询结果集 union
*/
@Test
public void selectBySqlQueryFactory(){
// 使用 extends EntityPathBase
QUserAddressSql uaSql = QUserAddressSql.userAddress;
QUserSql uSql = QUserSql.user;
SQLQuery
.select(uaSql.userId.as("user_id") , uaSql.phone)
.from(uaSql)
.where(uaSql.userId.eq(30));
SQLQuery
.select(uSql.id.as("user_id") , uSql.phone)
.from(uSql)
.where(uSql.id.eq(29).or(uSql.id.eq(30)));
Union
long count = sqlQueryFactory
.from(union, Expressions.stringPath("q")).fetchCount();
List
.from(union, Expressions.stringPath("q"))
.orderBy(Expressions.numberPath(Integer.class, "user_id").desc()
, Expressions.stringTemplate("phone").desc())
.offset(0)
.limit(5)
.transform(
GroupBy.groupBy(Expressions.template(String.class, "q.user_id")).list(
Projections.bean(UserAddressDTO.class
, Expressions.template(Integer.class, "q.user_id").as("userId")
, GroupBy.list(Projections.bean(UserAddress.class
, Expressions.stringTemplate("q.phone").as("phone")
)).as("userAddresses")
)));
System.out.println(count);
list.forEach(s -> System.out.println(JSON.toJSONString(s)));
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
SQLExpression 表达式工具类// 合并多张表记录。union为去重合并,unionAll为不去重合并static
// 调用函数查询序列static SimpleExpression
// 将多列记录聚合为一列记录。delimiter为分隔符。Oracle数据库专属,其他数据库报错static WithinGroup
// 将多列记录聚合为一列记录。separator为分隔符。MySQL、PostgreSQL都可用,PostgreSQL会根据模板翻译成String_agg函数static StringExpression groupConcat(Expression
static
static
static
// 获取两个日期的时间间隔(end-start)static
1234567891011121314151617181920212223242526272829303132333435
JPASQLQuery 方式使用 JPASQLQuery 作为查询引擎时,使用的QEntity(extends EntityPathBase
故并不能直接使用 apt 插件生成 的 jpa 使用的 Q类,仍需要使用改造版的 Q类(extends EntityPathBase
@Test
public void selectBySqlQueryFactory(){
// 使用 extends EntityPathBase
QUserAddress ua = QUserAddress.userAddress;
// jpa+sql的查询工具,本例使用的oracle的sql模板
JPASQLQuery> jpasqlQuery = new JPASQLQuery
// 子查询
SQLQuery
.select(
// 查询字段须是数据库表中的字段名(不是实体属性名),且类型一致。如直接不使用QEntity的属性,则需手动指定
Expressions.stringPath("addressee").as("addressee")
, Expressions.numberPath(Integer.class, "user_id").as("user_id")
)
.from(ua);
List
.select(
// 查询字段须是临时表中的字段名或别名,且类型一致。结果集字段需添加别名手动映射封装
Expressions.template(String.class, "q.addressee").as("addressee")
, Expressions.numberTemplate(Integer.class, "q.user_id").as("userId")
)
.from(q, Expressions.stringPath("q")) // 子查询作为临时表
.fetch();
System.out.println(fetch);
}
1.Querydsl支持代码自动完成,因为是纯Java API编写查询,因此主流Java IDE对起的代码自动完成功能支持几乎可以发挥到极致(因为是纯Java代码,所以支持很好)
2.Querydsl几乎可以避免所有的SQL语法错误(当然用错了Querydsl API除外,因为不写SQL了,因此想用错也难)
3.Querydsl采用Domain类型的对象和属性来构建查询,因此查询绝对是类型安全的,不会因为条件类型而出现问题
4.Querydsl采用纯Java API的作为SQL构建的实现可以让代码重构发挥到另一个高度
5.Querydsl的领一个优势就是可以更轻松的进行增量查询的定义
使用
在Spring环境下,可以通过两种风格来使用QueryDSL。
一种是使用JPAQueryFactory的原生QueryDSL风格, 另一种是基于Spring Data提供的QueryDslPredicateExecutor
使用QueryDslPredicateExecutor
依赖
1234567891011
添加maven插件(pom.xml)
添加这个插件是为了让程序自动生成query type(查询实体,命名方式为:“Q”+对应实体名)。
上文引入的依赖中querydsl-apt即是为此插件服务的。
注:在使用过程中,如果遇到query type无法自动生成的情况,用maven更新一下项目即可解决(右键项目->Maven->Update Project)。
123456789101112131415161718192021222324
补充:
QueryDSL默认使用HQL发出查询语句。但也支持原生SQL查询。
若要使用原生SQL查询,你需要使用下面这个maven插件生成相应的query type。
12345678910111213141516171819202122232425262728293031
生成查询实体
idea 工具 为maven project 自动添加了对应的功能。添加好依赖和 plugin 插件后,就可以生成查询实体了。
打开右侧的 Maven Projects,如下图所示:
双击 clean 清除已编译的 target
双击 compile 命令执行,执行完成后会在 pom.xml 配置文件内配置生成目录内生成对应实体的 QueryDSL 查询实体。
生成的查询实体如下图所示:
JPAQueryFactory 风格QueryDSL 在支持JPA的同时,也提供了对 Hibernate 的支持。可以通过 HibernateQueryFactory 来使用。
装配 与 注入SpringBoot注解方式装配
/**
* 方式一。使用Spring的@Configuration注解注册实例进行容器托管
*/
@Configuration
public class QueryDslConfig {
@Bean
public JPAQueryFactory jpaQueryFactory(EntityManager em){
return new JPAQueryFactory(em);
}
}
/**
* 方式二。在Dao类中初始化
*/
// 实体管理
@Autowired
private EntityManager entityManager;
// 查询工厂
private JPAQueryFactory queryFactory;
// 初始化JPA查询工厂
@PostConstruct // Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
public void init(){
queryFactory = new JPAQueryFactory(entityManager);
}
1234567891011121314151617181920212223242526
注入
@Autowired
private JPAQueryFactory queryFactory;
12
更新、删除JPAQueryFactory 更新
在Querydsl JPA中,更新语句是简单的 update-set/where-execute 形式。
execute()执行后返回的是被更新的实体的数量。
注意:使用QueryDsl更新实体时需要添加事务
@Test
@Transactional
public void testUpdate() {
QStudent qStudent = QStudent.student;
Long result = queryFactory.update(qStudent)
.set(qStudent.name, "haha") // 可以用if条件判断更新值来确定字段是否.set()
.setnull(qStudent.age) // 设置null值
.where(qStudent.id.eq(111L)).execute();
assertThat(result, equalTo(1L));
}
12345678910
JPAQueryFactory 删除
删除语句是简单的 delete-where-execute 形式。
注意:使用QueryDsl删除实体时需要添加事务
@Test
@Transactional
public void testDelete() {
QStudent qStudent = QStudent.student;
//删除指定条件的记录
Long result = queryFactory.delete(qStudent)
.where(qStudent.id.eq(111L))
.execute();
assertThat(result, equalTo(1L));
//删除所有记录。即不加where条件
Long totalResult = queryFactory.delete(qStudent).execute();
System.out.println(totalResult);
}
1234567891011121314
查询表达式工具类Expressions 表达式工具类
// when-then 条件表达式函数。when传参必须为名为eqTrue或eqFalse的Predicate
T cases().when(Predicate).then(T a).otherwise(T b)
DateExpression
TimeExpression
DateTimeExpression
// exprs 均为名为eqTrue的Predicate ,则返回名为eqTrue的Predicate,否则返回eqFalse的Predicate
BooleanExpression allOf(BooleanExpression... exprs)
// exprs 至少存在一个名为eqTrue的Predicate,则返回名为eqTrue的Predicate,否则返回eqFalse的Predicate
BooleanExpression anyOf(BooleanExpression... exprs)
// 转类型为 BooleanExpression。特别注意:asBoolean(Boolean).isTrue() 才是可用Predicate
BooleanExpression asBoolean(Boolean) // asBoolean(true) <==等价==> booleanPath("true")
NumberExpression asNumber(T)
StringrExpression asString(T)
DateExpression asDate(T)
TimeExpression asTime(T)
DateTimeExpression asDateTime(T)
// 自定义语法
StringTemplate stringTemplate(String template, Object... args)
NumberTemplate
BooleanTemplate booleanTemplate(String template, ImmutableList> args)
123456789101112131415161718192021222324
MathExpressions 数学表达式工具类
NumberExpression round(Expression num) // 四舍五入取整
NumberExpression round(Expression num, int s) // 四舍五入保留 s 位小数
NumberExpression
NumberExpression
// 慎用!qdsl-jpa底层是调用random()函数,MySQL没有该函数,只有rand()函数,会报错,解决方案为使用QDSL-SQL查询
NumberExpression
NumberExpression
123456789
表达式方法注意:在select()中查询出的结果使用表达式方法处理过后,若要封装到实体类中,则都需要使用 .as(alias) 起别名指定封装到实体类中的哪个字段。
SimpleExpression 简单表达式 extends DslExpression extends Expression
// 给查询字段取别名
T as(alias)
BooleanExpression eq(T right) // 等于 equal
BooleanExpression eqAll(T... right)
BooleanExpression eqAny(T... right)
BooleanExpression ne(T right) // 不等于 not equal
BooleanExpression neAll(T... right)
BooleanExpression neAny(T... right)
BooleanExpression in(T... right)
BooleanExpression notIn(T... right)
BooleanExpression isNotNull()
BooleanExpression isNull()
// 相当于java中的switch语句。两种写法
T when(A).then(B).otherwise(C)
// 该字段的查询结果等于参数则返回null,不等于则返回查询结果。Field == A ? null : Field
SimpleExpression
// 符合过滤条件的的总条数。 select count(table.id) from table
NumberExpression
123456789101112131415161718192021222324
ComparableExpressionBase extends SimpleExpression
// 设置默认值。返回 Field, A, B ... 顺序中第一个非null的值,若都为null则返回null
// 注意:使用该方法兜底Oracle数据库的null为空字符串时会失效,因为Oracle会把空字符串当作null
T coalesce(A, B ...)
123
NumberExpression 数值表达式 extends ComparableExpressionBase
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
NumberExpression
StringExpression stringValue() // 返回字符串表达式
// 数据类型转换为数字类型。type为数字基本类型的包装类.class。实体类接收字段需与type的类型一致。
NumberExpression
123456789101112131415161718192021222324
ComparableExpression extends ComparableExpressionBase
BooleanExpression lt(T right) // 小于 less than
BooleanExpression ltAll(T... right)
BooleanExpression ltAny(T... right)
BooleanExpression gt(T right) // 大于 greater than
BooleanExpression gtAll(T... right)
BooleanExpression gtAny(T... right)
BooleanExpression loe(T right) // 小于等于 less than or equal
BooleanExpression loeAll(T... right)
BooleanExpression loeAny(T... right)
BooleanExpression goe(T right) // 大于等于 greater than or equal
BooleanExpression goeAll(T... right)
BooleanExpression goeAny(T... right)
BooleanExpression between(from, to) // from和to之间 [from, to]
BooleanExpression notBetween(from, to)
12345678910111213141516
BooleanExpression 布尔表达式 extends LiteralExpression (extends ComparableExpression) implements Predicate
BooleanExpression isTrue() // 计算结果若为true,则返回名为eqTrue的Predicate,否则返回名为eqFalse的Predicate
BooleanExpression isFalse() // 计算结果若为false,则返回名为eqTrue的Predicate,否则返回名为eqFalse的Predicate
BooleanExpression not() // 返回相反的结果
BooleanExpression eq(Boolean right)
BooleanExpression and(Predicate right)
BooleanExpression andAnyOf(Predicate... predicates)
BooleanExpression or(Predicate right)
BooleanExpression orAllOf(Predicate... predicates)
12345678910
StringExpressions 字符串表达式 extends LiteralExpression extends ComparableExpression
StringExpression contains(String str) // 包含参数字符串
BooleanExpression isEmpty() // 判断是否为空
BooleanExpression isNotEmpty()
// 正则匹配查询
BooleanExpression matches(Expression
// 模糊查询。% 为通配符,_ 表一个字符,可以传参escape指定转义字符
BooleanExpression like(String str)
BooleanExpression like(String str, char escape)
BooleanExpression endsWith(str) // 判断字符串的后缀是否为str。注意:必须使用boolean数据类型的字段接收
BooleanExpression startsWith(str) // 判断字符串的前缀是否为str。注意:必须使用boolean数据类型的字段接收
// 将字母转换大小写
StringExpression toLowerCase()
StringExpression toUpperCase()
StringExpression lower()
StringExpression upper()
StringExpression trim() // 去掉字符串两端的空格
StringExpression substring(int beginIndex) // 截取子字符串从索引位置至末尾
StringExpression concat(str) // 拼接 str
StringExpression append(str) // 在末尾添加 str
StringExpression prepend(str) // 在前面添加 str
NumberExpression
NumberExpression
NumberExpression
SimpleExpression
1234567891011121314151617181920212223242526272829
select() 和 fetch() 的常用写法注意:使用fetch()查询时,数据库没有符合该条件的数据时,返回的是空集合,而不是null。
QMemberDomain qm = QMemberDomain.memberDomain;
//查询字段-select()
List
.select(qm.name)
.from(qm)
.fetch();
//查询实体-selectFrom()
List
.selectFrom(qm)
.fetch();
//查询并将结果封装至dto中
List
.select(
Projections.bean(MemberFavoriteDto.class,
qm.name,
qf.favoriteStoreCode))
.from(qm)
.leftJoin(qm.favoriteInfoDomains, qf)
.fetch();
//去重查询-selectDistinct()
List
.selectDistinct(qm.name)
.from(qm)
.fetch();
//获取首个查询结果-fetchFirst()
MemberDomain firstMember = queryFactory
.selectFrom(qm)
.fetchFirst();
//获取唯一查询结果-fetchOne()
//当fetchOne()根据查询条件从数据库中查询到多条匹配数据时,会抛`NonUniqueResultException`。
MemberDomain anotherFirstMember = queryFactory
.selectFrom(qm)
.fetchOne();
1234567891011121314151617181920212223242526272829303132333435363738
where 子句查询条件的常用写法//查询条件示例
List
//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();
1234567891011
使用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
// 复杂的查询关系
BooleanBuilder builder2 = new BooleanBuilder();
builder2.or(qm.status.eq("0013"));
builder2.or(qm.status.eq("0014"));
builder.and(builder2);
List
123456789101112131415161718
自定义封装查询的结果集方法一:使用Projections的Bean方法
JPAQueryFactory查询工厂的select方法可以将Projections方法返回的QBean作为参数,通过Projections的bean方法来构建返回的结果集映射到实体内,有点像Mybatis内的ResultMap的形式,不过内部处理机制肯定是有着巨大差别的!
bean方法第一个参数需要传递一个实体的泛型类型作为返回集合内的单个对象类型,如果QueryDSL查询实体内的字段与DTO实体的字段名字不一样时,可以采用as方法来处理,为查询的结果集指定的字段添加别名,这样就会自动映射到DTO实体内。
return queryFactory
.select(
Projections.bean(PersonIDCardDto.class,
QIDCard.iDCard.idNo,
QPerson.person.address,
QPerson.person.name.as("userName"))) // 使用别名对应dto内的userName
.from(QIDCard.iDCard, QPerson.person)
.where(predicate)
.fetch();
123456789
底层原理:
使用数据封装类的无参构造方法创建对象(如果类上有使用@Builder注解导致@Data无参构造方法被覆盖,则会报错,可以再加上 @AllArgsConstructor,@NoArgsConstructor 注解)使用setter方法封装数据给字段(会校验数据封装字段和Entity对应字段的数据类型是否一致,不一致则会报错)常见问题:
Entity中时间字段的数据类型为 java.util.Date,数据封装类中时间字段的数据类型为 java.sql.Date 或具有指定时间格式的String类型,数据类型不一致,导致数据无法封装成功方案1:修改数据封装类或Entity中时间的数据类型,使其类型一致方案2:数据封装类中新增util.Date类型字段,手动重写其setter方法,处理数据后赋值到原sql.Date类型字段上。注意:查询封装数据到字段时 as(“新增的util.Date字段”)方案3:数据封装类中新增util.Date类型字段,先将数据封装到该字段,再通过getter、setter方法处理数据后赋值到原sql.Date类型字段上。
方法二:使用Projections的fields方法
return queryFactory
.select(
Projections.fields(PersonIDCardDto.class,
QIDCard.iDCard.idNo,
QPerson.person.address,
QPerson.person.name))
.from(QIDCard.iDCard, QPerson.person)
.where(predicate)
.fetch();
123456789
方法三:使用Projections的constructor方法,注意构造方法中参数的顺序
return queryFactory
.select(
Projections.constructor(PersonIDCardDto.class,
QPerson.person.name,
QPerson.person.address,
QIDCard.iDCard.idNo))
.from(QIDCard.iDCard, QPerson.person)
.where(predicate)
.fetch();
123456789
方式四:使用集合的stream转换
从方法开始到fetch()结束完全跟QueryDSL没有任何区别,采用了最原始的方式进行返回结果集,但是从fetch()获取到结果集后处理的方式就有所改变了。
fetch()方法返回的类型是泛型List(List),List继承了Collection,完全存在使用Collection内非私有方法的权限,通过调用stream方法可以将集合转换成Stream泛型对象,该对象的map方法可以操作集合内单个对象的转换,具体的转换代码可以根据业务逻辑进行编写。
在map方法内有个lambda表达式参数tuple,通过tuple对象get方法就可以获取对应select方法内的查询字段。
注意:tuple只能获取select内存在的字段,如果select内为一个实体对象,tuple无法获取指定字段的值。
/**
* 使用java8新特性Collection内stream方法转换dto
*/
public List
//商品基本信息
QGoodInfoBean goodBean = QGoodInfoBean.goodInfoBean;
//商品类型
QGoodTypeBean goodTypeBean = QGoodTypeBean.goodTypeBean;
return queryFactory
.select(
goodBean.id,
goodBean.price,
goodTypeBean.name,
goodTypeBean.id)
.from(goodBean,goodTypeBean) //构建两表笛卡尔集
.where(goodBean.typeId.eq(goodTypeBean.id)) //关联两表
.orderBy(goodBean.order.desc()) //倒序
.fetch()
.stream()
//转换集合内的数据
.map(tuple -> {
//创建商品dto
GoodDTO dto = new GoodDTO();
//设置商品编号
dto.setId(tuple.get(goodBean.id));
//设置商品价格
dto.setPrice(tuple.get(goodBean.price));
//设置类型编号
dto.setTypeId(tuple.get(goodTypeBean.id));
//设置类型名称
dto.setTypeName(tuple.get(goodTypeBean.name));
//返回本次构建的dto
return dto;
})
//返回集合并且转换为List
.collect(Collectors.toList());
}
12345678910111213141516171819202122232425262728293031323334353637
排序、分页排序
.asc() // 升序
.desc() // 降序
.asc().nullsFirst() // 升序,空值放前面
.asc().nullsLast() // 降序,空值放前面
1234
//排序
List
.orderBy(qm.name.asc())
.fetch();
1234
分页
.limit(long limit) // 限制查询结果返回的数量。即一页多少条记录(pageSize)
.offset(long offset) // 跳过多少行。offset = ( pageNum - 1 ) * pageSize // pageNum:第几页
12
QMemberDomain qm = QMemberDomain.memberDomain;
//写法一
JPAQuery
.selectFrom(qm)
.orderBy(qm.age.asc());
// 查询总条数。fetchCount时,orderBy不会被执行
long total = query.fetchCount();
// 获取过滤后的查询结果集
List
//写法二。fetchResults()自动实现count查询和结果查询,并封装到QueryResults
QueryResults
.selectFrom(qm)
.orderBy(qm.age.asc())
.offset(2)
.limit(5)
.fetchResults();
List
logger.debug("total:"+results.getTotal()); // 符合过滤条件的的总条数
logger.debug("offset:"+results.getOffset()); // 跳过多少条符合过滤条件的查询结果
logger.debug("limit:"+results.getLimit()); // 限制查询结果返回的条数
123456789101112131415161718192021
写法一和二都会发出两条sql进行查询,一条查询count,一条查询具体数据。
写法二的getTotal()等价于写法一的fetchCount。
无论是哪种写法,在查询count的时候,orderBy、limit、offset这三个都不会被执行。可以大胆使用。
子查询 // 子查询作为where条件内容
@Test
public void selectJPAExpressions() {
List
.selectFrom(qm)
.where(qm.status.in(
JPAExpressions.select(qm.status).from(qm)))
.fetch();
}
// 子查询作为select查询字段
@Test
public void selectJPAExpressions() {
QUserAddress ua = QUserAddress.userAddress;
QUser u = QUser.user;
List
.select(
Projections.bean(UserAddressDTO.class
, ua.addressee
, Expressions.asNumber(
JPAExpressions
.select(u.id.count())
.from(u)
.where(u.id.ne(ua.userId))
)
.longValue() // asNumber接收子查询结果后需要指定数值的数据类型
.as("lon")
// , Expressions.asString( // asString接收子查询结果后不用指定数据类型
// JPAExpressions.
// select(u.username)
// .from(u)
// .where(u.id.eq(ua.userId))
// )
// .as("password")
)
)
.from(ua)
.where(ua.id.eq(38))
.fetch();
}
1234567891011121314151617181920212223242526272829303132333435363738394041
联表动态查询 // JPA查询工厂
@Autowired
private JPAQueryFactory queryFactory;
/**
* 关联查询示例,查询出城市和对应的旅店
*/
@Test
public void findCityAndHotel() {
QTCity qtCity = QTCity.tCity;
QTHotel qtHotel = QTHotel.tHotel;
JPAQuery
.select(qtCity, qtHotel)
.from(qtCity)
.leftJoin(qtHotel)
.on(qtHotel.city.longValue().eq(qtCity.id.longValue()));
// 分离式 添加查询条件
jpaQuery.where(QTCity.tCity.name.like("shanghai"));
// 获取查询结果
List
// 对多元组取出数据,这个和select时的数据相匹配
for (Tuple row : result) {
System.out.println("qtCity:" + row.get(qtCity));
System.out.println("qtHotel:" + row.get(qtHotel));
System.out.println("--------------------");
}
}
12345678910111213141516171819202122232425262728293031
联表一对多查询封装方式一:查询结果返回类型为List
List
.from(u)
.join(ua)
.on(ua.userId.eq(u.id))
.where(u.id.eq(31))
.transform(GroupBy.groupBy(u.id)
.list(
Projections.bean(UserAddressDTO.class,
u.id,
u.username,
GroupBy.list(
Projections.bean(UserAddress.class,
ua.address,
ua.city,
ua.district
)).as("userAddresses")))
);
1234567891011121314151617
方式二:查询结果返回类型为Map
map的key为分组字段,一般为主键ID
Map
.from(u)
.join(ua)
.on(ua.userId.eq(u.id))
.where(u.id.eq(31))
.transform(GroupBy.groupBy(u.id)
.as(
Projections.bean(UserAddressDTO.class,
u.id,
u.username,
GroupBy.list(Projections.bean(UserAddress.class,
ua.address,
ua.city,
ua.district
)).as("userAddresses")))
);
12345678910111213141516
实体类:
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserAddressDTO {
private Integer id;
private String username;
private String password;
private String phone;
private List
}
12345678910111213
使用聚合函数//聚合函数-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();
12345678910111213141516171819
当用到DATE_FORMAT这类QueryDSL似乎没有提供支持的Mysql函数时,可以手动拼一个String表达式。这样就可以无缝使用Mysql中的函数了。
使用 Template 实现自定义语法QueryDSL并没有对数据库的所有函数提供支持,好在它提供了Template特性。
可以使用Template来实现各种QueryDSL未直接支持的语法。
Template的局限性:
由于Template中使用了{}来作为占位符(内部序号从0开始),而正则表达式中也可能使用了{},因而会产生冲突。
QMemberDomain qm = QMemberDomain.memberDomain;
//使用booleanTemplate充当where子句或where子句的一部分
List
.selectFrom(qm)
.where(Expressions.booleanTemplate("{0} = \"tofu\"", qm.name))
.fetch();
//上面的写法,当booleanTemplate中需要用到多个占位时
List
.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();
123456789101112131415161718192021222324252627282930313233
QueryDslPredicateExecutor 风格通常使用Repository来继承QueryDslPredicateExecutor
Repository 接口
Spring Data JPA中提供了QueryDslPredicateExecutor接口,用于支持QueryDSL的查询操作。
public interface tityRepository extends JpaRepository
1
QueryDslPredicateExecutor
count()会返回满足查询条件的数据行的数量exists()会根据所要查询的数据是否存在返回一个boolean值
findOne()、findAll()findOne
从数据库中查出一条数据。没有重载方法。
Optional
1
和JPAQuery的fetchOne()一样,当根据查询条件从数据库中查询到多条匹配数据时,会抛NonUniqueResultException。使用的时候需要慎重。
findAll()
findAll是从数据库中查出匹配的所有数据。提供了以下几个重载方法。
Iterable
Iterable
Iterable
Iterable
Page
123456789
使用示例:
QMemberDomain qm = QMemberDomain.memberDomain;
// QueryDSL 提供的排序实现
OrderSpecifier
Iterable
QMemberDomain qm = QMemberDomain.memberDomain;
// Spring Data 提供的排序实现
Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC, "age"));
Iterable
123456789
单表动态分页查询单表动态查询示例:
//动态条件
QTCity qtCity = QTCity.tCity; //SDL实体类
//该Predicate为querydsl下的类,支持嵌套组装复杂查询条件
Predicate predicate = qtCity.id.longValue().lt(3).and(qtCity.name.like("shanghai"));
//分页排序
Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC,"id"));
PageRequest pageRequest = new PageRequest(0,10,sort);
//查找结果
Page
123456789
Querydsl SQL 查询Querydsl SQL 模块提供与 JDBC API 的集成。可以使用更多的 JDBC SQL方法。比如可以实现 from 的查询主体为子查询出来的临时表、union、union All 等Querydsl-JPA限制的相关操作。还可以根据 JDBC API 获取数据库的类型使用不同的数据库语法模板。
依赖及配置依赖:
1234567891011
yaml配置:
logging:
level:
com.querydsl.sql: debug # 打印日志
123
SQLQuery 的 Q 类需要自己创建编写(可以基于apt 插件生成的 JPA 的Q类改造),并放到主目录(src)启动类下的包里。
使用 extends RelationalPathBase
* extends RelationalPathBase
*/
public class QEmployee extends RelationalPathBase
private static final long serialVersionUID = 1394463749655231079L;
public static final QEmployee employee = new QEmployee("EMPLOYEE");
public final NumberPath
public final StringPath firstname = createString("firstname");
public final DatePath
public final PrimaryKey
public QEmployee(String path) {
super(Employee.class, PathMetadataFactory.forVariable(path), "PUBLIC", "EMPLOYEE");
addMetadata();
}
public QEmployee(PathMetadata metadata) {
super(Employee.class, metadata, "PUBLIC", "EMPLOYEE");
addMetadata();
}
protected void addMetadata() {
addMetadata(id, ColumnMetadata.named("ID").ofType(Types.INTEGER));
addMetadata(firstname, ColumnMetadata.named("FIRSTNAME").ofType(Types.VARCHAR));
addMetadata(datefield, ColumnMetadata.named("DATEFIELD").ofType(Types.DATE));
}
}
123456789101112131415161718192021222324252627282930313233
/**
* extends EntityPathBase
*/
public class QUserAddressS extends EntityPathBase
private static final long serialVersionUID = -1295712525L;
public static final QUserAddressS userAddress = new QUserAddressS("tb_user_address");
public final NumberPath
public final StringPath address = createString("address");
public final DateTimePath
public QUserAddressS(String variable) {
super(UserAddress.class, forVariable(variable));
}
public QUserAddressS(Path extends UserAddress> path) {
super(path.getType(), path.getMetadata());
}
public QUserAddressS(PathMetadata metadata) {
super(UserAddress.class, metadata);
}
}
123456789101112131415161718192021222324252627
SQLQueryFactory 方式装配及基本使用装配
@Configuration
@Slf4j
public class QueryDslConfig {
@Bean
public SQLQueryFactory sqlQueryFactory(DataSource druidDataSource){
SQLTemplates t;
try(Connection connection = druidDataSource.getConnection()){
t = new SQLTemplatesRegistry().getTemplates(connection.getMetaData());
}catch (Exception e){
log.error("", e);
t = SQLTemplates.DEFAULT;
}
com.querydsl.sql.Configuration configuration = new com.querydsl.sql.Configuration(t);
configuration.addListener(new SQLBaseListener(){
@Override
public void end(SQLListenerContext context) {
if (context != null && !DataSourceUtils.isConnectionTransactional(context.getConnection(), druidDataSource)){
// 若非事务连接
SQLCloseListener.DEFAULT.end(context);
}
}
});
configuration.setExceptionTranslator(new SpringExceptionTranslator());
// 创建SQLQueryFactory,且数据库连接由spring管理
return new SQLQueryFactory(configuration, () -> DataSourceUtils.getConnection(druidDataSource));
}
}
123456789101112131415161718192021222324252627
注入
@Autowired
private SQLQueryFactory sqlQueryFactory;
12
SQLQueryFactory 基本使用
/**
* 子查询作为临时表传入from()中
*/
@Test
public void selectBySqlQueryFactory(){
// 使用 extends RelationalPathBase
QUserAddressSql uaSql = QUserAddressSql.userAddress;
// 子查询
SQLQuery
.select(
// 查询字段须是数据库表中的字段名(不是实体属性名),且类型一致
uaSql.addressee
, uaSql.userId
)
.from(uaSql);
List
.select(
// 查询字段须是临时表中的字段别名,且类型一致
Expressions.template(String.class, "q.addressee").as("addressee")
, Expressions.numberTemplate(Integer.class, "q.user_id").as("userId")
)
.from(q, Expressions.stringPath("q")) // 子查询作为临时表
.fetch();
System.out.println(fetch);
}
/**
* 子查询结果集 union
*/
@Test
public void selectBySqlQueryFactory(){
// 使用 extends EntityPathBase
QUserAddressSql uaSql = QUserAddressSql.userAddress;
QUserSql uSql = QUserSql.user;
SQLQuery
.select(uaSql.userId.as("user_id") , uaSql.phone)
.from(uaSql)
.where(uaSql.userId.eq(30));
SQLQuery
.select(uSql.id.as("user_id") , uSql.phone)
.from(uSql)
.where(uSql.id.eq(29).or(uSql.id.eq(30)));
Union
long count = sqlQueryFactory
.from(union, Expressions.stringPath("q")).fetchCount();
List
.from(union, Expressions.stringPath("q"))
.orderBy(Expressions.numberPath(Integer.class, "user_id").desc()
, Expressions.stringTemplate("phone").desc())
.offset(0)
.limit(5)
.transform(
GroupBy.groupBy(Expressions.template(String.class, "q.user_id")).list(
Projections.bean(UserAddressDTO.class
, Expressions.template(Integer.class, "q.user_id").as("userId")
, GroupBy.list(Projections.bean(UserAddress.class
, Expressions.stringTemplate("q.phone").as("phone")
)).as("userAddresses")
)));
System.out.println(count);
list.forEach(s -> System.out.println(JSON.toJSONString(s)));
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
SQLExpression 表达式工具类// 合并多张表记录。union为去重合并,unionAll为不去重合并
static
static
static
static
// 调用函数查询序列
static SimpleExpression
static
// 使用示例:SQL写法:select seq_process_no.nextval from dual;
Long nextvalReturn = sqlQueryFactory.select(SQLExpressions.nextval("序列名")).fetchOne;
// 将多列记录聚合为一列记录。delimiter为分隔符。Oracle数据库专属,其他数据库报错
static WithinGroup
// 使用示例:
SQLExpression.listagg(qEntity.name, ",").withinGroup.OrderBy(qEntity.name.asc()).getValue.as("Name")
// 将多列记录聚合为一列记录。separator为分隔符。MySQL、PostgreSQL都可用,PostgreSQL会根据模板翻译成String_agg函数
static StringExpression groupConcat(Expression
static StringExpression groupConcat(Expression
static
static
static
static
static
// 获取两个日期的时间间隔(end-start)
static
1234567891011121314151617181920212223242526272829303132333435
JPASQLQuery 方式使用 JPASQLQuery 作为查询引擎时,使用的QEntity(extends EntityPathBase
故并不能直接使用 apt 插件生成 的 jpa 使用的 Q类,仍需要使用改造版的 Q类(extends EntityPathBase
@Test
public void selectBySqlQueryFactory(){
// 使用 extends EntityPathBase
QUserAddress ua = QUserAddress.userAddress;
// jpa+sql的查询工具,本例使用的oracle的sql模板
JPASQLQuery> jpasqlQuery = new JPASQLQuery
// 子查询
SQLQuery
.select(
// 查询字段须是数据库表中的字段名(不是实体属性名),且类型一致。如直接不使用QEntity的属性,则需手动指定
Expressions.stringPath("addressee").as("addressee")
, Expressions.numberPath(Integer.class, "user_id").as("user_id")
)
.from(ua);
List
.select(
// 查询字段须是临时表中的字段名或别名,且类型一致。结果集字段需添加别名手动映射封装
Expressions.template(String.class, "q.addressee").as("addressee")
, Expressions.numberTemplate(Integer.class, "q.user_id").as("userId")
)
.from(q, Expressions.stringPath("q")) // 子查询作为临时表
.fetch();
System.out.println(fetch);
}