为了账号安全,请及时绑定邮箱和手机立即绑定

JPA 实体生命周期

在 JPA 实体创建之后和它与数据库同步之前存在一个中间层。这一层被称为持久化上下文,负责从实体创建到被转换为 SQL 查询并执行于数据库期间的状态的管理。

但有人可能会想,我们为什么要用那样的东西呢?这不会让持久化过程变得太复杂吗?直接操作数据库不是更直接吗?而不是在中间多做处理?

让我们看看吧!

持久上下文

持久化上下文是Java Persistence API (JPA) 的核心组件之一。它具备多种功能,例如:

  • 在 Java 代码和数据库之间充当 缓存,因此它也被称为 一级缓存。如果一个实体已经在持久化上下文中,它将从上下文中返回,而不是从数据库中再次加载。
  • 能够通过 脏检查机制 跟踪在给定的_工作单元_中对实体所做的所有更改。
  • 将所有实体分组,使它们在与数据库同步时以最佳顺序执行,从而鼓励 批处理查询
  • 确保在上下文中,任何实体实例(或数据库行)仅存在一次,从而提高持久化过程的效率。

在JPA中,持久性上下文由EntityManager(由EntityManagerFactory创建)创建和管理。同样,在Hibernate中,持久性上下文由Session(由SessionFactory创建)创建和管理。为了更清楚地理解JPA和Hibernate之间的区别,你可以查阅《JPA vs Hibernate vs Spring Data JPA》这篇文章。

每当 EntityManager 创建一个新的事务时,它都会创建一个新的持久化上下文,并在事务提交或回滚时关闭它。在事务块内使用的所有实体都将由持久化上下文管理,这些实体只会与数据库同步,要么在执行刷新操作时,要么在事务结束时。

持久化上下文中的更改通过脏检查机制来追踪。当一个实体首次添加到持久化上下文时,会对其进行一次快照。之后,如果实体发生任何更改,将与快照版本进行比较以识别变化。如果没有发现任何更改,数据库将不会被访问。

持久化上下文就像一个中间缓存。如果实体已存在于上下文中,就直接从上下文中获取,而不用从数据库中获取。它会汇总所有对实体所做的变更,并且只在事务结束或执行刷新操作时才将其与数据库同步。

借助Persistence Context,数据库同步尽可能地被推迟,以避免执行过多的查询。可以批量处理准备执行的多个操作以提高性能。这也为以最佳顺序执行查询提供了可能。在系统刷新时,查询将按照以下顺序执行:

  • 插入操作,更新操作,删除操作

知道这一点,你觉得,如果每个用户的名字必须独一无二,在里会怎样呢?

    // 实体管理工厂用于创建实体管理器
    EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("PersistenceUnitName");  
    // 使用实体管理工厂创建实体管理器
    EntityManager entityManager = entityManagerFactory.createEntityManager();  

    // 创建一个新的用户对象
    User user = new User();  
    // 设置用户的ID
    user.setId(1);  
    // 设置用户的姓名
    user.setName("Flavius");  

    // 将用户对象持久化到数据库
    entityManager.persist(user);  

    // 使用实体管理器查找ID为1的用户对象
    User persistedUser = entityManager.find(User.class, 1);  
    // 从数据库中移除该用户对象
    entityManager.remove(persistedUser);  

    // 再次将用户对象持久化到数据库
    entityManager.persist(user);

因为在刷新时删除操作是最后一个执行的,它会尝试连续插入两个具有相同名称的项目。因为名称必须唯一,程序会抛出一个 ConstraintViolationException 异常。

实体的状态

从它们被创建的那一刻起,这些实体对象可以经历不同的状态变化。在JPA中,EntityManager的以下方法可以用来进行状态之间的转换:

    /*

* 持久化一个实体实例
    */
    public void persist(Object entity);

    /*

* 合并实体的状态到持久化上下文中。
    */
    public <T> T merge(T entity);

    /*

* 删除实体实例
    */
    public void remove(Object entity);

    /*

* 通过主键查找实体。首先在持久化上下文中查找,若存在则返回,否则从数据库中获取。
    */
    public <T> T find(Class<T> entityClass, Object primaryKey);

    /*

* 通过主键查找实体,但延迟加载(只有在使用实体时才执行查询)。
    */
    public <T> T getReference(Class<T> entityClass, Object primaryKey);

    /*

* 刷新实体的状态,覆盖之前的更改。
    */
    public void refresh(Object entity);

    /*

* 移除实体从持久化上下文中。未刷新的修改将丢失。
    */
    public void detach(Object entity);
瞬时的——

一个实体在其首次创建的时刻变得暂时。处于这种状态时,持久性上下文不管理该实体,因此它在数据库中没有任何对应的记录。它仅仅是一个普通的Java类。

    User user = new User();  
    user.setId(1);  
    user.setName("Flavius");

暂态

管理的

如果一个实体被管理,这意味着它属于持久性上下文,并且它唯一地对应数据库中的一行。实体能够成为受管状态的方式有很多种。

  • 在调用 persist() 或 merge() 方法的时候
  • 在使用 find() 或 getReference() 方法获取实体的时候
// 瞬时到托管的示例

EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("PersistenceUnitName"); // 创建一个名为 "PersistenceUnitName" 的实体管理工厂
EntityManager entityManager = entityManagerFactory.createEntityManager();  

User user = new User(); // 创建一个新的用户实体
user.setId(1); // 设置用户ID为1
user.setName("Flavius"); // 设置用户名为 "Flavius"

entityManager.persist(user); // 将 user 对象持久化到数据库
// 数据库管理示例

EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("PersistenceUnitName");  
EntityManager entityManager = entityManagerFactory.createEntityManager();  

User user = entityManager.find(User.class, 1);  // 查找ID为1的用户
user.setName("John");  // 设置用户的名字为John

受管理的状态

独立屋

如果某个实体曾被管理但现在不再,它将处于脱离状态。你可能会遇到这种情况:

  • 当持久化上下文在 flush() 方法执行或事务提交前被关闭或清空时
  • 当使用 detach() 方法时
EntityManagerFactory 实体管理工厂 = Persistence.createEntityManagerFactory("PersistenceUnitName");  
EntityManager 实体管理器 = 实体管理工厂.createEntityManager();  

// 从实体管理器中查找 User 类型的实体,其中 1 是主键值
User 用户 = 实体管理器.find(User.class, 1);  

// 将用户实体从实体管理器中分离
实体管理器.detach(用户);

独立的

已删除

处于已被标记移除状态的实体计划被移除,但这一动作只会在持久化上下文刷新时发生。它只能通过调用 remove() 方法来达到这种状态。

    EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("PersistenceUnitName");  // 实体管理工厂 (shítǐ guǎnlí gōngchǎng)
    EntityManager entityManager = entityManagerFactory.createEntityManager();  // 实体管理器 (shítǐ guǎnlíqì)

    User user = entityManager.find(User.class, 1);  // 根据用户类 (yóugōnghù lèi) 查找用户
    // 用户类 (yóugōnghù lèi) 是指User类

    entityManager.remove(user);  // 删除用户 (shāchú yóugōnghù)

已移除

我不太确定有没有用过这些方法

如果你在用 Spring 框架,你可能会说这样的话。

好的,我懂了,但我之前用过JPA和Hibernate一阵子,但从来没用过这些方法,比如persist()、merge()、detach()等,这是为什么?

这是因为Spring Data JPA使得这种情况成为可能,它在所有EntityManager方法上添加了一层抽象,使您能够以更友好的方式使用它们。

例如,当你使用 JpaRepository 中的 save() 方法时,它也继承了 CrudRepository 的方法,底层会调用 EntityManagerpersist()merge() 方法。

    // 以下代码在 SimpleJpaRepository 中实现 JpaRepository 接口中的 save 方法
    @Transactional  // 注释: 此方法使用 @Transactional 注解,确保事务一致性
    public <S extends T> S save(S entity) {  
        Assert.notNull(entity, "实体对象不能为 null");  
        if (this.entityInformation.isNew(entity)) {  
            this.entityManager.persist(entity);  
            return entity;  
        } else {  
            return this.entityManager.merge(entity);  
        }  
    }

所以,你通过JpaRepository间接操作这些实体,而不是直接进行的。

结果

现在我们知道了持久化上下文是如何工作的,以及实体如何从一种状态转变到另一种状态,现在我们可以回头看看文章开头提到的问题:拥有这样一个中间层是否值得?

想象一下没有这一层的持久化过程。在一个实体上先执行插入操作,然后紧接着进行两次更新操作,会生成三个查询,而不是一个带有实体最新版本的插入查询。两次从数据库加载同一个实体会产生两个查询,而不是重复创建查询来加载之前已有的实体。这听起来并不像是一个优化的持久化策略。总而言之,

Persistence Context 在持久化过程中起着至关重要的作用,带来了许多好处,所增加的复杂性可以忽略不计。它绝对物有所值

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消