在 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 的方法,底层会调用 EntityManager 的 persist() 和 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 在持久化过程中起着至关重要的作用,带来了许多好处,所增加的复杂性可以忽略不计。它绝对物有所值。
共同学习,写下你的评论
评论加载中...
作者其他优质文章