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
的优势则体现在其功能的强大,支持更复杂的查询业务。甚至可以用来进行更新和删除操作。
依赖
<dependencies> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> </dependency> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> </dependency></dependencies>
添加maven插件(pom.xml)
添加这个插件是为了让程序自动生成query type(查询实体,命名方式为:“Q”+对应实体名)。
上文引入的依赖中querydsl-apt
即是为此插件服务的。
注:在使用过程中,如果遇到query type无法自动生成的情况,用maven更新一下项目即可解决(右键项目->Maven->Update Project)。
<project><build> <plugins> <plugin> <groupId>com.mysema.maven</groupId> <artifactId>apt-maven-plugin</artifactId> <version>1.1.3</version> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>process</goal> </goals> <configuration> <outputDirectory>target/generated-sources/java</outputDirectory> <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor> </configuration> </execution> </executions> </plugin> </plugins></build></project>
补充:
QueryDSL默认使用HQL发出查询语句。但也支持原生SQL查询。
若要使用原生SQL查询,你需要使用下面这个maven插件生成相应的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>
生成查询实体
idea 工具 为maven project 自动添加了对应的功能。添加好依赖和 plugin 插件后,就可以生成查询实体了。
打开右侧的 Maven Projects,如下图所示:
双击 clean 清除已编译的 target
双击 compile 命令执行,执行完成后会在 pom.xml 配置文件内配置生成目录内生成对应实体的 QueryDSL 查询实体。
生成的查询实体如下图所示:
JPAQueryFactory 风格
QueryDSL 在支持JPA的同时,也提供了对 Hibernate 的支持。可以通过 HibernateQueryFactory
来使用。
装配 与 注入
SpringBoot注解方式装配
/** * 方式一。使用Spring的@Configuration注解注册实例进行容器托管 */@Configurationpublic 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); }
注入
@Autowired private JPAQueryFactory queryFactory;
更新、删除
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));}
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);}
查询
表达式工具类
Expressions 表达式工具类
// when-then 条件表达式函数。when传参必须为名为eqTrue或eqFalse的PredicateT cases().when(Predicate).then(T a).otherwise(T b) DateExpression<Date> currentDate()// 返回当前日历(年-月-日)的 DateExpressionTimeExpression<Time> currentTime()// 返回当前时刻(时:分:秒)的 TimeExpressionDateTimeExpression<Date> currentTimestamp()// 返回当前时间(年-月-日 时:分:秒)的 DateTimeExpression // 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<T> numberTemplate(Class<" />, qm.name, qm.address)) .fetch();//使用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();
QueryDslPredicateExecutor 风格
通常使用Repository来继承QueryDslPredicateExecutor
接口。通过注入Repository来使用。
Repository 接口
Spring Data JPA中提供了QueryDslPredicateExecutor接口,用于支持QueryDSL的查询操作。
public interface tityRepository extends JpaRepository<City, Integer>, QuerydslPredicateExecutor<city> {}
QueryDslPredicateExecutor
接口提供了findOne()
,findAll()
,count()
,exists()
四个方法来支持查询。并可以使用更优雅的BooleanBuilder
来进行条件分支管理。
count()
会返回满足查询条件的数据行的数量exists()
会根据所要查询的数据是否存在返回一个boolean值
findOne()、findAll()
findOne
从数据库中查出一条数据。没有重载方法。
Optional<T> findOne(Predicate var1);
和JPAQuery
的fetchOne()
一样,当根据查询条件从数据库中查询到多条匹配数据时,会抛NonUniqueResultException
。使用的时候需要慎重。
findAll()
findAll是从数据库中查出匹配的所有数据。提供了以下几个重载方法。
Iterable<T> findAll(Predicate var1);Iterable<T> findAll(Predicate var1, Sort var2);Iterable<T> findAll(Predicate var1, OrderSpecifier<?>... var2);Iterable<T> findAll(OrderSpecifier<?>... var1);Page<T> findAll(Predicate var1, Pageable var2);
使用示例:
QMemberDomain qm = QMemberDomain.memberDomain;// QueryDSL 提供的排序实现OrderSpecifier<Integer> order = new OrderSpecifier<>(Order.DESC, qm.age);Iterable<MemberDomain> 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<MemberDomain> 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<TCity> tCityPage = tCityRepository.findAll(predicate, pageRequest);
Querydsl SQL 查询
Querydsl SQL 模块提供与 JDBC API 的集成。可以使用更多的 JDBC SQL方法。比如可以实现 from 的查询主体为子查询出来的临时表、union、union All 等Querydsl-JPA限制的相关操作。还可以根据 JDBC API 获取数据库的类型使用不同的数据库语法模板。
依赖及配置
依赖:
<dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-sql</artifactId> <version>${querydsl.version}</version> </dependency> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.5</version> </dependency>
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<Employee> { private static final long serialVersionUID = 1394463749655231079L; public static final QEmployee employee = new QEmployee("EMPLOYEE"); public final NumberPath<Integer> id = createNumber("id", Integer.class); public final StringPath firstname = createString("firstname"); public final DatePath<java.util.Date> datefield = createDate("datefield", java.util.Date.class); public final PrimaryKey<Employee> 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<UserAddress> { private static final long serialVersionUID = -1295712525L; public static final QUserAddressS userAddress = new QUserAddressS("tb_user_address"); public final NumberPath<Integer> id = createNumber("id", Integer.class); public final StringPath address = createString("address"); public final DateTimePath<java.util.Date> createTime = createDateTime("create_time", java.util.Date.class); 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); }}
SQLQueryFactory 方式
装配及基本使用
装配
@Configuration@Slf4jpublic 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)); }}
注入
@Autowired private SQLQueryFactory sqlQueryFactory;
SQLQueryFactory 基本使用
/** * 子查询作为临时表传入from()中 */@Test public void selectBySqlQueryFactory(){ // 使用 extends RelationalPathBase 的QEntity,自动映射封装 QUserAddressSql uaSql = QUserAddressSql.userAddress; // 子查询 SQLQuery<Tuple> q = SQLExpressions .select( // 查询字段须是数据库表中的字段名(不是实体属性名),且类型一致 uaSql.addressee , uaSql.userId ) .from(uaSql); List<Tuple> 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 */@Test public void selectBySqlQueryFactory(){ // 使用 extends EntityPathBase 的改造版QEntity,结果集如需封装到实体类,必须手动指定实体类来接收 QUserAddressSql uaSql = QUserAddressSql.userAddress; QUserSql uSql = QUserSql.user; SQLQuery<Tuple> a = SQLExpressions .select(uaSql.userId.as("user_id") , uaSql.phone) .from(uaSql) .where(uaSql.userId.eq(30)); SQLQuery<Tuple> b = SQLExpressions .select(uSql.id.as("user_id") , uSql.phone) .from(uSql) .where(uSql.id.eq(29).or(uSql.id.eq(30))); Union<Tuple> union = sqlQueryFactory.query().union(a, b); long count = sqlQueryFactory .from(union, Expressions.stringPath("q")).fetchCount(); List<UserAddressDTO> 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 <T> Union<T> union(SubQueryExpression<T>... sq)static <T> Union<T> union(List<SubQueryExpression<T>> sq)static <T> Union<T> unionAll(SubQueryExpression<T>... sq)static <T> Union<T> unionAll(List<SubQueryExpression<T>> sq)// 调用函数查询序列static SimpleExpression<Long> nextval(String sequence)static <T extends Number> SimpleExpression<T> nextval(Class<T> type, String sequence)// 使用示例:SQL写法:select seq_process_no.nextval from dual;Long nextvalReturn = sqlQueryFactory.select(SQLExpressions.nextval("序列名")).fetchOne;// 将多列记录聚合为一列记录。delimiter为分隔符。Oracle数据库专属,其他数据库报错static WithinGroup<Object> 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<String> expr, String separator)static StringExpression groupConcat(Expression<String> expr) static <T> RelationalFunctionCall<T> relationalFunctionCall(Class<? extends T> type, String function, Object... args)static <D extends Comparable> DateExpression<D> date(DateTimeExpression<D> dateTime)static <D extends Comparable> DateExpression<D> date(Class<D> type, DateTimeExpression<?> dateTime) static <D extends Comparable> DateTimeExpression<D> dateadd(DatePart unit, DateTimeExpression<D> date, int amount)static <D extends Comparable> DateExpression<D> dateadd(DatePart unit, DateExpression<D> date, int amount)// 获取两个日期的时间间隔(end-start)static <D extends Comparable> NumberExpression<Integer> datediff(DatePart unit, DateTimeExpression<D> start, DateTimeExpression<D> 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<Void>(em, new OracleTemplates()); // 子查询 SQLQuery<Tuple> q = SQLExpressions .select( // 查询字段须是数据库表中的字段名(不是实体属性名),且类型一致。如直接不使用QEntity的属性,则需手动指定 Expressions.stringPath("addressee").as("addressee") , Expressions.numberPath(Integer.class, "user_id").as("user_id") ) .from(ua); List<Tuple> 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); }