0%

Middleware Hibernate

介绍 HIbernate 相关概念。

Hibernate基本介绍

Hibernate的三种状态

Hibernate 在事务执行过程中对象分为三种状态:transient(瞬时态),persistent(持久态)以及 detached(游离态)。它们的区别在于:

  • 瞬时态:事务 session 中存在,但并未保存在 DB 中。
  • 持久态:对象已经被保存到数据库中。
  • 游离态:数据库中存在,但 session 中不存在该对象。

Insert Transient Domain 的情况

Configuration cfg = new Configuration().configure();
SessionFactory sessionFactory = cfg.buildSessionFactory();
Session session = sessionFactory.openSession();

//开启事务
session.getTransaction().begin();
//此时,Domain为"瞬时态",即session中并不托管该对象
Domain domain = Domain.allocate(); 
//此时,Domain被session托管,此时Domain为”持久态“
session.save(domain);
//执行事务,Domain被持久化到DB
session.getTransaction().commit();

对应 SQL 执行

Hibernate: insert into domain_table (property, createAt, updateAt) values (?, ?, ?)

Insert Transient Domain Before Modify 的情况

//开启事务
session.getTransaction().begin();
//此时,Domain为"瞬时态",即session中并不托管该对象
Domain domain = Domain.allocate(); 
//此时,Domain被session托管,此时Domain为"持久态"
session.save(domain);
//此时Domain是持久化状态,已经被session所管理。
//当在commit时,会把session中的对象和目前的对象进行比较。
//如果两个对象中的值不一致就会继续发出相应的sql语句
domain.setProperty("Modify");
//执行事务,Domain被持久化到DB
session.getTransaction().commit();

对应 SQL 执行

Hibernate: insert into domain_table (property, createAt, updateAt) values (?, ?, ?)
Hibernate: update domain_table set property = ?, createAt = ?, updateAt = ? where id = ?

Insert Transient Domain Before Multi-Modify 的情况

//开启事务
session.getTransaction().begin();
//创建"瞬时态"Domain
Domain domain = Domain.allocate(); 
//转为"持久态",设为session托管状态
session.save(domain);
//变更,会在commit后补加update语句
domain.setProperty("Modify");
//无意义的操作
session.save(domain);
//变更,会在commit后补加update语句
domain.setUpdateAt("xxx");
//无意义的操作
session.update(domain);
//会最终判断当前Domain与刚被托管时的属性变化,补加update语句,因此只会补加一条。
session.getTransaction().commit();

对应 SQL 执行

Hibernate: insert into domain_table (property, createAt, updateAt) values (?, ?, ?)
Hibernate: update domain_table set property = ?, createAt = ?, updateAt = ? where id = ?

Insert Persistent Domain Before Modify 的情况

//开启事务
session.getTransaction().begin();
//通过load从DB获取持久化对象,此时Domain本身为"持久态",也同时被session托管。
Domain domain = session.load(Domain.class, 4);
//变更,会在commit后补加update语句
domain.setProperty("Modify");
//会最终判断当前Domain与刚被托管时的属性变化,补加update语句,因此只会补加一条。
session.getTransaction().commit();

对应 SQL 执行

Hibernate: insert into domain_table (property, createAt, updateAt) values (?, ?, ?)
Hibernate: update domain_table set property = ?, createAt = ?, updateAt = ? where id = ?

Insert Detached Domain 的情况

//开启事务
session.getTransaction().begin();
//创建一个DB中有同样ID的Domain,此时Domain称作"游离态",不受session管理
Domain domain = Domain.allocate();domain.setId(4);
//当执行save的时候总是会添加一条数据,此时id就会根据Hibernate所定义的规则来生成
session.save(domain);session.getTransaction().commit();

对应 SQL 执行

Hibernate: insert into domain_table (property, createAt, updateAt) values (?, ?, ?)

所以对于游离对象,如果要使其变成持久化对象的话,我们不能使用 save 方法,而应该使用 update 方法。

Domain domain = Domain.allocate();domain.setId(4);
//变成持久态,受session管理
session.update(domain);
//对需要修改的内容进行set

但是对象一旦变成持久态,其 ID 就不能变化,否则会直接抛出异常。

org.hibernate.HibernateException: identifier of an instance of com.tarot.elijah.Domain was altered from 4 to 333.

Save Or Update Domain 的情况

//开启事务
session.getTransaction().begin();
Domain domain = Domain.allocate();domain.setId(4);
domain.setProperty("Modify");
//根据ID判断,如果瞬时态就执行save,如果是游离态就执行
updatesession.saveOrUpdate(domain);
session.getTransaction().commit();

Delete Transient Domain 的情况

//开启事务
session.getTransaction().begin();
//创建一个DB中有同样ID的Domain,此时Domain称作"游离态",不受session管理
Domain domain = Domain.allocate();
domain.setId(4);
//删除Domain,并将其置为瞬时态,即不再受session和DB管理
session.delete(domain);
//此时domain的变化已经和数据库无关了,hibernate不会发送任何修改语句
domain.setProperty("Modify");
session.getTransaction().commit();

对应 SQL 执行

Hibernate: delete from domain_table where id=?

Mutiple different reference in the column of ID 的情况

//开启事务
session.getTransaction().begin();
//Domain1已经是持久化状态
Domain domain1 = session.load(Domain.class, 4);
//创建一个"游离态"的Domain2并转为"持久态"
Domain domain2 = Domain.allocate();
domain2.setId(4);
session.update(domain2);
//提交事务
session.getTransaction().commit();

这个时候 session 缓存中就存在了同一 ID 的持久化对象有两个引用拷贝了,Hibernate 会报错:

org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.tarot.elijah.Domain#3]

要解决这个问题,我们需要用到 session 的 merge 方法,其作用就是解决一个持久化对象两分拷贝的问题,这个方法会将两个对象合并在一起成为一个对象。

//开启事务
session.getTransaction().begin();
//Domain1已经是持久化状态
Domain domain1 = session.load(Domain.class, 4);
//创建一个"游离态"的Domain并转为"持久态"
Domain domain2 = Domain.allocate();
domain2.setId(4);
//session.update(domain2);
//最佳实践:merge最好不用
//merge方法会判断session中是否已经存在同一个对象,如果存在就将两个对象合并
session.merge(domain2);
//提交事务
session.getTransaction().commit();

Clear Session 的情况

session.getTransaction().begin();
Domain domain = session.load(Domain.class, 4);
domain.setProperty("Modify");//清空
sessionsession.clear();
session.getTransaction().commit();

当 load 出 Domain 对象时,在 session 缓存中存在该对象,此时我们再对 Domain 进行修改后,调用 session.clear() 方法,会将 session 的缓存对象清空。在提交事务时,session 中已经没有该对象了,所以就不会进行任何操作,因此这里只会发送一条 select 语句。