持久化后修改属性,会发生什么
@Test
public void demo3(){
UserInfo userInfo = new UserInfo();
userInfo.user_name = "demo123";
userInfo.user_sex = 1;
userInfo.user_age = 18;
UserPassword userPassword = new UserPassword();
userPassword.password = "demo-password";
userInfo.userPassword = userPassword;
userPassword.UserPassword_userInfo = userInfo;
BaseEntityUtils.save(userInfo);
System.out.println(userInfo.user_name);
userInfo.user_name = "demo1234";
userPassword.password = "demo1234-password";
UserInfo new1 = BaseEntityUtils.findById(userInfo.id,UserInfo.class);
System.out.println(new1.user_name);
}
在提供的代码中,在执行 BaseEntityUtils.saveAndFlush(userInfo) 之后,userInfo 和 userPassword 对象处于持久态(Persistent State)。
userInfo 对象的状态
- 由于使用了 BaseEntityUtils.saveAndFlush(userInfo) 方法,userInfo 对象被保存到数据库,并且通过 flush 操作将其状态同步到数据库中。
- 因此,userInfo 对象现在处于持久态,它受到 Hibernate Session 的管理。
userPassword 对象的状态
- userPassword 对象与 userInfo 之间存在一对一的关联关系,由于级联关系的存在,userPassword 对象也被保存到数据库中。
- userPassword 对象的状态也是持久态,它同样受到 Hibernate Session 的管理。
在持久态下,任何对 userInfo 和 userPassword 对象的属性的修改都将被监测到,并在适当的时候同步到数据库中。在测试方法的最后,我们对 userPassword 的 password 属性和 userInfo 的 user_name 属性进行了修改。这些修改将在 Hibernate Session 中被跟踪,但由于在测试方法中并没有进行事务的提交或刷新,这些变化可能尚未同步到数据库。如果我们想要确保变化及时更新到数据库中,我们可以在测试方法的最后添加 BaseEntityUtils.flush() 或者使用 Spring 的 @Transactional 注解,以确保事务的正确提交和刷新。这样,持久态下的对象变化将会及时同步到数据库。
什么时候会刷新到数据库
在 Hibernate 中,对于持久态的实体对象,属性的修改会被监测到,并且会在以下情况下被同步到数据库
事务提交时
当事务被提交时,Hibernate 会检查事务中所有持久态对象的变化,并将这些变化同步到数据库。这是最常见的触发时机。
显式调用 flush() 方法时
我们可以显式调用 Hibernate Session 的 flush() 方法,强制将所有挂起的 SQL 语句发送到数据库。这样可以在事务未提交的情况下将变化同步到数据库。
在查询时自动执行 flush
Hibernate 在执行一些查询操作(例如执行查询语句之前)时,会自动执行 flush 操作,以确保最新的数据被加载。这种情况通常涉及到缓存和查询的一致性。所以,当我们在持久态的对象上进行属性的修改后,如果我们正在一个事务中,通常会在事务提交时或在显式调用 flush() 方法时将变化同步到数据库。在我们提供的测试方法中,如果没有显式调用 BaseEntityUtils.flush(),并且也没有使用 Spring 的事务管理(例如,使用 @Transactional 注解),那么在测试方法执行完毕时,可能并没有执行事务提交和 flush 操作,因此对 userInfo 和 userPassword 的属性修改可能尚未同步到数据库。在实际应用中,确保在需要同步变化时执行事务提交或显式调用 flush 方法。
持久态的前提是什么
需要在同一个EntityManager中才能被自动更新到数据库中。
持久态的前提是实体对象必须处于同一个持久化上下文(Persistence Context)中,而持久化上下文通常对应于一个 EntityManager。持久态实体对象是通过 EntityManager 进行管理和跟踪的。具体来说,以下是实体对象变为持久态的前提条件
通过 EntityManager 进行持久化操作
持久态的实体对象通常是通过 EntityManager 的 persist、merge、find 等方法从数据库中加载或保存得到的。在这些操作中,实体对象会被添加到 EntityManager 的持久化上下文中,从而成为持久态。
EntityManager entityManager = // 获取 EntityManager 的方式
UserInfo userInfo = new UserInfo();
entityManager.persist(userInfo); // 将实体对象变为持久态
通过查询操作获取的实体对象
当通过查询操作从数据库中获取实体对象时,这些对象也会成为持久态。例如,通过 find 方法获取对象
EntityManager entityManager = // 获取 EntityManager 的方式
UserInfo userInfo = entityManager.find(UserInfo.class, 1L); // 获取持久态对象
在这种情况下,userInfo 对象会被添加到 EntityManager 的持久化上下文中。
级联关系的影响
如果实体对象之间存在级联关系,并且级联操作的范围包括持久化操作,那么在级联操作中涉及到的对象也会变为持久态。
UserInfo userInfo = new UserInfo();
UserPassword userPassword = new UserPassword();
userInfo.setUserPassword(userPassword); // 设置级联关系
在这个例子中,如果级联关系包括 CascadeType.PERSIST,那么当 userInfo 被保存时,userPassword 也会成为持久态。总体而言,持久态的前提是实体对象必须由 EntityManager 进行管理,且这些实体对象需要处于同一个持久化上下文中。这确保了对实体对象的任何修改都能被 EntityManager 跟踪并在适当的时候同步到数据库。
问题分析
@Test
public void demo3(){
UserInfo userInfo = new UserInfo();
userInfo.user_name = "demo123";
userInfo.user_sex = 1;
userInfo.user_age = 18;
UserPassword userPassword = new UserPassword();
userPassword.password = "demo-password";
userInfo.userPassword = userPassword;
userPassword.UserPassword_userInfo = userInfo;
BaseEntityUtils.save(userInfo);
System.out.println(userInfo.user_name);
userInfo.user_name = "demo1234";
userPassword.password = "demo1234-password";
UserInfo new1 = BaseEntityUtils.findById(userInfo.id,UserInfo.class);
System.out.println(new1.user_name);
}
Alt text
在上面的代码中,由于使用的是静态方法去save,所以查询方法和Save方法并不是使用的一个entityManager,所以此时这两个对象的修改没有被同步到数据库中,两次打印都是demo123。
在我们提供的代码中,确实存在使用不同的 EntityManager 的情况,导致了持久态对象的状态在不同的 EntityManager 中不同步。具体来说,在 BaseEntityUtils.save(userInfo) 方法中使用了一个 EntityManager 对 userInfo 进行了保存,而在查询方法 BaseEntityUtils.findById(userInfo.id, UserInfo.class) 中使用了另一个 EntityManager 进行查询。由于每个 EntityManager 管理着自己的持久化上下文,所以在不同的 EntityManager 中,对相同的实体对象进行的修改不会同步到数据库。这就解释了为什么在查询方法中打印的 new1.user_name 仍然是 "demo123"。要解决这个问题,确保在同一个 EntityManager 中进行保存和查询操作,以保持持久态对象在相同的上下文中。
改进方案
@Test
public void demo4(){
UserInfo userInfo = new UserInfo();
userInfo.user_name = "demo123";
userInfo.user_sex = 1;
userInfo.user_age = 18;
UserPassword userPassword = new UserPassword();
userPassword.password = "demo-password";
userInfo.userPassword = userPassword;
userPassword.UserPassword_userInfo = userInfo;
UserInfoDomain userInfoDomain = new UserInfoDomain();
userInfoDomain.setDomainEntity(userInfo);
userInfoDomain.save();
System.out.println(userInfo.user_name);
userInfo.user_name = "demo1234";
userPassword.password = "demo1234-password";
userInfoDomain.flush();
UserInfo new1 = userInfoDomain.findById(userInfo.id);
System.out.println(new1.user_name);
}
运行结果为:
Alt text
在UserInfoDomain中,所有的操作都在一个EntityManager里面,所以这两次打印分别是demo123和demo1234。
在我们提供的代码中,UserInfoDomain 类的设计确保了所有的操作都在同一个 EntityManager 中,这是非常好的实践。由于 UserInfoDomain 中的 save() 方法内部使用的是同一个 EntityManager 进行保存操作,保证了持久态对象在相同的上下文中。以下是对我们的代码的解释
保存操作
UserInfoDomain userInfoDomain = new UserInfoDomain();
userInfoDomain.setDomainEntity(userInfo);
userInfoDomain.save();
在这里,userInfoDomain.save() 方法内部使用了相同的 EntityManager 进行保存操作。因此,userInfo 对象及其关联的 userPassword 对象都处于持久态,保存到数据库。
第一次打印
System.out.println(userInfo.user_name);
这里打印的是 userInfo 对象的 user_name 属性,即 "demo123"。由于保存操作是在同一个 EntityManager 中执行的,所以在持久态下的 userInfo 对象的属性是最新的。
修改操作
userInfo.user_name = "demo1234";
userPassword.password = "demo1234-password";
这里对 userInfo 对象进行了属性的修改。
查询操作
UserInfo new1 = userInfoDomain.findById(userInfo.id);
在这里,findById 方法内部也是使用了相同的 EntityManager 进行查询操作。因此,获取的 new1 对象是 userInfo 对象在数据库中的最新状态。
第二次打印
System.out.println(new##### user_name);
这里打印的是经过查询操作后的 new1 对象的 user_name 属性,即 "demo1234"。由于查询操作也是在相同的 EntityManager 中执行的,所以能够获取到最新的数据库状态。总体来说,通过确保在同一个 EntityManager 中进行保存和查询操作,我们有效地维持了一致的持久化上下文,确保对象的状态能够正确同步到数据库。这是良好的实践,可以有效避免对象状态不同步的问题。