从零开始 Spring Boot 49:Hibernate Entity Lifecycle

news2024/9/20 0:45:21

从零开始 Spring Boot 49:Hibernate Entity Lifecycle

spring boot

图源:简书 (jianshu.com)

本文将介绍 Hibernate 的 Session 接口,以及如何用 Session 的相关 API 转换实体(Entity)的生命周期状态。

如果缺少的 JPA 和 Hibernate 的基本认识,可以阅读前篇文章。

概念

持久化上下文

在 JPA 的相关概念中,存在一个持久化上下文(Persistence Context)。

持久化上下文处于代码端与数据库之间,充当一个容器或一级缓存的作用,负责管理运行时的实体(Entity),它可以在合适的时间从数据库中加载数据到实体对象,也可以将实体对象“回写”到数据库。

在 Hibernate 中,持久化上下文由 org.hibernate.Session实例表示,在标准的 JPA 中,表现为jakarta. persistence. EntityManager。在使用 Hibernate 的时候,这两者都可以使用,但相比EntityManagerSession是一个更丰富的接口,有时候可能会更有用。

实体状态

与持久化上下文(本文特指Session)关联的实体实例是存在状态的,它们必然处于以下三种状态之一:

  • transient,此实例从来没有附加到 Session,且数据库中也不存在对应的行数据,这只是一个为保存数据到数据库创建的新对象。
  • persistent,实例与唯一的 Session 对象关联,并对应数据库中的一条记录。Session 刷新后,将检查数据一致性,并在不一致的情况下更新数据库中的数据。
  • detached,实例曾经与 Session 关联(处于 persistent 状态),但当前已经不是。将实例从 Session 逐出(Session.evict)、关闭 Session 或将实例序列化/反序列化都会让实例进入这个状态。

可以通过 Session 的 API 将实例的状态进行转换:

image-20230628104714585

图源:Baeldung

图中的一些方法调用已经作废,比如save()

持久的实体是最为关键的,处于这种状态的实体实例会被 Session 管理和监控,对这些实体的任何改变都会被记录,且在事务提交或 Session 关闭时回写到数据库,且不需要我们调用任何其它方法。

持久实体也被称作“被管理的实体”(Managed Entity)。实际上这些实体都有一个唯一的数据库标识(database identifier),所以对这些实体的任何更改都会被传播到数据库。

  • 这也是为什么在实体类中要用@Id定义一个主键字段。
  • 要牢记,只有在事务提交后才会真正向数据库中插入数据,但在此之前,也会为持久实体分配数据库标识。

Session API

下面我们看相关的 Session API。

在介绍相关 API 之前,需要对一些用到的工具类进行简要说明。

@Component
public class HibernateLifecycleUtil {
    @SneakyThrows
    public List<EntityEntry> getManagedEntities(Session session) {
        // 传入的参数 session 不能是一个代理对象,否则会报类型转换错误
        Map.Entry<Object, EntityEntry>[] entries = ((SessionImplementor) session).getPersistenceContext().reentrantSafeEntityEntries();
        return Arrays.stream(entries).map(e -> e.getValue()).collect(Collectors.toList());
    }
}

HibernateLifecycleUtil.getManagedEntities方法可以返回 Session 管理的实体实例(persistent)。

public class DirtyDataRecorderInterceptor implements Interceptor, Serializable {
    private static final List<Object> dirtyEntities = Collections.synchronizedList(new ArrayList<>());

    @Override
    public boolean onFlushDirty(Object entity, Object id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) throws CallbackException {
        boolean b = Interceptor.super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types);
        dirtyEntities.add(entity);
        return b;
    }

    public static List<Object> getDirtyEntities() {
        return dirtyEntities;
    }

    public static void clearDirtyEntitites() {
        dirtyEntities.clear();
    }
}

DirtyDataRecorderInterceptor 是一个 Hiberante 的拦截器,在这里用于记录变成“脏数据”的实体实例(被修改过的 persistent 实体实例)。

之前的 Hibernate 拦截器往往通过扩展 EmptyInterceptor 实现,该类已经作废。

persist

使用persist方法可以将一个瞬时(transient)的实体实例变成持久的(关联到 Session):

Session session = sessionFactory.withOptions().interceptor(new DirtyDataRecorderInterceptor()).openSession();
Transaction transaction = session.beginTransaction();
var student = new Student("icexmoon", LocalDate.of(1990, 1, 1), Gender.MALE);
session.persist(student);
transaction.commit();
session.close();

事务提交后就能看到数据库中多了一条新纪录。

这里只展示测试用例中的关键代码,可以从这里查看完整代码。

特别的,如果实体实例已经是持久的,那么调用persist方法不会有任何影响。如果实体实例是分离的,则会产生一个异常

// 添加实体,实体变成持久化的
session.persist(student);
// 删除实体,实体变成已分离的
session.evict(student);
// 尝试添加已分离的实体,会抛出一个PersistentObjectException异常
Assertions.assertThrows(PersistentObjectException.class, () -> {
session.persist(student);
});

merge

使用merge方法,可以用一个实体实例“更新” Session 中的对应实体实例。

一个典型的用法是在实体实例被分离后,改变其数据,并合并(merge)回 Session:

List<Student> students = session.createQuery("from user_student", Student.class)
.getResultList();
var icexmoon = students.stream().filter(student -> student.getName().equals("icexmoon"))
.findFirst().get();
// 分离实体,分离后可以对实体进行序列化/反序列化等操作
session.evict(icexmoon);
icexmoon.setBirthDay(LocalDate.of(2000, 5, 1));
Student mergedIcexmoon = session.merge(icexmoon);
// 合并后的 entity 与原始 entity 不是同一个对象,但内容一致
Assertions.assertNotSame(mergedIcexmoon, icexmoon);
Assertions.assertEquals(mergedIcexmoon, icexmoon);

要注意的是,merge方法被调用后会返回“被管理的实体实例”,且该实例与用于合并的实例并不是同一个对象。前者是持久的,后者依然是分离的。

如果实体实例是瞬时的,调用merge方法会新建一个持久的实体实例并返回

// 用一个新的 Entity 添加
Student lalala = new Student("lalala", LocalDate.of(2001, 1, 1), Gender.MALE);
Student mergedLalala = session.merge(lalala);
// 合并后的 entity 与原始 entity 不是同一个对象,但内容一致
Assertions.assertNotSame(mergedLalala, lalala);
Assertions.assertEquals(mergedLalala, lalala);

如果实体实例已经是持久的,调用这个方法不会有任何影响。

evict & remove

前边已经多次演示了evict的用途——将持久实体从 Session 中“驱逐”(变成分离实体)。

要注意的是,evict仅改变了实体的状态,并不会影响数据库(不会将对应数据删除):

List<Student> students = session.createQuery("from user_student", Student.class)
.getResultList();
var icexmoon = students.stream().filter(s -> s.getName().equals("icexmoon")).findFirst().get();
session.evict(icexmoon);
transaction.commit();
session.close();
var modifiedStudents = studentRepository.findAll();
Assertions.assertEquals(this.students.size() , modifiedStudents.size());

remove会将持久实体从 Session 中移除,变成瞬时实体。换言之,会删除数据库中对应的数据:

List<Student> students = session.createQuery("from user_student", Student.class)
.getResultList();
var icexmoon = students.stream().filter(s -> s.getName().equals("icexmoon")).findFirst().get();
session.remove(icexmoon);
transaction.commit();
session.close();
var modifiedStudents = studentRepository.findAll();
Assertions.assertEquals(this.students.size() - 1, modifiedStudents.size());

因为remove后的实体状态是瞬时的(transient),所以可以用persist再次添加:

var icexmoon = students.stream().filter(s -> s.getName().equals("icexmoon")).findFirst().get();
session.remove(icexmoon);
session.persist(icexmoon);

身份域

通常我们并不会直接修改(或添加)实体对象中的身份域(Indentity field),身份域都是由 Session 赋予和管理的。但如果我们愿意,完全可以通过修改和指定身份域去更新指定的持久实体:

var icexmoon = students.stream().filter(s -> s.getName().equals("icexmoon")).findFirst().get();
var student = new Student("icexmoon", LocalDate.of(2002, 1, 1), Gender.MALE);
student.setId(icexmoon.getId());
session.merge(student);

在这个示例中,并没有像常见的那样直接修改持久实体icexmoon中的属性来变更数据,而是创建了一个新的瞬时实体student,并且通过setId方法为其指定了和持久实体相同的身份域,此时调用merge方法,就不再是添加一个新的持久实体,而是修改了已有的持久实体(因为它们的 id 相同),并最终修改了数据库中的数据。

一般而言实体类不需要 id 的 Setter,这里仅为了实现测试用例而添加。

The End,谢谢阅读。

可以从这里获取本文的完整示例代码。

参考资料

  • Hibernate Entity Lifecycle | Baeldung
  • Hibernate: save,persist, update, merge | Baeldung
  • 从零开始 Spring Boot 48:JPA & Hibernate - 红茶的个人站点 (icexmoon.cn)
  • What Is the JDK com.sun.proxy.$Proxy Class? | Baeldung
  • Dynamic Proxies in Java | Baeldung
  • spring boot 中如何设置hibernate Interceptor
  • Hibernate Interceptors | Baeldung
  • Guide to the Hibernate EntityManager | Baeldung

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/698123.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ubuntu18.04 ros报错Command ‘roscore‘ not found

问题描述 git clone https://github.com/ros/catkin.gitcd catkingit branch melodic-develgit checkout melodic-develmkdir buildcd buildcmake …make -j8 && sudo make installcd .. sudo python2 setup.py installsudo python3 setup.py install出现问题 hua…

ADB命令(app自动化测试底层技术)

一、adb相关知识体系 1&#xff0c;adb的使用场景 操作手机设备 app自动化测试2&#xff0c;adb测试体系 app自动化测试-appium 遍历测试-appcrawier app性能测试 app专项测试 STF设备管理平台 云测平台 兼容性测试 二、adb 基础 1&#xff0c;什么是adb Adb是用来操作Andro…

spark、pyspark 常用的模版 demo 网址

1、我自己有时候用百度或者其他的搜索出来的spark 常用案例&#xff0c;质量有的好有的差有时候就很烦。特地分享一个我常用的质量高的网站地址 https://sparkbyexamples.com/pyspark/pyspark-collect/

进阶2:JVM 启动参数

目录 jvm启动参数 参数分类 系统属性 功能解析 运行模式 jvm有两种运行模式 堆内存 设置堆内存 GC相关 GC 日志相关的参数 分析诊断 指定垃圾收集器相关参数 JavaAgent 什么是Java agent 常见问题 视频 前言 这堂课程不用过多的记忆&#xff0c;自身有印象即可…

Vue中Object.defineProperty

放到Object.defineProperty中比直接写在person对象中更灵活&#xff0c;可以设置的属性更多 <script>let person{name:张三,age:男}Object.defineProperty(person,age,{value:18,enumerable:true, //控制属性是否可以被枚举&#xff0c;默认值是falsewritable:true, //控…

无人机动力测试台-15公斤级-Flight Stand 15

Flight Stand 15测试台通过测量电机和螺旋桨的拉力、扭矩、转速、电流、电压、温度、螺旋桨效率和电机效率来精准地描述和评估无人机动力系统的性能。 产品应用 Flight Stand 15测试台可以用于以下方向&#xff1a; 实时动态测试 FS15 Pro的1000 Hz采样率使测试成为可能&…

Chrome 插件开发覆写xhr请求

这几天搞chrome谷歌浏览器插件遇到个问题 我想拦截网页请求&#xff0c;并把数据传递到下一个子窗口。获取responsebody内容 background.js 单纯靠sendmessage实现不了通讯 chrome.runtime.sendMessage({data: e.data.responseText,type:ajaxResponse}); 最开始的时候想用chr…

基于matlab使用自动要素匹配查找图像旋转和缩放(附源码)

一、前言 此示例演示如何自动确定一对图像之间的几何变换。当一个图像因旋转和缩放而相对于另一个图像失真时&#xff0c;请使用 和 查找旋转角度和比例因子。然后&#xff0c;您可以转换扭曲的图像以恢复原始图像。 二、步骤 1&#xff1a;读取图像 将映像引入工作区。 三、…

Bean的基础配置

问题1&#xff1a;在<bean>标签上如何配置别名&#xff1f; 问题2&#xff1a;Bean的默认作用范围是什么&#xff1f;如何修改&#xff1f; 1. Bean基础配置【重点】 类型描述名称bean类型标签所属beans标签功能定义Spring核心容器管理的对象格式 <beans> …

xcall脚本和xsync脚本 linux

一、xcall脚本 在cd /usr/local/bin/路径下创建xcall vim /usr/local/bin/xcall #!/bin/bash for host in hadoop100 hadoop101 hadoop102 doecho $host ssh $host jps done …

网络攻击与防御措施及防御产品

网络攻击与防御措施及产品 思维导图模板_ProcessOn思维导图、流程图分析了攻击类型、并列出了对不同的攻击方法的防御措施&#xff0c;和可以使用的安全设备。https://www.processon.com/view/649ba05dabde99162f8aecba

Java字节码分析快速入门/字节码执行分析(一)

目录 什么是字节码&#xff1f; 为什么要了解字节码&#xff1f; 如何查看字节码&#xff1f; 字节码包括哪些内容&#xff1f; 总结 hello读者盆友们&#xff0c;在上一篇文章[Java基础]面向对象-内存解析_小王师傅66的博客-CSDN博客最后&#xff0c;我们通过查看字节码&…

2022版本的unity里面的snap setting在哪

1.2022版本的unity 在scene 里面图中画圈的位置。 2.点击后效果如图。

数据库实验—存储过程

创建下列存储过程&#xff1a; 查询某位学生指定课程的成绩和学分&#xff08;修正&#xff1a;若该学生的课程成绩小于60分&#xff0c;则学分要显示为0分&#xff09; 提示&#xff1a;使用CASE…WHEN 例如&#xff1a;分别查询’张建国’和’李平方’选修的’数据库系统原理…

chatgpt赋能python:Python退出venv指南:安全退出虚拟环境

Python退出venv指南&#xff1a;安全退出虚拟环境 作为一名有10年Python编程经验的工程师&#xff0c;我很清楚地明白通过venv管理Python虚拟环境的好处。虚拟环境为每个项目提供了一个独立的Python运行时环境&#xff0c;这可以避免项目之间的依赖冲突&#xff0c;并且可以轻…

canvas图形等距、间距测量

首先定义画线&#xff08;实线、虚线&#xff09;、画面&#xff08;矩形块&#xff09;、值&#xff08;距离&#xff09;等渲染数据结构&#xff08;渲染数据只提供坐标信息和一些基本样式属性&#xff0c;不需要依赖渲染是dom&#xff0c;还是canvas或webgl&#xff09;侦测…

C语言:调整数组使奇数全部都位于偶数前面

题目&#xff1a; 输入一个整数数组&#xff0c;实现一个函数&#xff0c; 来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分&#xff0c; 所有偶数位于数组的后半部分。 &#xff08;奇数在数组前面&#xff0c;偶数在数组后面&#xff09; 思路&#xff1a;…

【shell】expect命令详解:用expect实现自动化交互式操作

文章目录 一. 运用场景二. 语法说明三. 例子1. scp文件传输自动化2. ssh远程登录3. 切到root用户4. 创建ssh key5. ssh到一个节点创建用户 一. 运用场景 expect主要应用于自动化交互式操作的场景&#xff0c;借助Expect处理交互的命令&#xff0c;可以将交互过程如&#xff1a…

FPGA的软核、硬核、固核

“核” 现在的FPGA设计&#xff0c;规模巨大而且功能复杂&#xff0c;因此设计的每一个部分都从头开始是不切实际的。一种解决的办法是&#xff1a;对于较为通用的部分可以重用现有的功能模块&#xff0c;而把主要的时间和资源用在设计中的那些全新的、独特的部分。这就像是你在…

Golang:cannot find main module; see ‘go help modules‘解决

出现这个的原因就是之前在Golang语言介绍、环境搭建以及编译工具&#xff08; CDN 加速代理&#xff09;https://mp.csdn.net/mp_blog/creation/editor/131431492 这个部分配置CDN加速代理的时候&#xff0c;开启了GO111MODULEon后&#xff1b; go会忽略GOPATH和vendor文件夹&…