从零开始 Spring Boot 49:Hibernate Entity Lifecycle

这篇具有很好参考价值的文章主要介绍了从零开始 Spring Boot 49:Hibernate Entity Lifecycle。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

从零开始 Spring Boot 49:Hibernate Entity Lifecycle

从零开始 Spring Boot 49:Hibernate Entity Lifecycle

图源:简书 (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 将实例的状态进行转换:

从零开始 Spring Boot 49:Hibernate Entity Lifecycle

图源: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,谢谢阅读。

可以从这里获取本文的完整示例代码。文章来源地址https://www.toymoban.com/news/detail-515120.html

参考资料

  • 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

到了这里,关于从零开始 Spring Boot 49:Hibernate Entity Lifecycle的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 从零开始学Spring Boot系列-集成mybatis

    在Spring Boot的应用开发中,MyBatis是一个非常流行的持久层框架,它支持定制化SQL、存储过程以及高级映射。在本篇文章中,我们将学习如何在Spring Boot项目中集成MyBatis,以便通过MyBatis进行数据库操作。 首先,我们需要在项目中添加MyBatis的依赖。在Spring Boot中,我们通常会使

    2024年03月10日
    浏览(129)
  • 从零开始学Spring Boot系列-集成Kafka

    Apache Kafka是一个开源的分布式流处理平台,由LinkedIn公司开发和维护,后来捐赠给了Apache软件基金会。Kafka主要用于构建实时数据管道和流应用。它类似于一个分布式、高吞吐量的发布-订阅消息系统,可以处理消费者网站的所有动作流数据。这种动作流数据包括页面浏览、搜

    2024年03月21日
    浏览(62)
  • Spring Boot(04):让你的Spring Boot应用“火力全开”,从零开始学习starter

            Spring Boot是一款非常流行的Java开发框架,其具有快速开发、自动化配置、内嵌服务器、易于扩展等特点,因此备受开发者欢迎。在日常开发中,我们经常需要在不同的环境中进行测试和部署,此时,如何实现开发、测试、生产环境的快速切换,成为了我们需要解决

    2024年04月13日
    浏览(61)
  • 从零开始 Spring Boot 52:@Embedded 和 @Embeddable

    图源:简书 (jianshu.com) 这篇文章会介绍 @Embedded 和 @Embeddable 两个注解在 JPA 中的用法。 先看一个示例: 这里使用了 Lombok 相关注解(比如 @Builder )帮助构建实体类,详细内容可以阅读我的相关文章。 user_student 是一个学生表,其中的 contacts_ 开头的字段保存联系人信息,这体

    2024年02月12日
    浏览(67)
  • 从零开始 Spring Boot 38:Lombok 与依赖注入

    图源:简书 (jianshu.com) 在之前的文章中,我详细介绍了 Lombok 的用法,考虑到在 Spring 中使用依赖注入(DI)是如此的频繁,因此有必要讨论使用 Lombok 时可能对依赖注入造成的影响。 我们都知道,Spring 中的依赖注入分为三种情况: 通过属性进行依赖注入。 通过构造器进行依

    2024年02月08日
    浏览(70)
  • 从零开始 Spring Boot 37:初始化 ApplicationContext

    图源:简书 (jianshu.com) 从前文可以知道,作为 Ioc 容器的 ApplicationContext,需要进行一系列步骤来初始化以最终就绪(对于 Web 应用来说就是可以提供Http服务)。 这些步骤大概可以分为以下内容: 准备上下文关联的 Environment 。 初始化 ApplicationContext( ApplicationContextInitializers

    2024年02月08日
    浏览(44)
  • 从零开始学Spring Boot系列-外部化配置

    Spring Boot 允许你将配置外部化,以便可以在不同的环境中使用相同的应用程序代码。可以使用属性文件、YAML文件、环境变量和命令行参数将配置外部化。属性值可以通过使用 @Value 注解直接注入 bean,可以通过 Spring 的 Environment 抽象访问,也可以通过 @ConfigurationProperties。 Sp

    2024年04月10日
    浏览(107)
  • 从零开始 Spring Boot 51:JPA 中的默认列值

    图源:简书 (jianshu.com) JPA 是一个 ORM 框架,因此,通常我们需要在实体类中定义表结构,这其中就包含可能的字段默认值。 本文介绍如何在 Hibernate(JPA)中设置默认列值(Default Column Value)。 最简单的方式是对实体类指定一个默认的属性值,比如: 测试用例: 这样做的缺点

    2024年02月11日
    浏览(91)
  • 从零开始 Spring Boot 57:JPA中的一对多关系

    图源:简书 (jianshu.com) 在上篇文章中我们介绍了如何在 JPA 中实现实体的一对一关系,在关系型数据库设计中,除了一对一关系,还存在一对多关系。本篇文章介绍如何在 JPA 中实现一对多关系。 假设我们有两张表,学生表和电子邮件账号表,一个学生可以有多个电子邮件账

    2024年02月12日
    浏览(48)
  • Spring Boot日志系统大揭秘:从零开始学习Spring Boot日志:常见问题解答和最佳实践

    Spring Boot 日志机制和工具用于记录应用程序的日志信息和追踪应用程序的执行过程。它集成了常用的日志框架,如 Log4j、logback、Java Util Logging等,并提供简单易用的配置方式,让开发人员可以方便地监控应用程序的运行状态和性能。在项目启动时,日志已经开始输出,但尚未

    2024年02月08日
    浏览(63)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包