欢迎来到 通辽市某某化工涂料售后客服中心
全国咨询热线:020-123456789
联系我们

地址:联系地址联系地址联系地址

电话:020-123456789

传真:020-123456789

邮箱:admin@aa.com

新闻中心
Spring Data JPA:JPA项目中核心场景与进阶用法介绍
  来源:通辽市某某化工涂料售后客服中心  更新时间:2024-04-27 16:25:24

Spring Data JPA:JPA项目中核心场景与进阶用法介绍

repository全貌梳理

先看下Repository相关的项目心场类图 :

Spring Data JPA	:JPA项目中核心场景与进阶用法介绍

整体类图虽然咋看上去很庞杂,但其实主线脉络还是中核比较清晰的。

  • 先看下蓝色的景进阶用部分其实就是Repository的一整个接口定义链条,而橙色的法介则是我们自己自定义的一些Repository接口类 ,继承父层接口的项目心场所有已有能力。
  • 左侧的中核类图与接口 ,其实都是景进阶用JPA提供的一些用于实现或者定制查询操作的一些辅助实现类,后面章节中会看到他们的法介身影 。

对主体repository层级提供的项目心场主要方法进行简单的梳理 ,如下 :

Spring Data JPA:JPA项目中核心场景与进阶用法介绍

下面对各个repository接口进行简单的独立介绍 。

JpaRepository与它的景进阶用父类们

  • Repository 位于 Spring Data Common 的lib里面 ,是法介Spring Data 里面做数据库操作的最底层的抽象接口 、最顶级的项目心场父类 ,源码里面其实什么方法都没有 ,中核仅仅起到一个标识作用 。景进阶用
  • CrudRepository 作为直接继承 Repository 的次顶层接口类 ,看名字也可以大致猜测出其主要作用就是封装提供基础CRUD操作。
  • PagingAndSortingRepository 继承自 CrudRepository ,自然也就具备了 CrudRepository 提供的全部接口能力 。此外,从其自身新提供的接口来看 ,增加了排序和分页查询列表的能力,非常符合其类名的含义 。

JpaRepository 与其前面的几个父类相比是个特殊的存在 ,其中补充添加了一组JPA规范的接口方法。前面的几个接口类都是Spring Data为了兼容NoSQL而进行的一些抽象封装(因为SpringData项目是一个庞大的家族 ,支持各种SQL与NoSQL的数据库,SpringData JPA是SpringData家族中面向SQL数据库的一个子分支项目),从 JpaRepository 开始是对关系型数据库进行抽象封装 。

Spring Data JPA
:JPA项目中核心场景与进阶用法介绍

从类图可以看得出来它继承了 PagingAndSortingRepository 类,也就继承了其所有方法 ,并且实现类也是 SimpleJpaRepository  。从类图上还可以看出 JpaRepository 继承和拥有了 QueryByExampleExecutor 的相关方法 。

Spring Data JPA�:JPA项目中核心场景与进阶用法介绍

通过源码和 CrudRepository 相比较,它支持Query By Example,批量删除 ,提高删除效率 ,手动刷新数据库的更改方法 ,并将默认实现的查询结果变成了List。

额外补充一句 :

实际的项目编码中 ,大部分的场景中,我们自定义Repository都是继承 JpaRepository 来实现的。

自定义Repository

先看个自定义Repository的例子,如下:

Spring Data JPA:JPA项目中核心场景与进阶用法介绍

看下对应类图结构  ,自定义Repository继承了JpaRepository,具备了其父系所有的操作接口 ,此外 ,额外扩展了业务层面自定义的一些接口方法:

Spring Data JPA:JPA项目中核心场景与进阶用法介绍

自定义Repository 的时候,继承JpaRepository需要传入两个泛型:

  • 此Repository需要操作的具体Entity对象(Entity与具体DB中表映射 ,所以指定Entity也等同于指定了此Repository所对应的目标操作Table),
  • 此Entity实体的主键数据类型(也就是第一个参数指定的Entity类中以@Id注解标识的字段的类型)
Spring Data JPA:JPA项目中核心场景与进阶用法介绍

分页 、排序 ,一招搞定

分页,排序使用 Pageable 对象进行传递,其中包含 Page 和 Sort 参数对象 。

查询的时候,直接传递 Pageable 参数即可(注意下 ,如果是用原生SQL查询的方式 ,此法行不通 ,后文有详细说明) 。

// 定义repository接口的时候,直接传入Pageable参数即可nList<UserEntity> findAllByDepartment(DepartmentEntity department, Pageable pageable);

还有一种特殊的分页场景。比如,DB表中有100w条记录 ,然后现在需要将这些数据全量的加载到ES中 。如果逐条查询然后插入ES,显然效率太慢;如果一次性全部查询出来然后直接往ES写 ,服务端内存可能会爆掉。

这种场景 ,其实可以基于 Slice 结果对象进行实现 。Slice的作用是,只知道是否有下一个 Slice 可用 ,不会执行count  ,所以当查询较大的结果集时,只知道数据是足够的就可以了 ,而且相关的业务场景也不用关心一共有多少页 。

private <T extends EsDocument, F> void fullLoadToEs(IESLoadService<T, F> esLoadService) { n try { n final int batchHandleSize = 10000;n Pageable pageable = PageRequest.of(0, batchHandleSize);n do { n // 批量加载数据,返回Slice类型结果n Slice<F> entitySilce = esLoadService.slicePageQueryData(pageable);nn // 具体业务处理逻辑n List<T> esDocumentData = esLoadService.buildEsDocumentData(entitySilce);n esUtil.batchSaveOrUpdateAsync(esDocumentData);nn // 获取本次实际上加载到的具体数据量n int pageLoadedCount = entitySilce.getNumberOfElements();n if (!entitySilce.hasNext()) { n break;n }nn // 自动重置page分页参数,继续拉取下一批数据n pageable = entitySilce.nextPageable();n } while (true);n } catch (Exception e) { n log.error("error occurred when load data into es", e);n }n}

复杂搜索 ,其实不复杂

按照条件进行搜索查询 ,是项目中遇到的非常典型且常用的场景 。但是条件搜索也分几种场景 ,下面分开说下  。

简单固定场景

所谓简单固定,即查询条件就是固定的1个字段或者若干个字段,且查询字段数量不会变 ,比如根据部门查询具体人员列表这种。

这种情况,我们可以简单地直接在repository中,根据命名规范定义一个接口即可 。

@Repositorynpublic interface UserRepository extends JpaRepository<UserEntity, Long> { n // 根据一个固定字段查询n List<UserEntity> findAllByDepartment(DepartmentEntity department);n // 根据多个固定字段组合查询n UserEntity findFirstByWorkIdAndUserNameAndDepartment(String workId, String userName, DepartmentEntity department);n}

简单不固定场景

考虑一种场景,界面上需要做一个用户搜索的能力 ,要求支持根据用户名、工号、部门 、性别、年龄 、职务等等若干个字段中的1个或者多个的组合来查询符合条件的用户信息。

显然,上述通过直接在repository中按照命名规则定义接口的方式行不通了。这个时候 , Example 对象便派上用场了 。

其实在前面整体介绍Repository的UML图中,就已经有了 Example 的身影了 ,虽然这个名字起得很敷衍,但其功能确是挺实在的 。

Spring Data JPA
	�:JPA项目中核心场景与进阶用法介绍

看下具体用法:

public Page<UserEntity> queryUsers(Request request, UserEntity queryParams) { n // 查询条件构造出对应Entity对象 ,转为Example查询条件n Example<UserEntity> example = Example.of(queryParams);n // 构造分页参数n Pageable pageable = PageHelper.buildPageable(request);n n // 按照条件查询,并分页返回结果n return userRepository.findAll(example, pageable);n}

复杂场景

如果是一些自定义的复杂查询场景,可以通过定制SQL语句的方式来实现。

@Repositorynpublic interface UserRepository extends JpaRepository<UserEntity, Long> { n @Query(n value = "select t.*,(select group_concat(a.assigner_name) from workflow_task a where a.state='R' and a.proc_inst_id=t.proc_inst_id) deal_person,"n + " (select a.task_name from workflow_task a where a.state='R' and a.proc_inst_id=t.proc_inst_id limit 1) cur_step "n + " from workflow_info t where t.state='R' and t.type in (?1) "n + "and exists(select 1 from workflow_task b where b.assigner=?2 and b.state='R' and b.proc_inst_id=t.proc_inst_id) order by t.create_time desc",n countQuery = "select count(1) from workflow_info t where t.state='R' and t.type in (?1) "n + "and exists(select 1 from workflow_task b where b.assigner=?2 and b.state='R' and b.proc_inst_id=t.proc_inst_id) ",n nativeQuery = true)n Page<FlowResource> queryResource(List<String> type, String workId, Pageable pageable);n}

此外,还可以基于 JpaSpecificationExecutor 提供的能力接口来实现。

自定义接口需要增加 JpaSpecificationExecutor 的继承,然后利用 Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable); 接口来实现复杂查询能力。

// 增加对JpaSpecificationExecutor的继承n@Repositorynpublic interface UserRepository extends JpaRepository<UserEntity, Long>, JpaSpecificationExecutor<UserEntity> { nn}

public List<UserEntity> queryUsers(QueryParams queryParams) { n // 构造Specification查询条件n Specification<UserEntity> specification =n (root, query, cb) -> { n List<Predicate> predicates = new ArrayList<>();n // 范围查询条件构造n predicates.add(cb.greaterThanOrEqualTo(root.get("age"), queryParams.getMinAge()));n predicates.add(cb.lessThanOrEqualTo(root.get("age"), queryParams.getMaxAge()));n // 精确匹配查询条件构造n predicates.add(cb.equal(root.get("department"), queryParams.getDepartment()));n // 关键字模糊匹配条件构造n if (Objects.nonNull(queryParams.getNameKeyword())) { n predicates.add(cb.like(root.get("userName"), "%" + queryParams.getNameKeyword() + "%"));n }n return query.where(predicates.toArray(new Predicate[0])).getRestriction();n };n // 执行复杂查询条件n return userRepository.findAll(specification);n}

自定义Listener,玩出花样

实际项目中,经常会有一种场景 ,就是需要监听某个数据的变更然后做一些额外的处理逻辑  。一种逻辑,是写操作的时候顺便调用下相关业务的处理API,这样会造成业务间耦合加深;优化点的策略是搞个MQ队列 ,然后在这个写DB操作的同时发个消息到MQ里面,然后一堆的consumer会监听MQ并去做对应的处理逻辑 ,这样引入个消息队列代价也有点高。

这个时候 ,我们可以借助JPA的自定义 EntityListener 功能来完美解决。通过监听某个Entity表的变更情况,通知或者调用相关其他的业务代码处理,完美实现了与主体业务逻辑的解耦,也无需引入其他组件。

举个例子 :现有一个论坛发帖系统,发帖Post和评论Comment属于两个相对独立又有点关系的数据 ,现在需要检测当评论变化的时候,需要更新下Post对应记录的评论数字段。下面演示下具体实现 。

  • 首先,定制一个Listener类,并指定Callbacks注解

public class CommentCountAuditListener { n /**n * 当Comment表有新增数据的操作时  ,触发此方法的调用n */n @PostPersistn public void postPersist(CommentEntity entity) { n // 执行Post表中评论数字段的更新n // do something here...n }nn /**n * 当Comment表有删除数据的操作时 ,触发此方法的调用n */n @PostRemoven public void postRemove(CommentEntity entity) { n // 执行Post表中评论数字段的更新n // do something here...n }nn /**n * 当Comment表有更新数据的操作时,触发此方法的调用n */n @PostUpdaten public void postUpdate(CommentEntity entity) { n // 执行Post表中评论数字段的更新n // do something here...n }n n}

  • 其次,在评论实体CommentEntity上,加上自定义Listener信息

@Entityn@Table("t_comment")n// 指定前面定制的Listenern@EntityListeners({ CommentCountAuditListener.class})npublic class CommentEntity extends AbstractAuditable { n // ...n}

这样就搞定了 。

自定义Listener还有个典型的使用场景  ,就是可以统一的记录DB数据的操作日志 。

定制化SQL,随心所欲

JPA提供@Query注解,可以实现自定义SQL语句的能力。比如:

@Query(value = "select * from user " +n "where work_id in (?1) " +n "and department_id = 0 " +n "order by CREATE_TIME desc ",n nativeQuery = true)nList<OssFileInfoEntity> queryUsersByWorkIdIn(List<String> workIds);

如果需要执行写操作SQL的时候 ,需要额外增加@Modifying注解标识,如下 :

@Modifyingn@Query(value = "insert into user (work_id, user_name) values (?1, ?2)",n nativeQuery = true)nint createUser(String workId, String userName);

其中, nativeQuery = true 表示 @Query 注解中提供的value值为原生SQL语句。如果 nativeQuery 未设置或者设置为false ,则表示将使用 JPQL 语言来执行。所谓JPQL,即JAVA持久化查询语句 ,是一种类似SQL的语法 ,不同点在于其使用类名来替代表名,使用类字段来替代表字段名  。比如 :

@Query("SELECT u FROM com.vzn.demo.UserInfo u WHERE u.userName = ?1")npublic UserInfo getUserInfoByName(String name);

几个关注点要特别阐述下 :

  • like查询的时候,参数前后的 % 需要手动添加,系统是不会自动加上的

// like 需要手动添加百分号n@Query("SELECT u FROM com.vzn.demo.UserInfo u WHERE u.userName like %?1")npublic UserInfo getUserInfoByName(String name);

  • 使用 nativeQuery=true 查询的时候(原生SQL方式) ,不支持API接口里面传入Sort对象然后进行混合执行

// 错误示范 : 自定义sql与API中Sort参数不可同时混用n@Query("SELECT * FROM t_user u WHERE u.user_name = ?1", nativeQuery=true)npublic UserInfo getUserInfoByName(String name, Sort sort);nnn// 正确示范 : 自定义SQL完成对应sort操作n@Query("SELECT * FROM t_user u WHERE u.user_name = ?1 order by ?2", nativeQuery=true)npublic UserInfo getUserInfoByName(String name, String sortColumn);

  • 未指定 nativeQuery=true 查询的时候(JPQL方式),支持API接口里面传入 Sort 、 PageRequest 等对象然后进行混合执行,来完成排序 、分页等操作

// 正确 :自定义jpql与API中Sort参数不可同时混用n@Query("SELECT u FROM com.vzn.demo.UserInfo u WHERE u.userName = ?1")npublic UserInfo getUserInfoByName(String name, Sort sort);

  • 支持使用参数名作为 @Query 查询中的SQL或者JPQL语句的入参 ,取代参数顺序占位符

默认情况下,参数是通过顺序绑定在自定义执行语句上的 ,这样如果API接口传参顺序或者位置改变 ,极易引起自定义查询传参出问题,为了解决此问题,我们可以使用 @Param 注解来绑定一个具体的参数名称 ,然后以参数名称的形式替代位置顺序占位符 ,这也是比较推荐的一种做法。

// 默认的顺序位置传参n@Query("SELECT * FROM t_user u WHERE u.user_name = ?1 order by ?2", nativeQuery=true)npublic UserInfo getUserInfoByName(String name, String sortColumn);nn// 使用参数名称传参n@Query("SELECT * FROM t_user u WHERE u.user_name = :name order by :sortColumn", nativeQuery=true)npublic UserInfo getUserInfoByName(@Param("name") String name, @Param("sortColumn") String sortColumn);

字段命名映射策略

一般而言 ,JAVA的编码规范都要求filed字段命名需要遵循小驼峰命名的规范 ,比如userName,而DB中column命名的时候,很多人习惯于使用下划线分隔的方式命名,比如 user_name 这种 。这样就涉及到一个映射的策略问题 ,需要让JPA知道代码里面的userName就对应着DB中的 user_name  。

这里就会涉及到对命名映射策略的映射  。主要有两种映射配置,下面分别阐述下。

  • implicit-strategy

配置项key值  :

spring.jpa.hibernate.naming.implicit-strategy=xxxxx

取值说明 :

映射规则说明

org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImp

默认的命名策略  ,兼容JPA2.0规范

org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl

兼容老版本Hibernate的命名规范

org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl

与ImplicitNamingStrategyJpaCompliantImp基本相同

org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl

兼容JPA 1.0规范中的命名规范  。

org.hibernate.boot.model.naming.SpringImplicitNamingStrategy

继承ImplicitNamingStrategyJpaCompliantImpl,对外键、链表查询 、索引如果未定义 ,都有下划线的处理策略 ,而table和column名字都默认与字段一样

  • physical-strategy

配置项key值:

spring.jpa.hibernate.naming.physical-strategy=xxxxx

取值说明:

映射规则说明

org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

默认字符串一致映射 ,不做任何转换处理 ,比如java类中userName ,映射到table中列名也叫userName

org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

java类中filed名称小写字母进行映射到DB表column名称 ,遇大写字母时转为分隔符"_"命名格式 ,比如java类中userName字段 ,映射到DB表column名称叫user_name

  • physical-strategy与implicit-strategy

SpringData JPA只是对JPA规范的二次封装,其底层使用的是 Hibernate ,所以此处涉及到Hibernate提供的一些处理策略  。Hibernate将对象模型映射到关系数据库分为两个步骤 :

  1. 从对象模型中确定逻辑名称。逻辑名可以由用户显式指定(使用 @Column 或 @Table ),也可以隐式指定 。
  2. 将逻辑名称映射到物理名称,也就是数据库中使用的名称 。

这里 , implicit-strategy 用于第一步隐式指定逻辑名称 ,而 physical-strategy 则用于第二步中逻辑名称到物理名称的映射 。

注意:

当没有使用 @Table 和 @Column 注解时 , implicit-strategy 配置项才会被使用 ,即 implicit-strategy 定义的是一种缺省场景的处理策略;而 physical-strategy 属于一种高优先级的策略,只要设置就会被执行,而不管是否有 @Table 和 @Column 注解。

小结 ,承上启下

好啦,本篇内容就介绍到这里 。

通过本篇的内容 ,我们对于如何在项目中使用 Spring Data JPA 来进行一些较为复杂场景的处理方案与策略有了进一步的了解,再结合本系列此前的内容,到此掌握的JPA的相关技能已经足以应付大部分项目开发场景。

在实际项目中,为了保障数据操作的可靠、避免脏数据的产生 ,需要在代码中加入对数据库操作的事务控制。在下一篇文档中 ,我们将一起聊一聊Spring Data JPA业务代码开发中关于数据库事务的控制,以及编码中存在哪些可能会导致事务失效的场景等等。


友情链接苹果发布会提前,无刘海iPhone 终于来了新车|哈弗H6入局新能源,PHEV车型亮相成都车展,预售16.88万起如何在App中实现课程表功能?全新哈弗 驭电未来 正式进入新能源赛道matlab2022a版本中的 app 中的串口设计「建议收藏」无门槛,每月一次,18元猫超卡等你白领经过一天的努力思考后,你的身体可能会促使你做出更糟糕的选择水产养殖水下机器人应用研究现状图解丨常态化核酸检测 这些细节要注意FF执行董事长Sue Swenson被曝遭全球140多名员工实名请愿罢免当机器狗去上班:进厂打工,还是做居家保姆?长安汽车朱华荣:建议将我国停售燃油车提上议事日程中兴远航30S采用人脸指纹双解锁,8月30日上市腾讯云与科大国创签署战略合作协议10万以内的新能源车,为什么首选小蚂蚁?新能源纯电、混动车型基础分类紧追华为抢占高端市场!vivo高端旗舰官宣!9月发布前排无人,全程40分钟无接管穿越市区!Waymo自动驾驶视频火了红米Note12 Pro曝光,120瓦快充+天玑8100,千元机皇强势登场小米平板5 Pro 12.4英寸大屏版已可购买,几个价位哪个更合适?海南科技职业大学机电工程学院:“机”巧善工“电”竞前程华南理工校友干出一只独角兽:微容科技估值超80亿降价最快的几款手机,手机各方面配置不错,重要的是价格便宜搭载天玑9000 +,ROG新款游戏手机跑分曝光:总跑分超114万分服贸观止丨AI学习机、智能词典笔……智能学习硬件产品亮相服贸会爱立信诺基亚证实:已和谷歌合作,在Android 13上演示网络切片英飞凌老板:芯片危机将持续多年中兴:被美国商务部一纸禁令掐住咽喉的世界通讯行业巨头亚马逊美国站又要征税了!竞争激烈的卖家们如何夹缝求生?语音控股在巴基斯坦手机市场的份额将超过40%上半年将降至36.8%联电携手Cadence针对22纳米模拟与混合信号设计完成认证当图书馆遇上元宇宙,会是怎样奇妙的体验?苹果开始M3芯片设计扎克伯格回应“长得像机器人”:听证会不人性导致表情管理失控冲上热搜!iOS15.6.1为iPhoneSE2带来惊艳的优化,不发热,推荐2025年的太阳风暴百年难遇,电力系统或将瘫痪,人类会怎么样?低价格也有高配置 千元机也有优秀的 这三款看过来款款都是良心推荐跨境电商老板不懂这些,财务很可能就白招了资讯 | 网约车能快速盈利吗?华为申请PETAL出行图形商标这是什么操作?小米手机也用上了iOS 16?
联系我们

地址:联系地址联系地址联系地址

电话:020-123456789

传真:020-123456789

邮箱:admin@aa.com

0.2375

Copyright © 2024 Powered by 通辽市某某化工涂料售后客服中心   sitemap