Session接口是Hibernate向应用程序提供的操纵数据库的最主要接口,其提供有基本的保存、更新、删除和加载Java对象的方法。
Session具有一个缓存,位于缓存中的对象称为持久化对象,其与数据表中相关记录相对应;其中,Session对象能够在某些时间点,按照缓存中对象的变化来执行对应SQL语句,以同步更新数据库,该过程被称为刷新缓存(flush)。
站在持久化的角度来看,Hibernate将对象分为四种状态:持久化状态、临时状态、游离状态和删除状态,Session的特定方法能使对象从一个状态转换到另一个状态。
1. Session缓存
概念:在Session接口的实现中包含一系列的Java集合,这些集合构成Session缓存。
**作用:**Session缓存可减少Hibernate应用程序访问数据库的频率,因为只要Session实例没有结束生命周期且没有清理缓存,则存放在其缓存中的对象也不会消失。
其中,Session缓存的基本原理测试代码如下所示:
// 注意:只会向数据库发送一条SQL语句
News news1 = (News) session.get(News.class, 1);
System.out.println(news1);
News news2 = (News) session.get(News.class, 1);
System.out.println(news2);
System.out.println(news1 == news2); // true
1.1 flush缓存
作用: Session按照缓存中对象的属性变化来同步更新数据库中的表记录。
实现:刷新缓存的时间点为session.flush();
或 transaction.commit()
。
比较: Session对象的flush()方法可能会执行SQL语句,但不提交事务;而Transaction对象的commit()方法先调用flush()方法,再提交事务(将对数据库的操作永久保存)。
设定刷新缓存的时间点:可以调用Session对象的setFlushMode()方法显式设定刷新缓存的时间点,具体模式如下图所示:
注意:在未提交事务或显式调用Session对象flush()方法,也可能会进行缓存刷新操作,具体如下:
- 执行HQL或QBC查询时:若缓存中持久化对象的属性发生变化,则会先刷新缓存,以保证查询结果能够反映持久化对象的最新状态;
- 执行save()方法保存对象时:若对象使用native生成器生成OID,则执行保存操作时,会立即执行刷新操作以保证对象的ID是存在的。
1.2 refresh缓存
作用:强制发送SELECT语句,以使Session缓存中对象的状态和数据表中对应的记录保持一致。
注意:MySQL数据库的默认事务隔离级别为“REPEATABLE READ”,需要手动修改为“READ COMMITED”。
1.3 clear缓存
作用:Session对象的clear()方法可以清除Session缓存中的所有缓存对象。
2. 数据库的隔离级别
数据库的隔离性是指隔离并发运行各个事务的能力,而隔离级别是指一个事务与其他事务的隔离程度;隔离级别越高,数据一致性就越好,但并发性越弱。
2.1 并发问题概述
对于同时运行的多个事务,当这些事务访问数据库中相同数据时,如果没有采取必要的隔离机制,会导致各种并发问题,如下:
- 脏读:对于两个事务T1和T2,T1读取了已经被T2更新但还没有被提交的字段后,若T2回滚,T1读取的内容就是临时且无效的;
- 不可重复读:对于两个事务T1和T2,T1读取一个字段后,T2更新了该字段;之后T1再次读取同一个字段时值发生变化;
- 幻读:对于两个事务T1和T2,T1从一个表中读取了一个字段后,T2在该表中插入一些新的行;之后,如果T1再次读取同一个表,就会多出几行。
2.2 事务隔离级别
数据库提供的四种事务隔离级别,分别是:
其中,Oracle 支持“READ COMMITED”(默认)和“SERIALIZABLE”两种事务隔离级别,而MySQL支持四种事务隔离级别,默认为“READ COMMITED”。
2.2 在MySQL中设置隔离级别
在MySQL数据库中,每启动一个程序就会获得一个单独的数据库连接,每个数据库连接都有一个全局变量@@tx_isolation,表示当前的事务隔离级别。
- 查看当前的隔离级别:
SELECT @@tx_isolation
; - 设置当前MySQL连接的隔离级别:
set transaction isolation level read committed
; - 设置当前MySQL数据库的隔离级别:
set global transaction isolation level read committed
。
2.3 在Hibernate中设置隔离级别
JDBC数据库连接使用数据库系统默认的隔离级别。在Hibernate中可通过配置其hibernate.connection.isolation属性的方式来设置事务的隔离级别,具体说明如下:
<!-- 1). 配置数据库的事务隔离级别为:READ UNCOMMITED -->
<property name="hibernate.connection.isolation">1</property>
<!-- 2). 配置数据库的事务隔离级别为:READ COMMITED -->
<property name="hibernate.connection.isolation">2</property>
<!-- 3). 配置数据库的事务隔离级别为:REPEATABLE READ -->
<property name="hibernate.connection.isolation">4</property>
<!-- 4). 配置数据库的事务隔离级别为:SERIALIZEABLE -->
<property name="hibernate.connection.isolation">8</property>
3. 持久化对象的状态
站在持久化的角度,Hibernate把对象分为四种状态:持久化状态、临时状态、游离状态、删除状态;Session的特定方法能使对象从一个状态转换到另一个状态。
3.1 临时对象(Transient)
- 在使用代理主键的情况下,OID通常为null;
- 不处于Session缓存中;
- 在数据库中没有对应的记录。
3.2 持久化对象(Persist)
- OID不为null;
- 位于Session缓存中;
- 若在数据库中已经有与其对应的记录,持久化对象和数据库中的相关记录对应;
- Session 在flush缓存时,会根据持久化对象的属性变化,来同步更新数据库;
- 在同一个Session实例的缓存中, 数据库表中每条记录只对应唯一的持久化对象。
3.3 删除对象(Removed)
- 在数据库中没有和其OID对应的记录;
- 不再处于Session缓存中;
- 一般情况下,应用程序不该再使用被删除的对象。
3.4 游离对象(Detached)
- OID不为null;
- 不再处于Session缓存中;
- 一般情况下,游离对象是由持久化对象转变而来,在数据库中可能还存在与其对应的记录。
3.5 对象状态转换图
4. Session核心方法
4.1 save()与persist()方法
/**
* Session对象的save()方法:
* 1). 将临时对象加入Session缓存中,使其转变为持久化对象;
* 2). 选用映射文件指定的标识符生成器,为持久化对象分配唯一的OID;
* 3). 在flush缓存时,会发送一条INSERT语句;
* 4). 在使用代理主键的情况下,通过setId()方法为临时对象设置OID是无效的;
* 5). 持久化对象的OID不能被随意修改,因其维持着持久化对象与数据库记录的对应关系。
*
* Session对象的persist()方法:也会执行INSERT操作;
* 与save()方法的不同之处:当临时对象的OID不为空时,该方法将抛出异常。
*/
@Test
public void testSaveAndPersist() {
News news = new News(null, "qiaobc", "qiaobei", new Date());
news.setId(1001); // 为临时对象设置OID是无效的
System.out.println(news);
// session.persist(news);
session.save(news);
System.out.println(news);
// news.setId(1002); // 持久化对象的OID不能修改
}
4.2 get()与load()方法
/**
* Session对象的get()与load()方法:均可根据OID从数据库中加载一个持久化对象
* 1). 执行get()方法时,立即加载对象;
* 而执行load()方法时,延迟加载对象,即若不使用该对象,则不会立即查询,而是返回一个代理对象;
* 2). load()方法可能会抛出LazyInitializationException异常:在需要初始化代理对象前关闭Session对象;
* 3). 若数据表中没有与OID对应的记录,则get()方法返回null;
* 而load()方法,若不使用该对象的任何属性则没问题,若需要初始化则抛出ObjectNotFoundException异常。
*/
@Test
public void testGet() {
News news1 = (News) session.get(News.class, 10);
System.out.println(news1); // 持久化状态
session.close(); // 游离状态
System.out.println(news1);
}
@Test
public void testLoad() {
News news2 = (News) session.load(News.class, 1);
// 若不使用,则返回代理对象com.qiaobc.hibernate.entities.News_$$_javassist_0
System.out.println(news2.getClass().getName());
session.close(); // 游离状态
System.out.println(news2); // 抛出LazyInitializationException异常
}
4.3 update()方法
/**
* Session对象的update()方法:
* 1). 更新持久化对象不需要显式调用update()方法,因为事务提交时会flush缓存;
* 2). 更新游离对象需要显式调用update()方法,将其转变为持久化对象;
*
* 注意:
* 1). 无论要更新的游离对象和数据表的记录是否一致,均会发送UPDATE语句;
* 当Hibernate与触发器协同工作时,可在*.hbm.xml文件的class节点
* 设置select-before-update=true以确保不盲目发送UPDATE语句;
* 2). 当 update()方法关联一个游离对象时,如果在数据库中不存在相应的记录,抛出异常;
* 3). 当 update()方法关联一个游离对象时,如果在Session缓存中已经存在相同OID的持久化对象,抛出异常;
*/
@Test
public void update1() {
News news = (News) session.get(News.class, 1);
news.setAuthor("SUN");
session.update(news); // 若更新持久化对象不需要显式调用update()方法
}
@Test
public void update2() {
News news = (News) session.get(News.class, 1);
transaction.commit();
session.close();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
news.setAuthor("SUN"); // 此时news为游离对象
session.update(news);
}
4.4 saveOrUpdate()方法
/**
* Session对象的saveOrUpdate()方法:临时对象执行保存操作,游离对象执行更新操作。
* 1). 判定对象为临时对象的标准:Java对象的OID是否为null;
* 2). 了解:若Java对象的OID取值等于映射文件中id节点unsaved-value属性值,则其也为临时对象。
*/
@Test
public void testSaveOrUpdate() {
// OID不为空,执行更新操作;若OID对应的记录不存在,则抛出StaleStateException异常。
News news1 = new News(10, "qiaobc1", "qiaobei1", new Date());
session.saveOrUpdate(news1);
// OID为空,执行保存操作
News news2 = new News(null, "qiaobc", "qiaobei", new Date());
session.saveOrUpdate(news2);
}
4.5 merge()方法
4.6 delete()方法
/**
* Session对象的delete()方法:既可以删除一个游离对象,也可以删除一个持久化对象
* 1). 只要OID与数据表中记录对应,即执行删除操作;无对应记录则抛出异常;
* 2). 通过设置hibernate.use_identifier_rollback=true,使删除对象时置OID=null;
*/
@Test
public void testDelete() {
News news = new News();
news.setId(5);
session.delete(news); // 删除游离对象
News news2 = (News) session.get(News.class, 6);
session.delete(news2); // 删除持久化对象
// 配置前:News [id=6, title=qiaobc, author=qiaobei, date=2017-02-20 17:10:35.0]
// 配置后:News [id=null, title=qiaobc, author=qiaobei, date=2017-02-20 17:10:35.0]
System.out.println(news2);
}
4.7 evict()方法
/**
* Session对象的evict()方法:从缓存中将持久化对象移除
*/
@Test
public void testEvict() {
News news1 = (News) session.get(News.class, 1);
News news2 = (News) session.get(News.class, 2);
news1.setAuthor("No.1");
news2.setAuthor("No.2");
session.evict(news2); // 移除news2对象,不再更新该对象
}