一、概述
Session接口是Hibernate向应用程序提供的操纵数据库最主要的接口,它提供了基本的保存、更新、删除和加载Java对象的方法。
Session具有一个缓存,位于缓存中的对象称为持久化对象,它和数据库相关记录对应。
Session能够在某些时间点按照缓存中对象的变化来执行相关SQL语句,来同步更新数据库,这一过程称为清理缓存(flush)。
二、Session的缓存
1.理解
如果希望一个Java对象A一直处于生命周期中,就必须保证至少有一个变量引用它,或者可以从其他处于生命周期中的对象B导航到这个对象A,比如在对象B的Java集合属性存放对象A的引用。在Session接口的实现中包含了一系列的Java集合,这些Java集合构成了Session的缓存。只要Session实例没有结束生命周期,存放在它缓存中的对象也不会结束生命周期。
当Session的save()方法持久化一个Customer对象时,Customer对象被加入到Session缓存中,以后即使应用程序中的引用变量不再引用Customer对象,只要Session的缓存没有被清空,Customer对象仍然处于生命周期中。
当Session的get()方法试图从数据库中加载一个Customer对象时,Session先判断缓存中是否已经存在这个Customer对象,如果存在就不需要再到数据库中检索,而直接从缓存中获取这个Customer对象。
@Test
public void testSessionCache(){
Session session = HibernateUtils.getSession();
//结果是查询了一次数据库,第二次没有查询数据库
News news1 = (News)session.get(News.class,1);
System.out.println(news1);
News news12 = (News)session.get(News.class,1);
System.out.println(news1);
}
2.Session缓存的作用
(1)减少访问数据库的频率
应用程序从缓存中读取持久化对象的速度显然要比到数据库中查询的速度要快的多,因此Session的缓存可以提高数据访问的性能。
//第1次执行Session的get()方法
News n1 = (News)session.get(News.class,1);
//第2次执行Session的get()方法
News n2 = (News)session.get(News.class,1);
System.out.println(n1 == n2); //true
下图显示第一次执行Session的的get()方法
下图显示了第二次执行Session的get()方法
(2)当缓存中的持久化对象之间存在循环关联关系时,Session会保证不出现访问对象图的死循环,以及死循环引起的JVM堆栈溢出异常。
(3)保证数据库中的相关记录与缓存中的相应对象保持同步。
如下图对象-关系映射建立了表与类之间的静态映射,而Session建立了表与Session缓存中的对象的动态映射。
以下程序先把Customer对象加载到Session缓存中,然后修改Session缓存中Customer对象的属性。
// 开启事务
tx = session.beginTransaction(); Customer c = (Customer)session.get(Customer.class,1);
c.setName("Jack"); // 提交事务
tx.commit();
执行完后,Session中的Customer对象的name属性与Customer表对应记录的name字段值不一致了。
幸运的是,Session在清理缓存时会自动进行脏检查(dirty-check),如果发现Session缓存中的对象与数据库中相应记录不一致,就会根据对象的最新属性去同步更新数据库,在本例中,Session在提交事务之前执行flush()方法清理缓存,即向数据库提交一条update语句,更新数据库中的name字段的值。
3.缓存清理机制
(1)Session如何进行脏检查?
当一个Customer对象被加入到Session缓存中时,Session会为Customer对象的值类型的属性复制一份快照。当Session清理缓存时,会先进行脏检查,即比较Customer对象的当前属性与它的快照来判断Customer对象的属性是否发生了变化,如果发生了变化,就称这个对象为“脏对象”,Session会根据脏对象的最新属性来执行相关SQL语句,从而更新到数据库。
当Session缓存对象的属性每次发生了变化,Session并不会立即执行清理缓存及执行相关SQL语句,而是在特定的时间点才清理缓存,这使得Session能够把几条相关SQL语句合并为一条SQL语句,以便减少访问数据库的次数,从而提高应用程序的数据访问性能。
tx = session.beginTransaction(); Customer c = (Customer)session.get(Customer.class,1);
//当Sesion清理缓存时,不必执行两条update语句,而是执行最后一句
c.setName("Jack");
c.setName("Mary"); tx.commit();
(2)Session清理缓存的时间点
A: 应用程序显式调用Session的flush()方法。
B: 应用程序调用commit()方法时,在内部先执行flush()方法,再向数据库提交事务。
C: 当应用程序执行一些查询(HQL/Criteria)操作时,如果缓存中的持久化对象已经发生了变化,就会先flush,以保证查询结果为最新的状态。
(3)Commit()与flush()方法的区别
flush 执行SQL语句,但不提交事务。
commit 方法先调用flush() 方法,然后提交事务. 意味着提交事务意味着对数据库操作永久保存下来。
三、Hibernate的的三种状态
1.概述
Hibernate有以下三种状态:
临时状态(transient): 刚用new语句创建,还没有被持久化,并且不处于Session的缓存中。
持久化状态(persistent): 已经被持久化,并且处于Session缓存中。
游离状态(detached): 已经被持久化,但不再处于Session缓存中。
2.临时对象的特征
(1)使用代理主键的情况下,OID通常为null。
(2)不在Session的缓存中。
(3)数据库没有相应的记录。
3.持久化对象的特征
(1)OID不为null。
(2)位于Session的缓存中。
(3)持久化对象和数据库的相关记录对应。
(4)Session在flush()缓存时,会根据持久化对象的属性变化,来同步更新数据库。
4.游离对象的特征
(1)OID不为null。
(2)不再处于Session的缓存中。
(3)一般情况下,游离对象是由持久化对象转变而来的,所以数据库还肯存在与它相对应的记录。
5.对象的状态转换图
四、Session的相关API
1.save()和persist()方法
Session的save()方法完成以下操作:
(1)使一个临时对象变为持久化对象。
(2)为对象分配OID。
(3)在flush时,会发送一条insert语句。
注意:
A: 在执行save()方法之前,设置对象的id是无效的。
B: 持久化对象的id是不能修改的,不然会抛异常。
@Test
public void testSave(){ Session session = HibernateUtils.getSession();
Transaction tx = null; try{
// 开启事务
tx = session.beginTransaction(); News news = new News();
news.setTitle("DDD");
news.setAuthor("Jack");
news.setDate(new Date()); //在save之前设置id是无效的
news.setId(100);
System.out.println(news); session.save(news); System.out.println(news); //持久化对象的id是不能修改的,下面这句话报异常
news.setId(200); // 提交事务
tx.commit();
}catch(RuntimeException e){
// 回滚事务
tx.rollback();
throw e;
}finally{
// 释放资源
session.close();
}
}
persist()方法也会执行insert语句,但是它和save()方法有点区别:
在调用persist()方法之前,若对象有id,则不会执行insert语句,会报异常。
@Test
public void testPersist(){ Session session = HibernateUtils.getSession();
Transaction tx = null; try{
// 开启事务
tx = session.beginTransaction(); News news = new News();
news.setTitle("CCC");
news.setAuthor("C");
news.setDate(new Date()); news.setId(100); System.out.println(news); //这句话会抛异常
session.persist(news); System.out.println(news); // 提交事务
tx.commit();
}catch(RuntimeException e){
// 回滚事务
tx.rollback();
throw e;
}finally{
// 释放资源
session.close();
}
}
2.get()和load()方法
Session的get()和load()方法都能根据给定的OID从数据库加载一个持久化对象。
它们有以下区别:
(1)get()使用的是立即检索策略。
load()使用的是延迟检索策略,即load()方法返回的是只要一个id属性的代理对象,只有真正使用该对象时,才会发生SQL语句。
(2)get()如果查询不存在的记录会返回null。
load()如果查询不存在的记录则会抛异常,ObjectNotFoundException。
3.update()方法
Session的update()方法完成以下操作:
(1)把游离对象变为持久化对象。
(2)无论游离对象是否和表一致,都会发生一条update语句。
News news = new News();
news.setTitle("CCC");
session.save(news);
tx.commit(); //清理缓存,提交事务
session.close(); //此时news对象变为游离对象 Session session2 = HibernateUtils.getSession();
Transaction tx2 = session2.beginTransaction();
news.setTitle("DDD");
session2.update(news);//news对象和session2关联
news.setTitle("EEE");
tx2.commit();
session2.close();
4.saveOrUpdate()方法
功能如下:
(1)如果传入的是游离对象就执行update()方法。
(2)如果传入的是临时对象就执行save()方法。
(3)如果传入的是持久化对象,那就直接返回。
saveOrUpdate()方法如何判断对象是临时对象还是游离对象?如果满足下列情况之一,Hibernate就把它作为临时对象,否则就作为游离对象。
(1)OID为null。
(2)映射文件中为<id>设置了unsaved-value属性, 并且Java对象的OID取值与这个unsaved-value 属性值匹配。
5.delete()方法
(1)只要OID和数据表中一条记录对应 ,就计划执行删除操作。
(2)若OID在数据表中没有对应记录,则抛异常。
(3)可以设置Hibernate配置文件 hibernate.use_identifier_rollback为true,使对象删除后,把其OID置为null。