一.缓存概述
什么是缓存:
缓存将数据库/硬盘上文件中数据,放入到缓存中(就是内存中一块空间).当再次使用的使用,可以直接从内存中获取。
缓存的好处:
提升程序运行的效率.缓存技术是Hibernate的一个优化的手段。
二.Hibernate缓存机制
Hibernate是一个持久层框架,经常访问物理数据库、为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能、缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
三.Hibernate缓存
包括两大类:Hibernate一级缓存和Hibernate二级缓存。
1.Hibernate一级缓存又称为“Session的缓存”。
Session内置不能被卸载,Session的缓存是事务范围的缓存(Session对象的生命周期通常对应一个数据库事务或者一个应用事务)。
一级缓存中,持久化类的每个实例都具有唯一的OID。
2.Hibernate二级缓存又称为“SessionFactory的缓存”。
由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。
二级缓存是可选的,是一个可配置的插件,默认下SessionFactory不会启用这个插件。
Hibernate提供了org.hibernate.cache.CacheProvider接口,它充当缓存插件与Hibernate之间的适配器。
Hibernate查找对象如何应用缓存?
当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;如果都查不到,再查询数据库,把结果按照ID放入到缓存删除、更新、增加数据的时候,同时更新缓存。
四.理解一级缓存
在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存. 只要 Session 实例没有结束生命周期, 存放在它缓存中的对象也不会结束生命周期
当session的save()方法持久化一个对象时,该对象被载入缓存,以后即使程序中不再引用该对象,只要缓存不清空,该对象仍然处于生命周期中。当试图get()、 load()对象时,会判断缓存中是否存在该对象,有则返回,此时不查询数据库。没有再查询数据库
Session 能够在某些时间点, 按照缓存中对象的变化来执行相关的 SQL 语句, 来同步更新数据库, 这一过程被称为刷出缓存(flush)
默认情况下 Session 在以下时间点刷出缓存:
当应用程序调用 Transaction 的 commit()方法的时, 该方法先刷出缓存(session.flush()),然后在向数据库提交事务(tx.commit())
当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先刷出缓存,以保证查询结果能够反映持久化对象的最新状态调用 Session 的 flush() 方法。
案例:
// 分别用get执行两次查询.
Book book1 = (Book) session.get(Book.class, 1);// 马上发生SQL去查询
System.out.println(book1);
Book book2 = (Book) session.get(Book.class, 1);// 不发生SQL,因为使用一级缓存的数据
System.out.println(book2);
// save方法可以向一级缓存中存放数据的.
Book book = new Book();
book.setName("x新书");
book.setAuthor("张XX");
book.setPrice(45);
Integer id = (Integer) session.save(book);
Book book2 = (Book) session.get(Book.class, id);
System.out.println(book2);
Hibernate快照
当session加载了对象后,会在session中创建一个该对象的副本,副本的属性值与数据库中的值相同,该副本也成为快照。当session清理缓存时,通过比较对象的当前属性和快照的属性值,来判断对象的哪些属性发生了变化。
发生变化的执行sql语句
没有发生变化不再执行语句
结论:向一级缓存存入数据的时候,放入一级缓存区和一级缓存快照区,当更新了一级缓存的数据的时候,事务一旦提交,比对一级缓存和快照区,如果数据一致,不更新,如果数据不一致,自动更新数据库。
public class MyTest {
@Test
public void test() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
String hql="from Dept";
Query query = session.createQuery(hql);
List<Dept> list = query.list();
System.out.println(list);
tx.commit();
session.close();
}
@Test
public void test1() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Dept dept1= (Dept) session.get(Dept.class, 2);
Dept dept3= (Dept) session.get(Dept.class, 3);
//1 session(一级缓存) 2 二级缓存(组件) 3 数据库
//一级缓存之(session)
session.evict(dept1); //清除dept1
//session.clear(); //清除 session之前或之后
//session.save(arg0)
Dept dept2= (Dept) session.get(Dept.class, 3);
System.out.println(dept2.getDname());
tx.commit();
session.close();
}
@Test
public void test2() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Dept dept=new Dept();
dept.setDname("科研部");
Integer id=(Integer) session.save(dept); //一定会放在一级缓存
Dept dept1= (Dept) session.get(Dept.class, id);
tx.commit();
session.close();
}
//快照
@Test
public void test3() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Dept dept1= (Dept) session.get(Dept.class, 8);
//1 session 科研部 2 快照 科研部
dept1.setDname("aaaa");
dept1.setDname("科研部");
session.save(dept1);
tx.commit();
session.close();
}
//flush 把session中数据 放入数据库的内存中
@Test
public void test4() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Dept dept=new Dept();
dept.setDname("科研部3");
Integer id=(Integer)session.save(dept);
session.flush(); //重点
session.clear();
tx.commit();
session.get(Dept.class, id);
session.close();
}
//refesh 同步数据库中的数据到session
@Test
public void test5() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Dept dept = (Dept) session.get(Dept.class, 11);
dept.setDname("abc");
session.refresh(dept); //重点方法
System.out.println(dept.getDname()); // 科研部3
//session.update(dept);
tx.commit();
session.close();
}
}
session缓存概述:
session的缓存:在 Session 接口的实现中包含一系列的 集合(List),这些集合用于保存本次session创建/修改/查询出来的对象, 这些 集合(List)构成了 Session 缓存. 只要 Session 实例没有结束生命周期, 存放在它缓存中的对象也不会结束生命周期
举例: 当session的save()方法持久化一个对象时,该对象被载入缓存,以后即使程序中不再引用该对象,只要缓存不清空,该对象仍然处于生命周期中。当使用get()、 load()再次获取对象时,会判断缓存中是否存在该对象,有则返回,此时不查询数据库。没有再查询数据库
session缓存管理:
Hibernate提供了若干方法管理缓存信息
session.flush:将缓存中状态为未同步的对象先同步到数据库内存中(并未保存到数据库物理硬盘),并标注刚才完成同步的对象状态为已同步,实际就是执行一系列sql语句,但不提交事务;
transation.commit:先调用flush() 方法,然后提交事务. 则意味着提交事务意味着对数据库操作永久保存下来。
session.refresh:刷新,让session和数据库同步,执行查询,把数据库的最新信息显示出来,更新本地缓存的对象状态.将快照区的数据重新覆盖了一级缓存的数据.
session.evict(obj):将参数中的对象在session缓存中清除。
session.clear:清空缓存,等价于list.removeAll();
session.close:清空Session的缓存,关闭session资源。
•一级缓存是默认开启的Session每次查询数据时,都会从一级缓存中取数,若存在则直接返回数据,如果缓存中没取到数据,则查询数据库,并将查询结果放入缓存中
•缓存是Session独享的,每个Session不能访问其他Session的缓存区。
session.flush一般是结合事务对象Transaction用的,一般是每一千条提交一次,提交了后就要session.flush(),来达到清空session中的缓存作用,避免内存溢出。
五.理解二级缓存
Hibernate中提供了两个级别的缓存
第一级别的缓存是 Session 级别的缓存,在一次请求中共享数据!它是属于事务范围的缓存。这一级别的缓存由 hibernate 管理的,一般情况下无需进行干预
第二级别的缓存是 SessionFactory 级别的缓存,它是属于进程范围的缓存,整个应用程序共享一个会话工厂,共享一个二级缓存
Hibernate 的缓存可以分为两类:
内置缓存: Hibernate 自带的, 不可卸载. 使用一个Map,用于存放配置信息,预定义HQL语句等,提供给Hibernate框架自己使用,对外只读的。不能操作. 该内置缓存是只读的。
外置缓存(二级缓存): 一个可配置的缓存插件. 使用另一个Map,用于存放用户自定义 数据。默认不开启。外置缓存hibernate只提供规范(接口),需要第三方实现类。 外置缓存有成为二级缓存。
二级缓存包括:
类级别缓存
集合级别缓存
时间戳缓存
查询缓存
理解二级缓存的并发访问策略:
缓存中存放的数据?
适合放入二级缓存中的数据:
1.很少被修改
2. 不是很重要的数据, 允许出现偶尔的并发问题
不适合放入二级缓存中的数据:
1.经常被修改
2.财务数据, 绝对不允许出现并发问题
3.与其他应用数据共享的数据
二级缓存提供的供应商:
Hibernate 的二级缓存是进程或集群范围内的缓存, 缓存中存放的是对象的散装数据;
二级缓存是可配置的的插件, Hibernate 允许选用以下类型的缓存插件:
EHCache: 可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 对 Hibernate 的查询缓存提供了支持
OpenSymphony :可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 提供了丰富的缓存数据过期策略, 对 Hibernate 的查询缓存提供了支持
SwarmCache: 可作为集群范围内的缓存, 但不支持 Hibernate 的查询缓存
JBossCache:可作为集群范围内的缓存, 支持 Hibernate 的查询缓存
4 种缓存插件支持的并发访问策略(x 代表支持, 空白代表不支持)
配置二级缓存?
1.导入jar包:ehcache-1.5.0.jar/ commons-logging.jar/ backport-util-concurrent.jar
2.开启二级缓存(我要使用二级缓存)
3.确定二级缓存提供商(我要使用哪个二级缓存)
4.确定需要缓存内容
1>配置需要缓存的类
2>配置需要缓存的集合
5.配置ehcache自定义配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 数据库连接池 -->
<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider </property>
<!--在连接池中可用的数据库连接的最少数目 -->
<property name="c3p0.min_size">5</property>
<!--在连接池中所有数据库连接的最大数目 -->
<property name="c3p0.max_size">20</property>
<!--设定数据库连接的过期时间,以秒为单位,
如果连接池中的某个数据库连接处于空闲状态的时间超过了timeout时间,就会从连接池中清除 -->
<property name="c3p0.timeout">120</property>
<!--每3000秒检查所有连接池中的空闲连接 以秒为单位-->
<property name="c3p0.idle_test_period">3000</property>
<!-- 连接数据库 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///crm</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">1234</property>
<!-- 方言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 是否在制台显示sql语句 -->
<property name="hibernate.show_sql">true</property>
<!-- 格式化sql -->
<property name="hibernate.format_sql">true</property>
<!-- 动态创建或更新表 create/create-drop/update/validate -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 指定session 与当前线程绑定 必须加-->
<property name="hibernate.current_session_context_class">thread</property>
<!-- 开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 二级缓存提供商 -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<!-- 单向的一对多 -->
<mapping resource="com/onetomany/Dept.hbm.xml" />
<mapping resource="com/onetomany/Emp.hbm.xml" />
</session-factory>
</hibernate-configuration>
类级别缓存:类缓存:只存放数据,散装数据一级缓存:存放对象本身。
集合级别缓存:集合缓存:只存放关联对象OID的值,如果需要数据,从类缓存中获取。
时间戳缓存:时间戳:任何操作都在时间戳中记录操作时间(所有的操作都会在时间戳中进行记录,如果数据不一致,将触发select语句进行查询)。
查询缓存:查询缓存默认不使用。需要手动开启。查询缓存:将HQL语句与 查询结果进行绑定。通过HQL相同语句可以缓存内容。默认情况Query对象只将查询结果存放在一级和二级缓存,不从一级或二级缓存获取。查询缓存就是让Query可以从二级缓存获得内容。
配置进程范围内的二级缓存(配置ehcache缓存)
<diskStore>:指定一个目录, 当 EHCache 把数据写到硬盘上时, 将把数据写到这个文件目录下. 默认是C:\WINDOWS\Temp
<defaultCache>: 设置缓存的默认数据过期策略
<cache> 设定具体的命名缓存的数据过期策略
使用name属性:
每个命名缓存代表一个缓存区域,每个缓存区域有各自的数据过期策略。命名缓存机制使得用户能够在每个类以及类的每个集合的粒度上设置数据过期策略。
二级缓存的数据存放到临时目录
参数设置:
maxElementsInMemory="10000" 内存最大数
eternal="false" 是否永久(内存常驻留)
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true" 内存满了,是否写入到硬盘
maxElementsOnDisk="10000000" 硬盘最大数
diskPersistent="false" 关闭JVM,是否将内存保存硬盘中
diskExpiryThreadIntervalSeconds="120" 轮询
memoryStoreEvictionPolicy="LRU"
解析:
maxElementsInMemory :设置基于内存的缓存中可存放的对象最大数目 eternal:设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值
overflowToDisk:设置基于内在的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
diskPersistent 当jvm结束时是否持久化对象 true false 默认是falsediskExpiryThreadIntervalSeconds 指定专门用于清除过期对象的监听线程的轮询时间
memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
外置缓存特点:
外置缓存将查询出来的数据以对象的形式放在缓存中(OID、HQL),不会缓存HQL语句查询出来的集合信息,因此两次相同的HQL语句,也要查询两次数据库。
外置缓存对象的数量如果超出缓存控件的配置数量,将会写入到硬盘中,默认路径为环境变量Temp的路径。
当更新session的缓存数据,将会更新外置缓存数据,但不要以非hibernate的方式更新数据。
Hibernate中Query接口的List()与iterate()的区别:
使用list()方法,每次都会执行SQL,从数据库返回数据并填充到缓存中
使用iterate(),会先查找缓存,如果缓存中没有才查询数据库
使用iterate()查询数据库,会先抛出一条查询主键的sql,然后根据主键ID,分别发出多条sql查询数据
当使用SQLQurey时,不能直接调用iterate(),只能调用list()
代码测试:
public class TestCache {
//测试二级缓存
@Test
public void test() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
//查询id为2的部门信息
Dept dept = (Dept) session.get(Dept.class, 3);
Set emps = dept.getEmps();
Iterator it1 = emps.iterator();
while (it1.hasNext()) {
Emp emp = (Emp) it1.next();
System.out.println(emp.getEname());
}
//清除一级缓存
session.clear();
//查询id为2的部门信息 先一级,然后二级,最后去发送sql到数据库去查询
Dept dept1 = (Dept) session.get(Dept.class, 3);
Set emps2 = dept1.getEmps();
Iterator it2 = emps2.iterator();
while (it2.hasNext()) {
Emp emp = (Emp) it2.next();
System.out.println(emp.getEname());
}
System.out.println(dept.getDname());
tx.commit();
session.close();
}
//list() / iterate() 区别
@Test
public void test1() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
//查询id为2的部门信息
Query query = session.createQuery("from Dept");
//list() //生成一条查询语句
List<Dept> list = query.list();
for (Dept d : list) {
System.out.println(d.getDname());
}
//iterate() //生成很多sql语句 先查ID ,然后每次生成sql查询语句(不用 )
Iterator it = query.iterate();
while(it.hasNext()){
Dept dept = (Dept) it.next();
System.out.println(dept.getDname());
}
tx.commit();
session.close();
}
}
Hibernate中Query接口的list()与iterate()的区别
Hibernate中Query接口的list()与iterate()的区别