Hibernate 缓存
缓存是关于应用程序性能的优化,降低了应用程序对物理数据源访问的频次,从而提高应用程序的运行性能。缓存对 Hibernate 来说也是重要的,它使用了如下解释的多级缓存方案:
开源大全
简单理解:
首先来解决缓存的几个问题:
- 有一定的方法把数据放入到缓存中
- 有一定的方法把数据取出来
- 如果缓存中的数据有变化,需要把数据同步到数据库中,检查缓存中的一个数据和数据库中的数据一致的依据。
- 把数据集库中的数据再同步到缓存中
- 假设内存很小,你的数据不能无限制的放,隔一段时间,把长期不动的弄出去,hit:命中率越低的对象就应该从缓存中清空。
加深理解一下:假设我们数据的数据的访问量太多,我需要加机器分担高并发压力,分布式的放在几台机器中,也就是几个节点中,但是每台机器中的数据是一样的。
分布式缓存,里面有同步的机制。
一级缓存
第一级缓存是 Session 缓存并且是一种强制性的缓存,是session级别的缓存,当session开启的时候开启,session关闭的时候,一级缓存关闭,则被清空掉。所有的要求都必须通过它。
Session 对象在它自己的权利之下,在将它提交给数据库之前保存一个对象。
如果你对一个对象发出多个更新,Hibernate 会尝试尽可能长地延迟更新来减少发出的 SQL 更新语句的数目。如果你关闭 session,所有缓存的对象丢失,或是存留,或是在数据库中被更新。
源代码的位置
sessionimpl.class--statefulpersistencecontext--Map entitiesBykey
为啥一级缓存是个Map,正确的说法 该方法进去了一级缓存中,如果说一个对象是持久化对象,那么这个对象称为持久化对象。在一级缓存当中的对象叫做持久化对象。
那么问题来了,我怎么证明他是在缓存中还是不在缓存中的呢?
public void testGet(){
SessionFactory sessionFactory = HibernateUtils.sessionFactory;
Session session = sessionFactory.openSession();
Person person = (Person)session.get(Person.class,1L);
Person person2 = (Person)session.get(Person.class, 1L);
//java提供了一种统计机制,可以输出一级缓存中有几个对象
System.out.println(session.getStatistics().getEntityCount());
session.close();
}
发现控制台中只输出了一条sql语句,get()方法可以把对象放入到一级缓存中,也可以从一级缓存中取对象。
/** evit(Object) 剔除单个对象 把所有的对象放到list(持久化标识) 当中 则
clear 释放所有的缓存 自动就有不用配置
*/
/*** save方法*/
@Test
public void testSave(){
SessionFactory sessionFactory = HibernateUtils.sessionFactory;
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Person person = new Person();
person.setName("aaa");
session.save(person);//保存好一个对象以后
System.out.println(session.getStatistics().getEntityCount());
//发现一级缓存中有一个对象
transaction.commit();
session.close();
}
同理update(),save(),都可以把对象放入到一级缓存中.evict()方法可以把一个对象从session的缓存中清空。如果我再update一下,它不会发sql语句,但是可以把对象再次放到一级缓存中,clear()可以清空所有的一级缓存。close()一级缓存的声明周期结束了,啥都没有了。
探讨:放到一级缓存中到底有什么意义呢?
修改班级成绩信息,我在一级缓存中修改,我没有和数据库进行操作,你修改学生的属性值,你爱怎么改怎么改,那么当我执行session.flush()的时候,我才会检查对象状态,才会决定到底发什么请求(insert or update),才会发起sql语句。意义在于,我只和数据库交互一次,所以提高了效率。
内部的实现机制:
loadedstate 就是快照
创建session的方式:
方式一:
源代码:
打开一个session,就是开启了一个数据库连接。
来说思考一个问题:有啥不好!!
举个栗子:你在京东购物,在京东的后台发生了什么事情
- 你可以查看当前每个购物信息
- 你的购物信息保存在京东的后台
- 购买商品的总数 从仓库中剪掉
- 付款的方式 扣钱 京东的价钱
这几件事,必须在一个事务下运行,在一个session下运行,要么都成功,要么都不成功!
仓库是一个系统
付款也是一个系统
session的作用图:
我靠!发现弄成了三个session,这三个session代表三个链接,这就出问题,你怎么保证我在同一个状态下面,会挂掉的!!这就是SessionFactory.openSession的缺点.
那怎么解决这个问题呢?
哎?我这几个session在同一个线程里,最根本的意思,你请求的时候不论你去哪,你都在一个线程里,可以共享对象,我把你们都放在Thread.local中
创建方式二:
源代码的设计:有个Map集合放的是session对象。
- 这样的话可以支持多个数据库链接
- 尽量保证request到resonse只有一个链接最好
- 在Treadlocal中尽量只有一个sessFactory
如果说你想要在线程中共享数据,现在放在Threadlocal是很好的选择,当然还有别的方法。
使用方法
首先第一点:现在xml文件中设置
1. 如果使用该方法如果用该方法产生session,则crud操作必须在事务的环境下运行
2. 当执行transaction.commit的方法的时候,session自动关闭。
说明:
这么做相当于把session与transaction绑定在一起了。
当事务提交的时候,session关闭不好,因为如果事务提交以后,再做关于数据库的操作(例如我再把数据从数据库中取出来),因为没session就不能做了。
Spring后来把这个给改了。
总结一下
- session相当于一个连接,所有的操作都在session中操作
- riud都在session中的作用
- 一个对象变成持久化对象,实际上进到了sessionimpl中Map中,这就是一级缓存
- 一级缓存就是session中的东西,生命周期同步
- 意义:不管你怎么修改,针对的是一级缓存,只有在flush的时候,才会提交,提高了效率
一对多的关系
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.itheima12.hibernate.domain.Classes">
<id name="cid" length="5">
<generator class="increment"></generator>
</id>
<property name="description" length="50"></property>
<property name="name" length="20"></property>
<!--
set元素针对的就是Classes类中的Set属性
cascade 级联操作
null 默认值
save-update
在保存classes对象的时候,针对student进行保存或者更新的操作
在更新classes对象的时候,针对student进行保存或者更新的操作
all
delete
inverse 关系操作
default:classes维护classes与student之间的关系
true: classes不维护classes与student之间的关系
false: classes维护classes与student之间的关系
-->
<set name="students" cascade="save-update" inverse="true">
<!--
外键
告诉hibernate,通过cid就可以建立classes与student之间的关联
-->
<key>
<column name="cid"></column>
</key>
<!--
告诉hibernate,Classes类中的set集合中存放的是哪个元素
-->
<one-to-many class="com.itheima12.hibernate.domain.Student"/>
</set>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.itheima12.hibernate.domain.Student">
<id name="sid" length="5">
<generator class="increment"></generator>
</id>
<property name="description" length="50"></property>
<property name="name" length="20"></property>
</class>
</hibernate-mapping>
写完以后,一定要导入进去。编写测试类,到数据库中查看表是否生成。
二级缓存
第二级缓存是一种可选择的缓存并且第一级缓存在任何想要在第二级缓存中找到一个对象前将总是被询问。
第二级缓存可以在每一个类和每一个集合的基础上被安装,并且它主要负责跨会话缓存对象。
任何第三方缓存可以和 Hibernate 一起使用。org.hibernate.cache.CacheProvider 接口被提供,它必须实现来给 Hibernate 提供一个缓存实现的解决方法。
二级缓存 需要自己配置
现在比较好用的是自己自带的ehcatche/memcache/jboss tree 。。。。实现的功能差不多都
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
命中率:是所有缓存当中最重要的东西,不能都放进去,缓存区是有大小的。新的顶掉旧的,也不好。
通过 Provide class 来进行制定相关的类。
查询层次缓存
Hibernate 也实现了一个和第二级缓存密切集成的查询结果集缓存。
这是一个可选择的特点并且需要两个额外的物理缓存区域,它们保存着缓存的查询结果和表单上一次更新时的时间戳。这仅对以同一个参数频繁运行的查询来说是有用的。
第二级缓存
Hibernate 使用默认的一级缓存并且你不用使用一级缓存。让我们直接看向可选的二级缓存。不是所有的类从缓存中获益,所以能关闭二级缓存是重要的。
Hibernate 的二级缓存通过两步设置。第一,你必须决定好使用哪个并发策略。之后,你使用缓存提供程序来配置缓存到期时间和物理缓存属性。
并发策略
一个并发策略是一个中介,它负责保存缓存中的数据项和从缓存中检索它们。如果你将使用一个二级缓存,你必须决定,对于每一个持久类和集合,使用哪一个并发策略。
Transactional:为主读数据使用这个策略,在一次更新的罕见状况下并发事务阻止过期数据是关键的。
Read-write:为主读数据再一次使用这个策略,在一次更新的罕见状况下并发事务阻止过期数据是关键的。
Nonstrict-read-write:这个策略不保证缓存和数据库之间的一致性。如果数据几乎不改变并且过期数据不是很重要,使用这个策略。
Read-only:一个适合永不改变数据的并发策略。只为参考数据使用它。
缓存提供者
在考虑你将为你的缓存候选类所使用的并发策略后你的下一步是挑选一个缓存提供者。Hibernate 让你为整个应用程序选择一个单独的缓存提供者。
缓存的配置文件:默认是放在 src目录下面
注意:事务性的问题
我们可以制定事务的缓存级别
脏读 换读
数据库中是默认是有索的操作 锁定表等
Spring 声明是事务管理 缓存也来管理缓存的 管理数据库 管理事务