hibernate缓存介绍
缓存是位于应用程序与物理数据源之间,用于临时存放复制数据的内存区域,目的是为了减少应用程序对物理数据源访问的次数,从而提高应用程序的运行性能.
Hibernate在查询数据时,首先到缓存中去查找,如果找到就直接使用,找不到的时候就会从物理数据源中检索,所以,把频繁使用的数据加载到缓存区后,就可以大大减少应用 程序对物理数据源的访问,使得程序的运行性能明显的提升.
hibernate缓存范围
1.事务范围
2.应用范围
3.集群范围
n+1问题
前提:Hibernate默认表与表的关联方法是fetch="select",不是fetch="join",这都是为了懒加载而准备的,因为缓存中没有,所以当我们使用Iterate查询的话可能就会出现n+1为题。
原因:
1)一对多(<set><list>) ,在1的这方,通过1条sql查找得到了1个对象,由于关联的存在 ,那么又需要将这个对象关联的集合取出,所以合集数量是n还要发出n条sql,于是本来的1条sql查询变成了1 +n条 。
2)多对一<many-to-one> ,在多的这方,通过1条sql查询得到了n个对象,由于关联的存在,也会将这n个对象对应的1 方的对象取出, 于是本来的1条sql查询变成了1 +n条 。
3)iterator 查询时,一定先去缓存中找(1条sql查集合,只查出ID),在没命中时,会再按ID到库中逐一查找, 产生1+n条SQL
例子:
Session session = sFactory.openSession();
@SuppressWarnings("unchecked")
Iterator<Pro> iter = session.createQuery("from Pro").iterate();
for (; iter.hasNext();) {
Pro pro = iter.next();
System.out.println(pro.getAddress());
}
现象:
Hibernate: select pro0_.aid as col_0_0_ from Pro pro0_
Hibernate: select pro0_.aid as aid1_1_1_, pro0_.address as address2_1_1_, pro0_.personid as personid3_1_1_, person1_.id as id1_0_0_, person1_.name as name2_0_0_ from Pro pro0_ left outer join Person person1_ on pro0_.personid=person1_.id where pro0_.aid=?
安徽0
Hibernate: select pro0_.aid as aid1_1_1_, pro0_.address as address2_1_1_, pro0_.personid as personid3_1_1_, person1_.id as id1_0_0_, person1_.name as name2_0_0_ from Pro pro0_ left outer join Person person1_ on pro0_.personid=person1_.id where pro0_.aid=?
安徽1
Hibernate: select pro0_.aid as aid1_1_1_, pro0_.address as address2_1_1_, pro0_.personid as personid3_1_1_, person1_.id as id1_0_0_, person1_.name as name2_0_0_ from Pro pro0_ left outer join Person person1_ on pro0_.personid=person1_.id where pro0_.aid=?
安徽2
Hibernate: select pro0_.aid as aid1_1_1_, pro0_.address as address2_1_1_, pro0_.personid as personid3_1_1_, person1_.id as id1_0_0_, person1_.name as name2_0_0_ from Pro pro0_ left outer join Person person1_ on pro0_.personid=person1_.id where pro0_.aid=?
安徽3
Hibernate: select pro0_.aid as aid1_1_1_, pro0_.address as address2_1_1_, pro0_.personid as personid3_1_1_, person1_.id as id1_0_0_, person1_.name as name2_0_0_ from Pro pro0_ left outer join Person person1_ on pro0_.personid=person1_.id where pro0_.aid=?
安徽4
Hibernate: select pro0_.aid as aid1_1_1_, pro0_.address as address2_1_1_, pro0_.personid as personid3_1_1_, person1_.id as id1_0_0_, person1_.name
解决方法:
1 )lazy=false, hibernate3开始已经默认是lazy=true了;lazy=true时不会立刻查询关联对象,只有当需要关联对象(访问其属性,非id字段)时才会发生查询动作。
eg:xml中配置(<class name="hibernate.senssic.relevance.Pro" lazy="false">, <class name="hibernate.senssic.relevance.Person" lazy="false">)
注解的话:
@ManyToOne
@JoinColumn(name = "personid")
@ForeignKey(name = "fperson")
private Person person;
@OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
private Set<Pro> spro;
2)使用二级缓存, 二级缓存的应用将不怕1+N 问题,因为即使第一次查询很慢(未命中),以后查询直接缓存命中也是很快的。刚好又利用了1+N 。
3) 当然你也可以设定fetch="join",只能返回list,一次关联表全查出来,但失去了懒加载的特性。
List<Pro> list = session.createQuery("select p from Pro p left join fetch p.person").list();
List<Pro> list = session.createQuery("from Pro").list();
一级缓存(session缓存)
缓存实体,缓存范围为session,session关闭或session缓存清空,缓存就消亡了
一级缓存无法取消但可以缓存,如session.clear(),session.evict()
例子:
Session session = sFactory.openSession();
Transaction tx = session.beginTransaction();
try {
Pro pro = (Pro) session.load(Pro.class, 11);
// 懒加载,会发出一条查询语句
System.out.println(pro.getAddress());
Pro pro2 = (Pro) session.load(Pro.class, 11);
// 不会发,因为查询的实体一样,在session缓存中存在直接用
System.out.println(pro2.getAddress());
// 立即加载,会发出一条查询语句
Pro pro3 = (Pro) session.get(Pro.class, 12);
System.out.println(pro3.getAddress());
// 不会发,因为查询的实体一样,在session缓存中存在直接用
Pro pro4 = (Pro) session.get(Pro.class, 12);
System.out.println(pro4.getAddress());
// 会发出一条查询语句
String str = (String) session
.createQuery("select p.address from Pro p where p.id=13")
.iterate().next();
System.out.println(str);
// 还会发出一条查询语句,因为session不缓存属性值,针对实体缓存
String str2 = (String) session
.createQuery("select p.address from Pro p where p.id=13")
.iterate().next();
System.out.println(str2);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
} finally {
if (session != null) {
session.close();
}
}
try {
session = sFactory.openSession();
// 会发出sql查询语句,因为session关了一次session缓存没了
Pro pro3 = (Pro) session.get(Pro.class, 12);
System.out.println(pro3.getAddress());
// session.clear();// 或 session.evict(pro);
// 会发出sql查询语句,因为缓存被手动清空了
Pro pro4 = (Pro) session.get(Pro.class, 12);
System.out.println(pro4.getAddress());
// 因为session的一级缓存,所以如果有时候我们一次大批量的插入数据可能照成jvm的溢出所以一般通过以下方法解决
Person person = new Person();
person.setName("池州");
session.save(person);
for (int i = 0; i < 1000; i++) {
Pro pro = new Pro();
pro.setAddress("大街" + i + "号");
session.save(pro);
// 每20清除缓存一次
if (i % 20 == 0) {
// 强制刷新
session.flush();
// 清除缓存
session.clear();
}}
tx.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (session != null) {
session.close();
}
}
二级缓存(sessionFactory缓存)
二级缓存映射配置
<class name="hibernate.senssic.Person">
<cache
usage="transactional|read-write|nonstrict-read-write|read-only" (1)
region="RegionName" (2)
include="all|non-lazy" (3)
/>
</class>
(1) | usage(必须)说明了缓存的策略: transactional、 read-write、 nonstrict-read-write或 read-only。 |
(2) | region (可选, 默认为类或者集合的名字(class or collection role name)) 指定第二级缓存的区域名(name of the second level cache region) |
(3) | include (可选,默认为 all) non-lazy 当属性级延迟抓取打开时, 标记为lazy="true"的实体的属性可能无法被缓存 |
另外(首选?), 你可以在hibernate.cfg.xml中指定<class-cache>和 <collection-cache> 元素。
这里的usage 属性指明了缓存并发策略(cache concurrency strategy)。
<class name="hibernate.senssic.Pro" mutable="false"> <cache usage="transactional" /> .... </class>
<class name="hibernate.senssic.Person" .... > <cache usage="read-write"/> .... <set name="hibernate.senssic.Pro" ... > <cache usage="read-write"/> .... </set> </class>
<class name="hibernate.senssic.Pro" mutable="false"> <cache usage="nonstrict-read-write" /> .... </class>
<class name="hibernate.senssic.Pro" mutable="false"> <cache usage="read-only"/> .... </class>
二级缓存使用配置:
<class name="hibernate.senssic.Pro" mutable="false"> <cache usage="transactional" /> .... </class>如果使用annotation可以在public class User 上面配置 @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<!--timeToIdleSeconds 当缓存闲置n秒后销毁 -->
<!--timeToLiveSeconds 当缓存存活n秒后销毁 -->
<!--
缓存配置
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
-->
<diskStore path="c:/cache" /> <!-- 二级缓存存放地址 -->
<defaultCache
maxElementsInMemory="500"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="1200"
overflowToDisk="true" />
<cache name="hibernate.senssic.Person" maxElementsInMemory="150" eternal="false" timeToLiveSeconds="36000" timeToIdleSeconds="3600" overflowToDisk="true"/>
</ehcache>
在SessionFactory中定义了许多方法, 清除缓存中实例、整个类、集合实例或者整个集合。
sessionFactory.evict(Pro.class, proId); //evict a particular pro
sessionFactory.evict(Pro.class); //evict all pro
sessionFactory.evictCollection("Person.pro", personid); //evict a particular collection of pro
sessionFactory.evictCollection(“Person.pro"); //evict all procollections
通过 hibernate.cache.use_minimal_puts=true的设置强制二级缓存从数据库中读取数据,刷新缓存内容
session与二级缓存的交互:
CacheMode参数用于控制具体的Session如何与二级缓存进行交互。
-
CacheMode.NORMAL - 从二级缓存中读、写数据。
-
CacheMode.GET - 从二级缓存中读取数据,仅在数据更新时对二级缓存写数据。
-
CacheMode.PUT - 仅向二级缓存写数据,但不从二级缓存中读数据。
-
CacheMode.REFRESH - 仅向二级缓存写数据,但不从二级缓存中读数据。
如若需要查看二级缓存或查询缓存区域的内容,你可以使用统计(Statistics) API。
Map cacheEntries = sessionFactory.getStatistics()
.getSecondLevelCacheStatistics(regionName)
.getEntries();
此时,你必须手工打开统计选项。可选的,你可以让Hibernate更人工可读的方式维护缓存内容。
hibernate.generate_statistics =true
hibernate.cache.use_structured_entries= true
常用二级缓存插件
OSCache
SwarmCahe
JBossCache
各种缓存提供商对缓存并发策略的支持情况(Cache Concurrency Strategy Support)
Cache | read-only | nonstrict-read-write | read-write | transactional |
---|---|---|---|---|
Hashtable (not intended for production use) | yes | yes | yes | |
EHCache | yes | yes | yes | |
OSCache | yes | yes | yes | |
SwarmCache | yes | yes | ||
JBoss TreeCache | yes | yes |
查询缓存
查询缓存是针对普通属性结果集的缓存
对实体对象的结果集只缓存id
查询缓存的生命周期,当前关联的表发生修改,那么查询缓存生命周期结束
查询缓存的配置和使用:
1. 启用查询缓存:在hibernate.cfg.xml中加入:
<property name=”hibernate.cache.use_query_cache”>true</property>
Query query = session.createQuery(“select s.name from Student s”);
//启用查询缓存
query.setCacheable(true);
List names = query.list();
for(Iterator iter = names.terator();iter.hasNext();){
}
System.out.println(“------------------------------------------”);
query = session.createQuery(“select s.name from Student s”);
//启用查询缓存
query.setCacheable(true);
//没有去查询数据库,因为启用了查询缓存
names = query.list();
for(Iterator iter = names.terator();iter.hasNext();){
}
注意:1.查询缓存生命周期与session生命周期无关,session关闭仍然缓存
2.查询缓存只对query.list()起作用,对query.iterate()不起作用,也就是说query.iterate()不使用查询缓存
3.如果查询需要强行刷新其查询缓存区域,那么你应该调用Query.setCacheMode(CacheMode.REFRESH)方法
4.在查询缓存中,它并不缓存结果集中所包含的实体的确切状态;它只缓存这些实体的标识符属性的值、以及各值类型的结果。 所以查询缓存通常会和二级缓存一起使用。