Session概述
- Session提供了基本的保存,更新,删除和加载Java对象的方法。
- Session具有一个缓存,位于缓存中的对象称为持久化对象,它和数据库中的相关记录对应。
- 站在持久化的角度,Hibernate把对象分为4种状态,持久化状态,临时状态,游离状态,删除状态。Session的特定方法能使对象从一个状态转换到另一个状态。
Session缓存
- 在Session接口的实现中包含一系列的Java集合,这些Java集合构成了Session缓存,只要Session实例没有结束生命周期,且没有清理缓存,则存放在它缓存中的对象也不会结束生命周期。
- Session缓存可减少Hibernate应用程序访问数据库的频率。
假设有代码如下:
News news = session.get(News.class,1);
System.out.println(news);
News news1 = session.get(News.class,1);
System.out.println(news1);
上述代码会输出news对象和news1对象,但是只会打印一次SQL语句。
第一个News对象会去Session缓存中查找有没有要查找对象的引用,没有就去查数据库。查完数据库就把该对象的引用存入Session缓存,然后第二个News对象去Session查的时候,发现有待查找对象的引用,即不用去数据库中查询了。
操作Session缓存的三个方法
-
flush()
:下面细说。 -
refresh()
:会强制发送SELECT语句
,以使Session缓存中对象的状态和数据表中对应的记录保持一致。
*clear()
:清理Session缓存。
flush缓存
flush
:使数据表中的记录和Session缓存中的对象的状态保持一致。为了保持一致,则可能会发送对应的SQL语句。
在Transaction
的commit()
方法中,先调用session
的flush
方法,再提交事务。flush()
方法可能会发送SQL语句,不会提交事务。只有等事务提交后。- 在未提交事务或显式的调用
session.flush()
方法之前,也有可能进行flush()
操作的情况如下:
执行HQL或QBC查询,会先进行flush
操作,以得到数据表的最新的记录。 - flush操作的例外情况
若记录的ID是由底层数据库使用自增的方式生成的,则在调用save()
方法时,就会立即发送INSERT语句,因为save()
后,必须保证对象的ID是存在的。 commit()
和flush()
的区别flush()
执行一系列sql语句,但不会提交事务;而commit()
先调用flush()
,然后再提交事务。所以提交事务意味着对数据库操作永久保存下来。
持久化对象的状态
- 临时对象
在使用代理主键的情况下,OID通常为null
不处于Session的缓存中
在数据库中没有对应的记录 - 持久化对象(也叫“托管”)
OID不为null
位于Session缓存中
若在数据库中已经有和其对应的记录,持久化对象和数据库中的相关记录对应
Session在flush缓存时,会根据持久化对象的属性变化,来同步更新数据库
在同一个Session实例的缓存中,数据库表中的每一条记录只对应唯一的持久化对象 - 删除对象
在数据库中没有和其OID对应的记录
不再处于Session缓存中
一般情况下,应用程序不该再使用被删除的对象 - 游离对象(也叫“脱管”)
OID不为null
不再处于Session缓存中
一般情况下,游离对象是由持久化对象转变过来的,因此在数据库中可能还存在与它对应的记录
对象的转换图
四种对象之间转换图
Session中save()方法
- 使一个临时对象变为持久化对象
- 为对象分配id
- 在flush缓存时会发送一条INSERT语句。
- 在
save()
之前设置的id是无效的,而在save()
之后的持久化对象的id是不能被修改的。
Session中persist()方法
- 和
save()
一样也会执行INSERT - 在调用persist()之前,如果对象已经有id了,则不会执行INSERT,而抛出异常
Session中的get()和load()
- 执行
get()
会立即加载对象,也可以说是立即检索;而执行load()
时,若不使用该对象则不会立即执行查询操作,而返回一个代理对象,也可以说是延迟检索。 load()
可能会抛出LazyInitializationException
异常:在需要初始化代理对象之前已经关闭了Session。- 若数据表中没有对应的记录,
get()
返回null
,load()
若不使用该对象的任何属性就不会抛出异常,如果需要使用该对象就会抛出异常。
Session中的update()方法
- 若更新一个持久化对象,不需要显式地调用
update()
,因为在调用Transaction
的commit()
时,会先执行session
的flush()
- 若更新一个游离对象,则需要显式地调用
update()
,可以把一个游离对象变为一个持久化对象。 - 对于游离对象,无论要更新的游离对象和数据表的记录是否一致,都会发送UPDATE语句;对于持久化对象,如果要更新的和数据表的记录一致,则不发送UPDATE语句,如果不一致,就发送UPDATE语句。
在.hbm.xml
文件中的class
节点设置select-before-update=true
就不会使update()
不再盲目的触发UPDATE语句,但是通常不需要设置该属性。
<hibernate-mapping>
<!--加入select-before-update="true" -->
<class name="com.cerr.hibernate.helloworld.News" table="news" schema="hibernate5" select-before-update="true">
<id name="id" column="id"/>
<property name="title" column="title"/>
<property name="author" column="author"/>
<property name="date" column="date"/>
</class>
</hibernate-mapping>
- 若数据表中没有对应的记录,但还调用了
update()
,就会抛出异常 - 当
update()
关联一个游离对象时,如果在Session的缓存中已经存在相同OID的持久化对象,会抛出异常。因为在Session缓存中不能有两个OID相同的对象。
Session中的saveOrUpdate()
- Session的saveOrUpdate()同时包含了save()与update()的功能
- 判定对象为临时对象的标准
Java对象的OID为null
映射文件中为<id>设置了unsaved-value
属性,并且java对象的OID取值与这个unsaved-value
属性值匹配。 - 注意
若OID不为null,但数据表中还没有和其对应的记录,则会抛出一个异常
Session中的delete()
- 只要OID和数据表中一条记录对应,就会准备执行
delete()
,若OID在数据表中没有对应的记录,则抛出异常 - 可以通过设置hibernate配置文件
use_identifier_rollback
为true
,使删除对象后,把其OID置为null。
Session中的evict()
从Session缓存中把指定的持久化对象移除。
示例:
package com.cerr.hibernate.helloworld;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.Date;
public class NewsTestTest {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() throws Exception {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destory() throws Exception {
transaction.commit();
session.close();
sessionFactory.close();
}
@Test
public void testEvict(){
News news = session.get(News.class,1);
News news1 = session.get(News.class,2);
session.evict(news);
news.setAuthor("aa"); //不会修改
news1.setAuthor("bb"); //会修改
}
Hibernate与触发器协同工作产生的问题
- 问题1:触发器使Session的缓存中的持久化对象与数据库中对应的数据不一致,触发器运行在数据库中,他执行的操作对Session是透明的。
解决:在执行完Session的相关操作后,立即调用Session的flush()和refresh(),迫使Session的缓存与数据库同步。 - 问题2:Session的
update()
盲目地激发触发器,无论游离对象的属性是否发生变化,都会执行update语句
,而update语句
会激发数据库中相应的触发器。
解决:在映射文件的<class>
元素中设置select-before-update
属性,当Session的update()
或saveOrUpdate()
更新一个游离对象时,会先执行SELECT语句
,获得当前游离对象在数据库中的最新数据,只有在不一致的情况下才会执行UPDATE语句
。