范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

在SpringDataJPA中,如何优雅的实现动态查询和连表查询

  前言
  在 ORM 框架的选择范围内,一直在讨论两个工具 Spring Data JPA 和 MyBatis,双方的争论各执一词,这里不去争论这些东西,不同的需求、不同的场景采用不同的解决方案是很正常的,孰优孰劣并没有万金油的答案。在这篇文章中我们来切切实实地解决 Spring Data JPA 中连表查询和动态查询实现复杂的问题。目前在网上搜这两个问题的解决方案大多是 JPQL 和 Specification 的方式,JPQL 在动态查询上不好实现,Specification 在实现的时候太麻烦,而且写出来的代码属实无法评价,也可能是我水平不够,见谅 。其他博客也是抄来抄去的,这里就不提这两种解决方案了,感兴趣的可以自行搜一下。好在现在可以搜到一些 QueryDSL 相关的博客了,虽然不多,但至少有人尝试新的解决方案,而不是简单的 CV,拿来就用。简介
  JPA 2.0 标准引入了一种新的类型安全的构建查询的方法,可以利用注释在预处理期间生成元模型类,通过生成的元模型类可以构建查询语句。具体可以看 Criteria Query API。
  QueryDSL 在编译的时候会自动帮我们生成一些 Criteria Query API 会用到的元模型类,然后我们可以直接用这些模型类构建查询,当然 QueryDSL 不仅仅只有这个作用。解决问题引入依赖
  在 Maven 的 pom.xml 文件中引入 QueryDSL,目前使用 Maven 管理项目依赖还是比较多,Gradle 的使用方式这里就不介绍了,Quiet 用的就是 Gradle,需要的话可以看下项目的具体配置,或者私信我也行。    com.querydsl   querydsl-apt   ${querydsl.version}   provided      com.querydsl   querydsl-jpa   ${querydsl.version}                       com.mysema.maven         apt-maven-plugin         1.1.3                                          process                                     target/generated-sources/java               com.querydsl.apt.jpa.JPAAnnotationProcessor                                          复制代码注入 JPAQueryFactory
  在 Spring Boot 项目中可以注入 Bean JPAQueryFactory 方便查询时使用,@Configuration public class JpaAutoConfig {    @PersistenceContext private final EntityManager entityManager;    public JpaAutoConfig(EntityManager entityManager) {     this.entityManager = entityManager;   }    @Bean   public JPAQueryFactory jpaQueryFactory() {     return new JPAQueryFactory(entityManager);   } } 复制代码生成元模型类
  编译项目,在开发的时候可以使用 maven 编译一下项目,或者直接运行项目也可以,这步主要是生成一些查询用到的元模型类。生成的模型类中我们用到类名最多的是 Q${EntityName}(前缀的 Q 好像是可以配置的,有需要修改的话可以自己研究下),EntityName 是我们的实体类的类名,比如@Entity public class User{} 复制代码
  那么生成的元模型类的类名就是 QUser。查询
  以下内容中的 queryFactory 即上文中注入的 Bean JPAQueryFactory,本文中只列举几种常用的查询方式,更多查询方式的构建可以看下官网文档(文末附有相关链接)。单表查询
  简单的单表查询直接使用 Repository 实现即可,动态条件查询在文章后面有构建动态查询条件的方式。QCustomer customer = QCustomer.customer; Customer bob = queryFactory.selectFrom(customer)   .where(customer.firstName.eq("Bob"))   .fetchOne(); 复制代码or 查询queryFactory.selectFrom(customer)     .where(customer.firstName.eq("Bob").or(customer.lastName.eq("Wilson"))); 复制代码查询部分字段QEmployee employee = QEmployee.employee; List result = queryFactory.select(employee.firstName, employee.lastName)                           .from(employee).fetch(); for (Tuple row : result) {      System.out.println("firstName " + row.get(employee.firstName));      System.out.println("lastName " + row.get(employee.lastName)); }} 复制代码查询指定字段并返回指定类型使用 setter 方法构建查询结果QUser user = QUser.user; List dtos = queryFactory.select(     Projections.bean(UserDTO.class, user.firstName, user.lastName)).fetch(); 复制代码使用字段填充的方式构建查询结果QUser user = QUser.user; List dtos = queryFactory.select(     Projections.fields(UserDTO.class, user.firstName, user.lastName)).fetch(); 复制代码使用类构造方法构建查询结果QUser user = QUser.user; List dtos = queryFactory.select(     Projections.constructor(UserDTO.class, user.firstName, user.lastName)).fetch(); 复制代码不同表之间 joinQQuietTeam quietTeam = QQuietTeam.quietTeam; QQuietTeamUser quietTeamUser = QQuietTeam.quietTeamUser; jpaQueryFactory             .selectFrom(quietTeam)             .leftJoin(quietTeamUser)             .on(quietTeam.id.eq(quietTeamUser.teamId))             .where(where)             .distinct()             .fetch(); 复制代码join 表取别名QCat cat = QCat.cat; QCat mate = new QCat("mate"); QCat kitten = new QCat("kitten"); queryFactory.selectFrom(cat)     .innerJoin(cat.mate, mate)     .leftJoin(cat.kittens, kitten)     .fetch(); 复制代码子查询QDepartment department = QDepartment.department; QDepartment d = new QDepartment("d"); queryFactory.selectFrom(department)     .where(department.size.eq(         JPAExpressions.select(d.size.max()).from(d)))      .fetch(); 复制代码QEmployee employee = QEmployee.employee; QEmployee e = new QEmployee("e"); queryFactory.selectFrom(employee)     .where(employee.weeklyhours.gt(         JPAExpressions.select(e.weeklyhours.avg())             .from(employee.department.employees, e)             .where(e.manager.eq(employee.manager))))     .fetch(); 复制代码分页查询
  QueryDSL 的分页查询是内存分页,在 5.0.0 版本已经过期,不建议使用,如果确定数据量不多,影响不大的话可以使用 fetchResults 方法,在文档中推荐了另一个开源项目:Blaze-Persistence引入依赖:     com.blazebit     blaze-persistence-integration-querydsl-expressions     ${blaze-persistence.version}     compile       com.blazebit     blaze-persistence-integration-hibernate-5.6     ${blaze-persistence.version}     runtime  复制代码注入 Bean CriteriaBuilderFactory/**  * @author lin-mt  */ @Configuration(proxyBeanMethods = false) public class JpaConfig {    @PersistenceUnit private EntityManagerFactory entityManagerFactory;    @Bean   @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)   public CriteriaBuilderFactory createCriteriaBuilderFactory() {     CriteriaBuilderConfiguration config = Criteria.getDefault();     // do some configuration     return config.createCriteriaBuilderFactory(entityManagerFactory);   } } 复制代码查询数据:  @Override   public PagedList pageUser(       @NotNull Long deptId, QuietUser params, @NotNull Pageable page) {     BooleanBuilder builder = SelectBuilder.booleanBuilder(params).getPredicate();     builder.and(quietDeptUser.deptId.eq(deptId));     return new BlazeJPAQuery(entityManager, criteriaBuilderFactory)         .select(quietUser)         .from(quietUser)         .leftJoin(quietDeptUser)         .on(quietUser.id.eq(quietDeptUser.userId))         .where(builder)         .orderBy(quietUser.id.desc())         .fetchPage((int) page.getOffset(), page.getPageSize());   } 复制代码结合 JPA 查询
  Spring Data JPA 提供了很多的扩展点,QueryDSL 和 Blaze-Persistence 构建的查询条件也是支持这些扩展点。在 JPA 中我们常用的是 org.springframework.data.jpa.repository.JpaRepository 相关的类作为我们 Repository 的父类,Spring Data JPA 对 QueryDSL 也是专门提供了一个接口(这应该能算 QueryDSL 得到了官方认可了吧):org.springframework.data.querydsl.QuerydslPredicateExecutor,那么我们在使用的时候就可以定义一个项目中所有 Repository 共用的父接口:/**  * @author lin-mt  */ @NoRepositoryBean public interface QuietRepository extends JpaRepository, QuerydslPredicateExecutor {} 复制代码
  在分页查询的时候就可以使用方法:org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Pageable)
  Predicate 参数是构建的查询条件实体信息的父类,下面我们会有动态查询条件构建的例子。动态查询
  在项目中我们常常有动态查询的需求,比如前端传了用户名,我们就需要根据用户名进行模糊查询,没有传用户名,就不添加用户名的查询条件,这种需求在 Spring Data JPA 中实现是比较麻烦的,这也是很多项目不选 Spring Data JPA 作为项目 ORM 框架的原因之一,这里就介绍一种比较优雅且可读性较好的方式解决这个问题。
  QueryDSL 提供了一种构建查询条件的实体类 com.querydsl.core.BooleanBuilder,这个类实现了接口 com.querydsl.core.types.Predicate,也就是文章上面提到的 Spring Data JPA 提供的 QueryDSL 扩展接口中方法的形参。  QEmployee employee = QEmployee.employee;   BooleanBuilder builder = new BooleanBuilder();   for (String name : names) {       builder.or(employee.name.equalsIgnoreCase(name));   }   if (id != null) {       builder.and(employee.id.equals(id))   }   queryFactory.selectFrom(employee).where(builder).fetch(); 复制代码
  构建动态查询的方式不仅仅只有 BooleanBuilder,所有 Predicate 的子类都可以:
  更优雅地构建动态查询
  在一些后台管理的项目中,统计需求往往会有很多的动态查询的字段,这时候可能就会出现很多的 if-else 的代码,这种代码可读性就不是很好了,观察一下 Q${EntityName} 的字段,相同类型的字段,它们返回的类型其实都是有共同的父类的,这就很好体现了 Java 的三大特性之一的多态。利用这点我们新建一个构建动态查询的工具类,将动态构建的 if-else 隐藏起来,工具里的方法可以根据自己项目的需要自行增删:/**  * 查询条件构造器.  *  * @author lin-mt  */ public abstract class SelectBuilder {    @NotNull   public static SelectBooleanBuilder booleanBuilder() {     return new SelectBooleanBuilder();   }    @NotNull   public static SelectBooleanBuilder booleanBuilder(BaseEntity entity) {     BooleanBuilder builder = null;     if (entity != null) {       builder = entity.booleanBuilder();     }     return new SelectBooleanBuilder(builder);   }    /**    * 获取查询条件    *    * @return 查询条件    */   @NotNull   public abstract T getPredicate(); } 复制代码/**  * 构建 BooleanBuilder.  *  * @author lin-mt  */ public class SelectBooleanBuilder extends SelectBuilder {    private final BooleanBuilder builder;    public SelectBooleanBuilder() {     this.builder = new BooleanBuilder();   }    public SelectBooleanBuilder(BooleanBuilder builder) {     this.builder = builder == null ? new BooleanBuilder() : builder;   }    @Override   public BooleanBuilder getPredicate() {     return builder;   }    public SelectBooleanBuilder and(@Nullable Predicate right) {     builder.and(right);     return this;   }    public SelectBooleanBuilder andAnyOf(Predicate... args) {     builder.andAnyOf(args);     return this;   }    public SelectBooleanBuilder andNot(Predicate right) {     return and(right.not());   }    public SelectBooleanBuilder or(@Nullable Predicate right) {     builder.or(right);     return this;   }    public SelectBooleanBuilder orAllOf(Predicate... args) {     builder.orAllOf(args);     return this;   }    public SelectBooleanBuilder orNot(Predicate right) {     return or(right.not());   }    public SelectBooleanBuilder notNullEq(Boolean param, BooleanPath path) {     if (param != null) {       builder.and(path.eq(param));     }     return this;   }    public > SelectBooleanBuilder notNullEq(       T param, NumberPath path) {     if (param != null) {       builder.and(path.eq(param));     }     return this;   }    public SelectBooleanBuilder isIdEq(Long param, NumberPath path) {     if (param != null && param > 0L) {       builder.and(path.eq(param));     }     return this;   }    public > SelectBooleanBuilder leZeroIsNull(       T param, NumberPath path) {     if (param != null && param.longValue() <= 0) {       builder.and(path.isNull());     }     return this;   }    public SelectBooleanBuilder notBlankEq(String param, StringPath path) {     if (StringUtils.isNoneBlank(param)) {       builder.and(path.eq(param));     }     return this;   }    public SelectBooleanBuilder with(@NotNull Consumer consumer) {     if (consumer != null) {       consumer.accept(this);     }     return this;   }    public > SelectBooleanBuilder notNullEq(T param, EnumPath path) {     if (param != null) {       builder.and(path.eq(param));     }     return this;   }    public SelectBooleanBuilder notBlankContains(String param, StringPath path) {     if (StringUtils.isNoneBlank(param)) {       builder.and(path.contains(param));     }     return this;   }    public SelectBooleanBuilder notNullEq(Dict dict, QDict qDict) {     if (dict != null && StringUtils.isNoneBlank(dict.getKey())) {       builder.and(qDict.eq(dict));     }     return this;   }    public SelectBooleanBuilder notNullBefore(LocalDateTime param, DateTimePath path) {     if (param != null) {       builder.and(path.before(param));     }     return this;   }    public SelectBooleanBuilder notNullAfter(LocalDateTime param, DateTimePath path) {     if (param != null) {       builder.and(path.after(param));     }     return this;   }    public SelectBooleanBuilder notEmptyIn(Collection<? extends Long> param, NumberPath path) {     if (CollectionUtils.isNotEmpty(param)) {       builder.and(path.in(param));     }     return this;   }    public SelectBooleanBuilder findInSet(Long param, SetPath> path) {     if (param != null) {       builder.and(Expressions.booleanTemplate("FIND_IN_SET({0}, {1}) > 0", param, path));     }     return this;   } } 复制代码使用例子@Override public List listByProjectIdAndName(Long projectId, Set ids, String name, Long limit) {   if (Objects.isNull(projectId)) {     return Lists.newArrayList();   }   BooleanBuilder where =       SelectBooleanBuilder.booleanBuilder()           .and(docApiGroup.projectId.eq(projectId))           .notEmptyIn(ids, docApiGroup.id)           .notBlankContains(name, docApiGroup.name)           .getPredicate();   JPAQuery query = jpaQueryFactory.selectFrom(docApiGroup).where(where);   if (limit != null && limit > 0) {     query.limit(limit);   }   return query.fetch(); } 复制代码结语
  这篇文章主要是介绍一些比较常用的内容,QueryDSL 是基于 SQL 标准实现了 SQL 语句的构建,对于不同类型的数据库(MySQL、Oracle等)具有的特性,就需要自己去构建查询方式了,比如上面的 findInSet 就是 MySQL 特有的函数,不在 SQL 标准中,所以要真正用好的话学习成本确实有点高,我也只是了解一点而已。Blaze-Persistence 也是一个很不错的开源项目,目前我也只是把它当成 QueryDSL 的补充,但其实它也提供了很多查询条件的构建方式,感兴趣的可以自行深入研究哈 最后,再附上相关链接
  QueryDSL 官网:querydsl.com/
  QueryDSL Github:github.com/querydsl/qu…
  Blaze-Persistence 官网:persistence.blazebit.com/index.html
  Blaze-Persistence Github:github.com/Blazebit/bl…
  QueryDSL 文档:querydsl.com/static/quer…
  Blaze-Persistence 文档:persistence.blazebit.com/documentati…
  文档的链接带有版本号,目前是最新的(文章发布时间:2023-03-10),本文就不实时更新这个链接了,后续需要最新的文档可以到官网查询哈。

孩子拿到什么东西都往嘴里塞,是不是很脏?稍微有点脏的环境反而能让孩子健康成长。孩子总是抓到什么都往嘴里塞,掉在地上的玩具也要伸出舌头舔一舔。大人难免嫌脏,不让孩子靠近,或者把玩具擦拭干净。但孩子的这种行为也许是构建免疫力宝宝爱抓耳朵怎么回事?这几个原因可大可小,家长不可忽视在陪伴孩子时,不少宝妈会发现宝宝有揪耳朵的行为。那么,他们为什么会揪耳朵呢?其实他们揪耳朵往往是有原因的,这些原因大可小,稍不注意可能会危害到娃的健康。今天,我们就来盘点一下宝宝揪NBA宣布新增东西部决赛MVP,将以黑白双雄命名分区决赛MVP奖杯正值联盟成立75周年,NBA将推出一个新的奖项东西部决赛MVP。东部决赛MVP将被命名为拉里伯德,西部决赛MVP被命名为魔术师约翰逊杯。今年分区决赛最早5月16日NBA惨案!勇士落后55分,打破52年纪录,库里目光呆滞,汤神发愣北京时间5月12日,NBA季后赛勇士对阵灰熊,此前4场比赛勇士31领先,本场比赛是赛点局,莫兰特继续缺席。但灰熊开场早早占据主动,第一节就砍下38分,半场打完领先勇士多达27分。下宝宝补钙还是补维生素D,妈妈弄错了导致孩子便秘长不高文莹妈丹丹是我的一个球友,她生下了龙凤双胞胎,孩子刚出生没多久的时候,她还按照医生的建议购买了维生素AD给孩子补,补到了孩子半岁的时候,她就觉得孩子开始吃辅食了,总是吃维生素AD也麦迪娜才是真的富家千金,看到她妈妈的打扮贫穷限制想象力就算是风光无限备受追捧的女明星们在当了母亲后,也会不自觉陷入晒娃秀母亲节等状态中,往往在这时,大家才觉得她们离大众也并不遥远。而相比于其他女明星或秀与孩子的合照或在字里行间炫耀自家早上金苹果,晚上毒苹果,吃苹果能够预防癌症,这些说法可信吗?苹果是我们生活中最常见的一种水果了,提到苹果每个人想到的都是健康。甚至还有一种说法一天一个苹果,医生远离我。这些赞美的词句都代表了人们对于苹果的追捧。由于苹果对身体具有多种功效,甚麦迪娜穿卫衣配高领打底挺保暖,但下身却不配裤子,看着反差挺大今天穿什么明星教你怎么穿潮流风格穿搭夏天可以说是女性朋友们比较喜欢的季节,女性朋友们在服装的选择上就可以多一些偏清爽的类型。说到清爽,大家很容易就能够想到露腿吧,最常见的露腿装应该可爱青春魅力,Blackpink智秀变身济州女孩,夏日麻花辫少女YG旗下韩国女团Blackpink成员金智秀,变身清新活泼的济州女孩,以可爱的双马尾麻花辫造型,展现青春魅力,吸引了网友们的关注。据韩国媒体报道,5月10日,Blackpink成员最爱用的抗老精华推荐紧致抗皱提亮肤色改善暗沉细化毛孔最爱用的抗老精华推荐紧致抗皱提亮肤色改善暗沉细化毛孔!彼得罗夫retinolfusionalternative精华这款精华是彼得罗夫的一款抗老精华,主体成分为胜肽,官方介绍是类A醇帆布鞋牛仔裤帆布鞋西装裤才是最时髦穿法,小个子穿减龄显高运动鞋也算是我们的老朋友了,几乎一年四季都能看到他的身影,但若是你今年夏天还在穿运动鞋的话,那你就out了。今年更流行用帆布鞋与裤子搭配,不仅能打造出减龄有活力的效果,运用一些穿搭
2022全球IPv6支持度白皮书发布2023年1月5日,由全球IPv6论坛(IPv6Forum)与下一代互联网国家工程中心(CFIEC)共同主办的全球IPv6发展与标准演进研讨会上,全球IPv6论坛正式发布2022全为什么许多股民容易买在高位?在一些人看来,网格交易是一种积少成多的策略,每次赚一点点,日积月累就是一笔大财富。表面上看确实如此,其实不然,我在设置网格的时候,还强调了一点,那就是底仓。网格赚小钱,底仓赚大钱,Java面试问答宝典(ArrayList篇)ArrayList如何删除数据?我们参加Java培训学习都是希望能够毕业后找到满意的工作,然而培训机构只是推荐就业,我们仍然需要进行面试,提高面试技术问答的正确率有助于增加面试官的满意度。本系列全面汇总了企业Ja能否抄底猪周期?来看两大千亿龙头的最新数据和动作见习记者骆吴两大生猪养殖龙头正在用最新经营数据和各自的行动告诉投资者,应该如何把握目前的行业投资机会。2023年1月5日晚间,牧原股份(002714)和温氏股份(300498)发布詹姆斯中场休息时我看的最多的数据是失误数而不是得分湖人前锋勒布朗詹姆斯近日接受了ESPN记者DaveMcMenamin的专访。詹姆斯已经连续1125场常规赛得分上双,上次他得分不足10分还要追溯到16年前的2007年1月5日,骑士数据中心忙瘦身让电老虎变身绿巨人贵州已成为世界聚集超大型数据中心最多的地区之一,图为工作人员在中国移动贵阳数据中心机房内巡检。(新华社图)没有高炉和烟囱,方方正正的大盒子里装着成千上万的机架和设备,其本身不直接产最高减价4。8万元,国产特斯拉全线降价!近90亿大资金跑步入场,光伏板块掀涨停潮,4股获逾5亿资金净流入(附股)数据是个宝数据宝炒股少烦恼早盘光伏板块掀起涨停潮,原因或是这些。国产特斯拉全线降价1月6日,据特斯拉中国官网,特斯拉国产车型大幅降价,对国产Model3后驱版高性能版,及Model纯电续航超1000公里,新款极氪001是大杀器还是智商税?近日,2023款极氪001正式上市,共推出4款不同配置车型,指导价30万38。6万元。作为年度改款车型,其变化主要集中在外观内饰及续航方面,其中最值得关注的当属其续航方面的变化。2CES2023更大!更亮!索尼三星今年的77吋新款QDOLED电视值得期待CES2023已经进入开幕倒计时状态,而各品牌新款的旗舰电视也将陆续登场发布,今年关注度最高的电视应该还是基于QDOLED面板的新款大尺寸型号,基于新款的77大尺寸QDOLED面板不止锐龙7000X3D处理器AMDCES2023移动端U显全都安排上了!大泽科技SHOWCES2023正在如火如荼的举行,与往年一样,又是一波的神仙打架,颇具看点。北京时间1月5日上午,AMD董事会主席首席执行官LisaSu博士将在CES上发表主题演讲微星展示白色款魔龙显卡,包括RTX4070Ti4080型号IT之家1月6日消息,在ROG和七彩虹等厂商发布白色款旗舰显卡后,微星也在CES上展示其白色款显卡,不过是中端的魔龙型号,包括RTX4070Ti4080型号。图源WccfTechI