通过 JPA 对实体对象进行增删改查时,JPA 需要维护存储在 Session(即一级缓存)中的实体对象,同时还要维护实体对象的状态。
实体对象的状态是 JPA 中非常重要的概念,它描述了实体对象从瞬时状态(Transient)到持久状态(Persistent)、从删除状态(Removed)到游离状态(Detached)的状态变换。JPA 中对实体的操作主要就是改变实体的状态。
EntityManager 提供一系列的方法管理实体对象的状态,如下:
persist:将新创建的或已删除的实体转变为持久化状态,数据存入数据库
remove:删除持久状态的实体,实体转变为删除状态
merge:将游离实体转变为持久化状态,数据存入数据库
如果使用了事务管理,则事务的 commit/rollback 也会改变实体的状态,实体状态变化如下图:
瞬时状态的实体就是一个普通的 java 对象,和持久化上下文无关联,数据库中也没有数据与之对应。
我们使用 new 关键字创建出来的新对象就是瞬时状态,该对象没有OID,且不在一级缓存中。
JPA 中调用持久化方法 persist() 之后,将对象保存到数据库中,对象状态转化成持久状态。
你可以使用 EntityManager 进行 find() 或者 persist() 操作返回的对象将处于托管状态,此时该对象已经处于持久化上下文中,因此任何对于该实体的更新都会同步到数据库中。
JPA 中对象存在于数据库中,但是不在一级缓存中,此时对象位于游离状态。
当事务提交后,处于托管状态的对象就转变为了游离状态。此时该对象已经不处于持久化上下文中,因此任何对于该对象的修改都不会同步到数据库中。
JPA 中事务一旦提交,对象就会被从数据库中删除(delete 操作),此时实体对象介于持久状态和被删除之间的一个临界状态。
我们可以通过下面的表格了解到各个状态的特点:
状态 | 是否在一级缓存 | 是否有OID |
瞬时状态(Transient) | 否 | 否 |
持久状态(Persistent) | 是 | 是 |
游离状态(Detached) | 否 | 是 |
删除状态(Removed) | 是 | 是 |
注意:其中的 OID 即对象 ID(Object ID)
上面有了对实体对象状态的了解之后,我们来分析下面的案例中 SQL 的发送。下面实例查询ID为1的用户,然后修改 name,但是不手动执行 merge 操作。Java 代码如下:
public void demo() throws Exception { EntityManagerFactory managerFactory = null; EntityManager entityManager = null; try { managerFactory = Persistence.createEntityManagerFactory(PERSISTENCE_NAME, System.getProperties()); entityManager = managerFactory.createEntityManager(); entityManager.getTransaction().begin(); // 获取一个 User 对象,然后修改 name,但是不手动执行 merge 操作 User user = entityManager.find(User.class, 1); // ① user.setName("update-" + user.getName()); //entityManager.merge(user); entityManager.getTransaction().commit(); } finally { if (null != entityManager) { entityManager.close(); } if (null != managerFactory) { managerFactory.close(); } } }
执行结果:
2322 openJPA TRACE [main] openjpa.jdbc.SQL - <t 2012744708, conn 31114735> executing prepstmnt 1312381159 SELECT t0.age, t0.birthday, t0.name, t0.salary FROM User t0 WHERE t0.id = ? [params=?] 2372 openJPA TRACE [main] openjpa.jdbc.SQL - <t 2012744708, conn 31114735> executing prepstmnt 1706292388 UPDATE User SET name = ? WHERE id = ? [params=?, ?]
上面实例中,在 ① 位置处,我们修改了查询出来处于持久状态的 User 对象的 name 属性的值。我们并没有调用 merge 方法去更新 User 对象,为什么会发送 update 语句呢?
首先,将数据从数据库中查询出来后,在内存中会有两份数据:
(1)在 EntityManager 一级缓存区域
(2)在 EntityManager 的快照区
上面两份数据完全一样。
然后,修改 User 的 name 属性时,其实是修改的缓存区的数据
最后,在提交事务的时候,会清理一级缓存;此时会对比两份数据是否一致?如果不一致,发送对应的 update 语句将缓存中的脏数据(和数据库中的数据不一致)同步到数据库中。
所以,在上面的例子中,我们看到执行了一条更新语句,这样相信大家就能够理解了,这也是在我们了解了对象的状态之后对 SQL 的发送有了更深入的认识。