今天来讲讲 Hibernate 的灵魂所在——> Session 缓存
session缓存是由一系列的Java集合构成的。当一个对象被加入到Session缓存中,这个对象的引用就加入到了java的集合中,以后即使应用程序中的引用变量不再引用该对象,只要Session缓存不被清空,这个对象一直处于生命周期中。
Session缓存的作用:
1)减少访问数据库的频率。
2)保证缓存中的对象与数据库中的相关记录保持同步。
Session清理缓存的时机:
1)当调用Transaction的commit()方法时,commit()方法先清理缓存(前提是FlushMode.COMMIT/AUTO),然后再向数据库提交事务。
2)当应用程序调用Session的createQuery().list()或者createQuery().iterate()时,如果缓存中的持久化对象的属性发生了变化,就会先清理缓存,以保证查询结果能反映持久化对象的最新状态。
hibernate2中Session.find()对应于3中的session.createQuery().list()
hibernate2中Session.iterate()对应于3中的session.createQuery().iterate()
3)当应用程序显示调用Session的flush()方法的时候。
Session的setFlushMode()方法用于设定清理缓存的时间点。FlushMode类定义了三种不同的清理模式:FlushMode.AUTO、FlushMode.COMMIT和FlushMode.NEVER。
FlushMode.AUTO是默认值:
session.setFlushMode(FlushMode.COMMIT);
Session清理模式执行清理缓存操作的时间点:
假设现在想把两个客户信息插入到数据库中:
Session session = HibernateSessionFactory.getSession();
Transaction transaction = session.beginTransaction();
Customer customer1 = new Customer(null, "张三", "78784854", "南昌");
Customer customer2 = new Customer(null, "李四", "45646463", "北京");
session.save(customer1);
session.save(customer2);
transaction.commit();
首先创建两个客户对象,这时会在栈区有两个引用指向堆区的两个客户对象即
Customer customer1 = new Customer(null, "张三", "78784854", "南昌");
Customer customer2 = new Customer(null, "李四", "45646463", "北京");
然后执行save()方法,会在session缓存即session对象中 创建两个引用指向堆区的这两个客户对象。并且会生成两条 insert (插入)语句保留在session中。
最后 等执行到 commit() 的时候就将事务提交。 这时因为 session 的清理缓存的时机默认为 FlushMode.AUTO ,所以当 commit() 的时候会先调用 session.flush()—>是将刚刚保留的 insert 语句发送到 数据库中。等 commit() 完之后就将 insert 语句的数据永久保存在数据库中了。
在刚才的基础上,现在来看看这段代码:
Transaction transaction = session.beginTransaction();
Customer customer = (Customer) session.load(Customer.class, 1L);
customer.setName("流川枫");
transaction.commit();
这段代码是要将 id 为 1 的用户的用户名更新为“流川枫”。很奇怪这段代码并没有调用 update()方法,那能更新到吗?答案是肯定的。现在我们来看看内存图:
来分析下:
首先执行 load()方法,会将数据库的对应数据加载到内存中即地址B01所对应的内容。这时session 和 栈区都指向 地址B01。
因为bo1是持久态对象,而持久态对象发生变化时,session会监听到这种变化,并且产生出对应sql保证在事务提交之后让内存中的对象和数据库中的数据保持一致。执行到 coustomer.setName() 方法时,持久态对象发生变化,随即 session 产生一条 update 语句。当 commit 之后就将update 语句发送到数据库并更新。
所以这个对象并不需要调用 update()方法去更新也会更新。
那么我们就得来了解下什么是持久态对象了。
在 Hibernate 中判断对象的状态,可以从以下两个方面去判断:
a.对象与 session 之间的关系
b.对象与数据库数据之间的关系
Hibernate对象的状态:
1)瞬时态Transient
由new操作符创建,且尚未与Hibernate Session关联的对象。处于瞬时态的java对象成为临时对象。
特点:
不处于Session的缓存中,即不被任何一个Session实例关联。
在数据库中没有对应的记录。
2)持久态Persistent
已经被持久化,加入到Session的缓存中,处于持久化状态的java对象被称为持久化对象。
特点:
位于一个Session实例的缓存中。
持久化对象在数据库中有相应的记录
Session在清理缓存时,会根据持久化对象的属性变化来同步更新数据库。
当一个持久化对象关联一个临时对象,在允许级联保存的情况下,Session在清理缓存的时候会把这个临时对象也转变为持久化对象。
3)脱管态Detached
已经被持久化,但不再处于Session的缓存中,处于脱管状态的java对象称为游离对象。
比如在刚才的基础上,添加如下代码:
Customer customer = new Customer(1L, "", "", "");
因为 id 为 1 的用户存在,但又和 session 没有关联,所以是游离对象。
特点:
不再位于Session的缓存中,即不被Session关联。
游离对象是由持久化对象转变过来的,因此在数据库存在与之对应的记录(前提是没有其他程序删除了这条记录)
临时对象VS游离对象
相同:都不被Session关联,Hibernate不会保证它们的属性变化与数据库保持同步。
不同:前者在数据库中没有与之对应的记录。后者由持久化对象转变而来,因此数据库中可能还存在与之对应的记录。
Hibernate 对象状态转换图
脱管状态
session中不维护脱管对象
数据库中有脱管对象对应的数据
save(obj)
obj 瞬态
将瞬态转化为持久态对象
obj 脱管
重新保存一个新的对象
obj 持久态
没意义
update(obj)
obj 脱管
将脱管态转换为持久态
saveOrUpdate(obj)
如果obj是瞬态对象,保存
如果obj是脱管对象,更新
delete(obj)
将持久态删除
Session API :
Session接口是Hibernate向应用程序提供的操纵数据库的最主要的接口,它提供了基本的保存,更新,删除和查询的方法。
save(): 把一个临时对象加入到缓存中,使它变成持久化对象
-->选用映射文件指定的主键生成器为持久化对象分配唯一的OID
-->计划一条insert语句,把参数对象当前的属性值组装到insert语句中,但是save()方法并不立即执行SQL insert语句,只有当Session清理缓存时候才会执行。
-->如果在save()方法之后,又修改了持久化对象的属性,会使得Session在清理缓存的时候额外执行SQL update语句。
注意:save()方法是用来持久化一个临时对象的!
如果将一个持久化对象传给save()方法将不会执行任何操作,多余的步骤
如果将一个游离态对象传给save()方法,session会将它当作临时对象来处理,再次向数据库中插入一条记录,不符合业务需求!
update():把Customer对象重新加入到Session缓存中,使之变为持久化对象。
--->计划一条update语句,只有在清理缓存的时候才会执行,并且在执行的时候才会把参数对象中的属性值组装到update语句中。
注意:update()是将一个游离对象转变为持久化对象的。
只要通过update()方法使游离对象被一个session关联,即使没有修改参数对象的任何属性,Session在清理缓存的时候也会执行由update方法计划的Update语句。
saveOrUpdate():同时包含了save()与update()方法的功能,如果传入的参数是临时对象,调用save方法,如果参入参数是游离对象,调用update()方法,如果传入的是持久化对象,直接返回。
load()/get(): 都会根据给定的OID从数据库中加载一个持久化对象,区别在于,当数据库中不存在与OID对应的记录时,load()方法会抛出ObjectNotFoundException异常,而get()方法返回null.
delete():用于从数据库中删除与参数对象对应的记录,如果传入的参数是持久化对象,Session就计划执行一个delete语句,如果传入的参数是游离对象,先使游离对象被Session关联,使它变为持久化对象,然后计划一个delete语句,在清理缓存的时候执行。
evict():从缓存中清除参数指定的持久化对象。
适用场合:不希望Session继续按照该对象的状态改变来同步更新数据库。
在批量更新或批量删除的场合,当更新或者删除一个对象后,及时释放该对象占用的内存。当然批量操作优先考虑JDBC.
clear():清空缓存中所有持久化对象。
get VS load
1.get方法,hibernate会确认一下该id对应的数据是否存在,首先在session缓存中查找,然后在二级缓存中查找,还没有就查数据库。默认延迟加载,查询当前对象的时候暂时先不查询与之关联的对象。
load方法,为延迟加载即先将查询语句放入缓存中,待到用时再将缓存中的查询语句发送到数据库进行查询。(lazy属性对它不影响)
2.get方法检索不到的话会返回null
load方式检索不到的话会抛出org.hibernate.ObjectNotFoundException异常
注意:
Java代码
Users user = (Users)session.load(Users.class, userId);
System.out.println(user.getId());
上面这2句代码,不会去执行数据库操作。因为load后会在hibernate的一级缓存里存放一个map对象,该map的key就是userId的值,但是当你getId()时,它会去一级缓存里拿map的key值,而不去执行数据库查询。所以不会报任何错。不会执行任何数据库操作。
补充点:
cascade
级联操作,一般谁来维护关系,就谁来级联。如果主表来级联,而从表来维护关系,这时保存的时候就会报错。
保存或更新(save-update)
customer
<set name="orders" cascade="save-update"/>
这表示customer级联order
维护关系
customer 维护 order
customer.getOrders().add(o1);
customer.getOrders().add(o2);
customer.getOrders().add(o3);
session.save(customer);
insert into tbl_customer values();
if(级联){
os = customer.getOrders();
for(Order o : os){
save(o);
}
}
删除(delete)
删除保存更新(all)
解除关系即删除(delete-orphan):必须双方接触关系,删除的是从表的数据
Customer c = (Customer) session.get(Customer.class, 4L);
Order order = (Order) session.get(Order.class, 8L);
//接触关系
c.getOrders().remove(order);
order.setCustomer(null);
删除保存更新,解除关系即删除(all-delete-orphan)
lazy
默认true 延迟加载,查询当前对象的时候暂时先不查询与之关联的对象
false 立即加载,查询当前对象的时候查询所有与之关联的对象。
inverse(维护关系的权利的反转)
一般谁来维护关系谁就来产生外键,不能权利颠倒。如果主表维护从表,而主表的维护关系的权利反转了(即在主表中 inverse="true"),则插入数据的时候不会产生外键即从表中的外键为null。一般1:n关系由多的一方来维护即可。
一般写在set中
默认false 当前方可以维护关系(产生外键)
true 当前方不维护关系(不产生外键)