JPA 之 QueryDSL-JPA 使用指南
创始人
2024-05-29 09:58:02
0

Querydsl-JPA 框架(推荐)

官网:传送门

参考:

  • JPA整合Querydsl入门篇
  • SpringBoot环境下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的Spring-data风格。

使用QueryDslPredicateExecutor可以简化一些代码,使得查询更加优雅。 而JPAQueryFactory的优势则体现在其功能的强大,支持更复杂的查询业务。甚至可以用来进行更新和删除操作。


依赖

 	com.querydslquerydsl-aptcom.querydslquerydsl-jpa

添加maven插件(pom.xml)

添加这个插件是为了让程序自动生成query type(查询实体,命名方式为:“Q”+对应实体名)。

上文引入的依赖中querydsl-apt即是为此插件服务的。

注:在使用过程中,如果遇到query type无法自动生成的情况,用maven更新一下项目即可解决(右键项目->Maven->Update Project)。

 com.mysema.mavenapt-maven-plugin1.1.3generate-sourcesprocesstarget/generated-sources/javacom.querydsl.apt.jpa.JPAAnnotationProcessor

补充:

QueryDSL默认使用HQL发出查询语句。但也支持原生SQL查询。

若要使用原生SQL查询,你需要使用下面这个maven插件生成相应的query type。

com.querydslquerydsl-maven-plugin${querydsl.version}exportorg.apache.derby.jdbc.EmbeddedDriverjdbc:derby:target/demoDB;create=truecom.mycompany.mydomain${project.basedir}/target/generated-sources/javaorg.apache.derbyderby${derby.version}


生成查询实体

idea 工具 为maven project 自动添加了对应的功能。添加好依赖和 plugin 插件后,就可以生成查询实体了。

打开右侧的 Maven Projects,如下图所示:

在这里插入图片描述

双击 clean 清除已编译的 target

双击 compile 命令执行,执行完成后会在 pom.xml 配置文件内配置生成目录内生成对应实体的 QueryDSL 查询实体。

生成的查询实体如下图所示:

在这里插入图片描述


JPAQueryFactory 风格

QueryDSL 在支持JPA的同时,也提供了对 Hibernate 的支持。可以通过 HibernateQueryFactory 来使用。

装配 与 注入

SpringBoot注解方式装配

/*** 方式一。使用Spring的@Configuration注解注册实例进行容器托管*/
@Configuration
public class QueryDslConfig {@Beanpublic 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);}

注入

    @Autowiredprivate JPAQueryFactory queryFactory;

更新、删除

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));
}

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);
}

查询

表达式工具类

Expressions 表达式工具类

// when-then 条件表达式函数。when传参必须为名为eqTrue或eqFalse的Predicate
T cases().when(Predicate).then(T a).otherwise(T b)DateExpression currentDate()			// 返回当前日历(年-月-日)的 DateExpression
TimeExpression

MathExpressions 数学表达式工具类

NumberExpression round(Expression num)			// 四舍五入取整
NumberExpression round(Expression num, int s)		// 四舍五入保留 s 位小数NumberExpression asin(Expression num)		// 返回num的反正弦值。-1 <= num <= 1,否则返回null
NumberExpression acos(Expression num)		// 返回num的反余弦值。-1 <= num <= 1,否则返回null// 慎用!qdsl-jpa底层是调用random()函数,MySQL没有该函数,只有rand()函数,会报错,解决方案为使用QDSL-SQL查询
NumberExpression random()			// 返回0到1内的随机值
NumberExpression random(int seed)	// 返回一个指定的0到1内的随机值

表达式方法

注意:在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 nullif(A)// 符合过滤条件的的总条数。		select count(table.id) from table
NumberExpression count()

ComparableExpressionBase extends SimpleExpression

// 设置默认值。返回 Field, A, B ... 顺序中第一个非null的值,若都为null则返回null
// 注意:使用该方法兜底Oracle数据库的null为空字符串时会失效,因为Oracle会把空字符串当作null
T coalesce(A, B ...)

NumberExpression 数值表达式 extends ComparableExpressionBase

NumberExpression add(A)			// 加    
NumberExpression subtract(A)		// 减  
NumberExpression multiply(A)		// 乘  
NumberExpression divide(A)		// 除  
NumberExpression mod(A)			// 返回余数NumberExpression floor()		// 向下取整
NumberExpression ceil()		// 向上取整
NumberExpression round()		// 四舍五入取整NumberExpression max()			// 返回指定数值列的最大值
NumberExpression min()			// 返回指定数值列的最小值
NumberExpression sqrt()			// 返回指定数值列的平方根NumberExpression sum()			// 返回指定数值列(或分组相同数值列)的总数
NumberExpression avg()			// 返回指定数值列(或分组相同数值列)的平均数NumberExpression abs()			// 返回指定数值列的绝对值
NumberExpression negate()		// 返回指定数值列的相反数StringExpression stringValue()	// 返回字符串表达式// 数据类型转换为数字类型。type为数字基本类型的包装类.class。实体类接收字段需与type的类型一致。
NumberExpression castToNum(Class type)

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)

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)

StringExpressions 字符串表达式 extends LiteralExpression extends ComparableExpression

StringExpression contains(String str)	// 包含参数字符串BooleanExpression isEmpty()		// 判断是否为空
BooleanExpression isNotEmpty()	// 正则匹配查询
BooleanExpression matches(Expression regex)
// 模糊查询。% 为通配符,_ 表一个字符,可以传参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)	// 在前面添加 strNumberExpression length()			// 返回字符串长度
NumberExpression locate(str)		// 返回 str 的位置(从1开始),没有返回0
NumberExpression indexOf(str)		// 返回 str 索引(从0开始),没有返回-1
SimpleExpression charAt(int i)	// 返回参数索引位置的字符。实体类接收字段需为char或CharacterSS

select() 和 fetch() 的常用写法

注意:使用fetch()查询时,数据库没有符合该条件的数据时,返回的是空集合,而不是null。

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.bean(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();

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();

使用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 memberConditionList = queryFactory.selectFrom(qm).where(builder).fetch();// 复杂的查询关系
BooleanBuilder builder2 = new BooleanBuilder();
builder2.or(qm.status.eq("0013"));
builder2.or(qm.status.eq("0014"));
builder.and(builder2);
List memberConditionList = queryFactory.selectFrom(qm).where(builder2).fetch();

自定义封装查询的结果集

方法一:使用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();

底层原理:

  1. 使用数据封装类的无参构造方法创建对象(如果类上有使用@Builder注解导致@Data无参构造方法被覆盖,则会报错,可以再加上 @AllArgsConstructor,@NoArgsConstructor 注解)
  2. 使用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();

方法三:使用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();

方式四:使用集合的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 selectWithStream() {//商品基本信息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 -> {//创建商品dtoGoodDTO 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));//返回本次构建的dtoreturn dto;})//返回集合并且转换为List.collect(Collectors.toList());}

排序、分页

排序

.asc()		// 升序
.desc()		// 降序
.asc().nullsFirst()		// 升序,空值放前面
.asc().nullsLast()		// 降序,空值放前面
//排序
List orderList = queryFactory.selectFrom(qm).orderBy(qm.name.asc()).fetch();

分页

.limit(long limit)		// 限制查询结果返回的数量。即一页多少条记录(pageSize)
.offset(long offset)	// 跳过多少行。offset = ( pageNum - 1 ) * pageSize		// pageNum:第几页
    QMemberDomain qm = QMemberDomain.memberDomain;//写法一JPAQuery query = queryFactory.selectFrom(qm).orderBy(qm.age.asc());// 查询总条数。fetchCount时,orderBy不会被执行long total = query.fetchCount(); // 获取过滤后的查询结果集List list0= query.offset(2).limit(5).fetch();//写法二。fetchResults()自动实现count查询和结果查询,并封装到QueryResults中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("offset:"+results.getOffset());	// 跳过多少条符合过滤条件的查询结果logger.debug("limit:"+results.getLimit());		// 限制查询结果返回的条数

写法一和二都会发出两条sql进行查询,一条查询count,一条查询具体数据。

写法二的getTotal()等价于写法一的fetchCount

无论是哪种写法,在查询count的时候,orderBy、limit、offset这三个都不会被执行。可以大胆使用。


子查询

	// 子查询作为where条件内容@Testpublic void selectJPAExpressions() {List subList = queryFactory.selectFrom(qm).where(qm.status.in(JPAExpressions.select(qm.status).from(qm))).fetch();}// 子查询作为select查询字段@Testpublic void selectJPAExpressions() {QUserAddress ua = QUserAddress.userAddress;QUser u = QUser.user;List list = queryFactory.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();}

联表动态查询

    // JPA查询工厂@Autowiredprivate JPAQueryFactory queryFactory; /*** 关联查询示例,查询出城市和对应的旅店*/@Testpublic void findCityAndHotel() {QTCity qtCity = QTCity.tCity;QTHotel qtHotel = QTHotel.tHotel;JPAQuery jpaQuery = queryFactory.select(qtCity, qtHotel).from(qtCity).leftJoin(qtHotel).on(qtHotel.city.longValue().eq(qtCity.id.longValue()));// 分离式 添加查询条件jpaQuery.where(QTCity.tCity.name.like("shanghai"));// 获取查询结果List result = jpaQuery.fetch();// 对多元组取出数据,这个和select时的数据相匹配for (Tuple row : result) {System.out.println("qtCity:" + row.get(qtCity));System.out.println("qtHotel:" + row.get(qtHotel));System.out.println("--------------------");}}

联表一对多查询封装

方式一:查询结果返回类型为List

List list = queryFactory.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"))));

方式二:查询结果返回类型为Map

map的key为分组字段,一般为主键ID

Map map = queryFactory.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"))));

实体类:

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserAddressDTO {private Integer id;private String username;private String password;private String phone;private List userAddresses;
}

使用聚合函数

//聚合函数-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中的函数了。


使用 Template 实现自定义语法

QueryDSL并没有对数据库的所有函数提供支持,好在它提供了Template特性。

可以使用Template来实现各种QueryDSL未直接支持的语法。

Template的局限性:

  由于Template中使用了{}来作为占位符(内部序号从0开始),而正则表达式中也可能使用了{},因而会产生冲突。

QMemberDomain qm = QMemberDomain.memberDomain;//使用booleanTemplate充当where子句或where子句的一部分
List list = queryFactory.selectFrom(qm).where(Expressions.booleanTemplate("{0} = \"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();

QueryDslPredicateExecutor 风格

通常使用Repository来继承QueryDslPredicateExecutor接口。通过注入Repository来使用。

Repository 接口

Spring Data JPA中提供了QueryDslPredicateExecutor接口,用于支持QueryDSL的查询操作。

public interface tityRepository extends JpaRepository, QuerydslPredicateExecutor {}

QueryDslPredicateExecutor接口提供了findOne()findAll()count()exists()四个方法来支持查询。并可以使用更优雅的BooleanBuilder 来进行条件分支管理。

  • count()会返回满足查询条件的数据行的数量
  • exists()会根据所要查询的数据是否存在返回一个boolean值

findOne()、findAll()

findOne

从数据库中查出一条数据。没有重载方法。

Optional findOne(Predicate var1);

JPAQueryfetchOne()一样,当根据查询条件从数据库中查询到多条匹配数据时,会抛NonUniqueResultException。使用的时候需要慎重。


findAll()

findAll是从数据库中查出匹配的所有数据。提供了以下几个重载方法。

Iterable findAll(Predicate var1);Iterable findAll(Predicate var1, Sort var2);Iterable findAll(Predicate var1, OrderSpecifier... var2);Iterable findAll(OrderSpecifier... var1);Page findAll(Predicate var1, Pageable var2);

使用示例:

QMemberDomain qm = QMemberDomain.memberDomain;
// QueryDSL 提供的排序实现
OrderSpecifier order = new OrderSpecifier<>(Order.DESC, qm.age);
Iterable iterable = memberRepo.findAll(qm.status.eq("0013"),order);QMemberDomain qm = QMemberDomain.memberDomain;
// Spring Data 提供的排序实现
Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC, "age"));
Iterable iterable = memberRepo.findAll(qm.status.eq("0013"), sort);

单表动态分页查询

单表动态查询示例:

//动态条件
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 tCityPage = tCityRepository.findAll(predicate, pageRequest);

Querydsl SQL 查询

Querydsl SQL 模块提供与 JDBC API 的集成。可以使用更多的 JDBC SQL方法。比如可以实现 from 的查询主体为子查询出来的临时表unionunion All 等Querydsl-JPA限制的相关操作。还可以根据 JDBC API 获取数据库的类型使用不同的数据库语法模板。

依赖及配置

依赖:

        com.querydslquerydsl-sql${querydsl.version}joda-timejoda-time2.10.5

yaml配置:

logging:level:com.querydsl.sql: debug		# 打印日志

SQLQuery 的 Q 类

需要自己创建编写(可以基于apt 插件生成的 JPA 的Q类改造),并放到主目录(src)启动类下的包里。

  • 使用 extends RelationalPathBase 的Q类。推荐

    需要将数据库表名传入构造方法的table参数里,path 可以传别名,所有的property参数为实体类的属性名(驼峰命名),addMetadata()ColumnMetadata.named("FeildNmae")FeildNmae 为数据库字段名。

    使用该Q类查询所有字段数据时(即select(Q类))可以自动映射封装结果集。

  • 使用extends EntityPathBase的Q类。

    需要将传入构造方法的variable参数改成数据库表名,并且将所有的property参数改成相对应的数据库字段名

    **注意:**使用 extends EntityPathBase 的实体Q类,直接 select(Q类) 会报错,无法自动映射封装结果集,需要使用Projections.bean(Entity.class,Expression... exprs) 手动封装结果集。

/*** extends RelationalPathBase 的Q类示例*/
public class QEmployee extends RelationalPathBase {private static final long serialVersionUID = 1394463749655231079L;public static final QEmployee employee = new QEmployee("EMPLOYEE");public final NumberPath id = createNumber("id", Integer.class);public final StringPath firstname = createString("firstname");public final DatePath datefield = createDate("datefield", java.util.Date.class);public final PrimaryKey idKey = createPrimaryKey(id);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));}
}
/*** extends EntityPathBase 的Q类示例*/
public class QUserAddressS extends EntityPathBase {private static final long serialVersionUID = -1295712525L;public static final QUserAddressS userAddress = new QUserAddressS("tb_user_address");public final NumberPath id = createNumber("id", Integer.class);public final StringPath address = createString("address");public final DateTimePath createTime = createDateTime("create_time", java.util.Date.class);public QUserAddressS(String variable) {super(UserAddress.class, forVariable(variable));}public QUserAddressS(Path path) {super(path.getType(), path.getMetadata());}public QUserAddressS(PathMetadata metadata) {super(UserAddress.class, metadata);}
}

SQLQueryFactory 方式

装配及基本使用

装配

@Configuration
@Slf4j
public 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));}
}

注入

    @Autowiredprivate SQLQueryFactory sqlQueryFactory;

SQLQueryFactory 基本使用

    /*** 子查询作为临时表传入from()中*/@Testpublic void selectBySqlQueryFactory(){// 使用 extends RelationalPathBase 的QEntity,自动映射封装QUserAddressSql uaSql = QUserAddressSql.userAddress;// 子查询SQLQuery q = SQLExpressions.select(// 查询字段须是数据库表中的字段名(不是实体属性名),且类型一致uaSql.addressee, uaSql.userId).from(uaSql);List fetch = sqlQueryFactory.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*/@Testpublic void selectBySqlQueryFactory(){// 使用 extends EntityPathBase 的改造版QEntity,结果集如需封装到实体类,必须手动指定实体类来接收QUserAddressSql uaSql = QUserAddressSql.userAddress;QUserSql uSql = QUserSql.user;SQLQuery a = SQLExpressions.select(uaSql.userId.as("user_id") , uaSql.phone).from(uaSql).where(uaSql.userId.eq(30));SQLQuery b = SQLExpressions.select(uSql.id.as("user_id") , uSql.phone).from(uSql).where(uSql.id.eq(29).or(uSql.id.eq(30)));Union union = sqlQueryFactory.query().union(a, b);long count = sqlQueryFactory.from(union, Expressions.stringPath("q")).fetchCount();List list = sqlQueryFactory.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)));}

SQLExpression 表达式工具类

// 合并多张表记录。union为去重合并,unionAll为不去重合并
static  Union union(SubQueryExpression... sq)
static  Union union(List> sq)
static  Union unionAll(SubQueryExpression... sq)
static  Union unionAll(List> sq)// 调用函数查询序列
static SimpleExpression nextval(String sequence)
static  SimpleExpression nextval(Class type, String sequence)
// 使用示例:SQL写法:select seq_process_no.nextval from dual;
Long nextvalReturn = sqlQueryFactory.select(SQLExpressions.nextval("序列名")).fetchOne;// 将多列记录聚合为一列记录。delimiter为分隔符。Oracle数据库专属,其他数据库报错
static WithinGroup listagg(Expression expr, String delimiter)
// 使用示例:
SQLExpression.listagg(qEntity.name, ",").withinGroup.OrderBy(qEntity.name.asc()).getValue.as("Name")// 将多列记录聚合为一列记录。separator为分隔符。MySQL、PostgreSQL都可用,PostgreSQL会根据模板翻译成String_agg函数
static StringExpression groupConcat(Expression expr, String separator)
static StringExpression groupConcat(Expression expr)static  RelationalFunctionCall relationalFunctionCall(Class type, String function, Object... args)static  DateExpression date(DateTimeExpression dateTime)
static  DateExpression date(Class type, DateTimeExpression dateTime)static  DateTimeExpression dateadd(DatePart unit, DateTimeExpression date, int amount)
static  DateExpression dateadd(DatePart unit, DateExpression date, int amount)// 获取两个日期的时间间隔(end-start)
static  NumberExpression datediff(DatePart unit, DateTimeExpression start, DateTimeExpression end)
 

JPASQLQuery 方式

使用 JPASQLQuery 作为查询引擎时,使用的QEntity(extends EntityPathBase,传入构造方法的 variable 参数可以不为数据库表名(因为 JPASQLQuery 可以找到映射的真实表名,仅把此参数作为表别名),但所有的 property 参数仍必需为相对应的数据库字段名

故并不能直接使用 apt 插件生成 的 jpa 使用的 Q类,仍需要使用改造版的 Q类(extends EntityPathBase

	@Testpublic void selectBySqlQueryFactory(){// 使用 extends EntityPathBase 的改造版QEntity,结果集如需封装到实体类,必须手动指定实体类来接收QUserAddress ua = QUserAddress.userAddress;// jpa+sql的查询工具,本例使用的oracle的sql模板JPASQLQuery jpasqlQuery = new JPASQLQuery(em, new OracleTemplates());// 子查询SQLQuery q = SQLExpressions.select(// 查询字段须是数据库表中的字段名(不是实体属性名),且类型一致。如直接不使用QEntity的属性,则需手动指定Expressions.stringPath("addressee").as("addressee"), Expressions.numberPath(Integer.class, "user_id").as("user_id")).from(ua);List fetch = jpasqlQuery.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);}

相关内容

热门资讯

Python|位运算|数组|动... 目录 1、只出现一次的数字(位运算,数组) 示例 选项代...
张岱的人物生平 张岱的人物生平张岱(414年-484年),字景山,吴郡吴县(今江苏苏州)人。南朝齐大臣。祖父张敞,东...
西游西后传演员女人物 西游西后传演员女人物西游西后传演员女人物 孙悟空 六小龄童 唐僧 徐少华 ...
名人故事中贾岛作诗内容简介 名人故事中贾岛作诗内容简介有一次,贾岛骑驴闯了官道.他正琢磨着一句诗,名叫《题李凝幽居》全诗如下:闲...
和男朋友一起优秀的文案? 和男朋友一起优秀的文案?1.希望是惟一所有的人都共同享有的好处;一无所有的人,仍拥有希望。2.生活,...
戴玉手镯的好处 戴玉手镯好还是... 戴玉手镯的好处 戴玉手镯好还是碧玺好 女人戴玉?戴玉好还是碧玺好点佩戴手镯,以和田玉手镯为佳!相嫌滑...
依然什么意思? 依然什么意思?依然(汉语词语)依然,汉语词汇。拼音:yī    rán基本解释:副词,指照往常、依旧...
高尔基的散文诗 高尔基的散文诗《海燕》、《大学》、《母亲》、《童年》这些都是比较出名的一些代表作。
心在飞扬作者简介 心在飞扬作者简介心在飞扬作者简介如下。根据相关公开资料查询,心在飞扬是一位优秀的小说作者,他的小说作...
卡什坦卡的故事赏析? 卡什坦卡的故事赏析?讲了一只小狗的故事, 我也是近来才读到这篇小说. 作家对动物的拟人描写真是惟妙...
林绍涛为简艾拿绿豆糕是哪一集 林绍涛为简艾拿绿豆糕是哪一集第三十二集。 贾宽认为是阎帅间接导致刘映霞住了院,第二天上班,他按捺不...
小爱同学是女生吗小安同学什么意... 小爱同学是女生吗小安同学什么意思 小爱同学,小安同学说你是女生。小安是男的。
内分泌失调导致脸上长斑,怎么调... 内分泌失调导致脸上长斑,怎么调理内分泌失调导致脸上长斑,怎么调理先调理内分泌,去看中医吧,另外用好的...
《魔幻仙境》刺客,骑士人物属性... 《魔幻仙境》刺客,骑士人物属性加点魔幻仙境骑士2功1体质
很喜欢她,该怎么办? 很喜欢她,该怎么办?太冷静了!! 太理智了!爱情是需要冲劲的~不要考虑着考虑那~否则缘...
言情小说作家 言情小说作家我比较喜欢匪我思存的,很虐,很悲,还有梅子黄时雨,笙离,叶萱,还有安宁的《温暖的玄》 小...
两个以名人的名字命名的风景名胜... 两个以名人的名字命名的风景名胜?快太白楼,李白。尚志公园,赵尚志。
幼儿教育的代表人物及其著作 幼儿教育的代表人物及其著作卡尔威特的《卡尔威特的教育》,小卡尔威特,他儿子成了天才后写的《小卡尔威特...
海贼王中为什么说路飞打凯多靠霸... 海贼王中为什么说路飞打凯多靠霸气升级?凯多是靠霸气升级吗?因为之前刚到时确实打不过人家因为路飞的实力...
运气不好拜财神有用吗运气不好拜... 运气不好拜财神有用吗运气不好拜财神有没有用1、运气不好拜财神有用。2、拜财神上香前先点蜡烛,照亮人神...