JPA 之 QueryDSL-JPA 使用指南
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.querydsl querydsl-apt com.querydsl querydsl-jpa
添加maven插件(pom.xml)
添加这个插件是为了让程序自动生成query type(查询实体,命名方式为:“Q”+对应实体名)。
上文引入的依赖中querydsl-apt即是为此插件服务的。
注:在使用过程中,如果遇到query type无法自动生成的情况,用maven更新一下项目即可解决(右键项目->Maven->Update Project)。
com.mysema.maven apt-maven-plugin 1.1.3 generate-sources process target/generated-sources/java com.querydsl.apt.jpa.JPAAnnotationProcessor
补充:
QueryDSL默认使用HQL发出查询语句。但也支持原生SQL查询。
若要使用原生SQL查询,你需要使用下面这个maven插件生成相应的query type。
com.querydsl querydsl-maven-plugin ${querydsl.version} export org.apache.derby.jdbc.EmbeddedDriver jdbc:derby:target/demoDB;create=true com.mycompany.mydomain ${project.basedir}/target/generated-sources/java org.apache.derby derby ${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 { @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); }
注入
@Autowired private 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 currentTime() // 返回当前时刻(时:分:秒)的 TimeExpression DateTimeExpression currentTimestamp() // 返回当前时间(年-月-日 时:分:秒)的 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 numberTemplate(Class args)
MathExpressions 数学表达式工具类
NumberExpression round(Expression num) // 四舍五入取整 NumberExpression round(Expression num, int s) // 四舍五入保留 s 位小数 NumberExpression asin(Expression num) // 返回num的反正弦值。-1 //商品基本信息 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 List QUserAddress ua = QUserAddress.userAddress; QUser u = QUser.user; List QTCity qtCity = QTCity.tCity; QTHotel qtHotel = QTHotel.tHotel; JPAQuery System.out.println("qtCity:" + row.get(qtCity)); System.out.println("qtHotel:" + row.get(qtHotel)); System.out.println("--------------------"); } } private Integer id; private String username; private String password; private String phone; private List} ... 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 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 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)。
@Test public 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); }