才高行厚的hibernate(6)---hibernate的缓存

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();

4)采用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)

事务型:仅仅在托管环境中适用。它提供了Repeatable Read事务隔离级别。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。Hibernate的事务缓存策略提供了全事务的缓存支持, 例如对JBoss TreeCache的支持。这样的缓存只能用于JTA环境中,你必须指定 为其hibernate.transaction.manager_lookup_class属性。
<class name="hibernate.senssic.Pro" mutable="false">
    <cache usage="transactional"
/>
    ....
</class>

读写型:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。
<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>

二级缓存使用配置:

在hibernate的配置文件中加入如下:
< property   name = "hibernate.cache.use_second_level_cache"   > true </ property >     <!-- 使用二级缓存 -->   
配置实体映射文件:
<class name="hibernate.senssic.Pro" mutable="false">
    <cache usage="transactional"
/>
    ....
</class>
如果使用annotation可以在public class User 上面配置 @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
在src目录下,新建ehcache.xml文件,Ehcache 中ehcache.xml 配置详解和示例:
<?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中定义了许多方法, 清除缓存中实例、整个类、集合实例或者整个集合。

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 - 仅向二级缓存写数据,但不从二级缓存中读数据。

eg: session.setCacheMode(CacheMode.GET);

如若需要查看二级缓存或查询缓存区域的内容,你可以使用统计(Statistics) API。

Map cacheEntries = sessionFactory.getStatistics()
                                             .getSecondLevelCacheStatistics(regionName)
                                              .getEntries();

此时,你必须手工打开统计选项。可选的,你可以让Hibernate更人工可读的方式维护缓存内容。

hibernate.generate_statistics =true
        hibernate.cache.use_structured_entries= true

常用二级缓存插件

EHCache   org.hibernate.cache.EhCacheProvider
OSCache   org.hibernate.cache.OSCacheProvider
SwarmCahe   org.hibernate.cache.SwarmCacheProvider
JBossCache   org.hibernate.cache.TreeCacheProvider

 各种缓存提供商对缓存并发策略的支持情况(Cache Concurrency Strategy Support)

Cacheread-onlynonstrict-read-writeread-writetransactional
Hashtable (not intended for production use)yesyesyes 
EHCacheyesyesyes 
OSCacheyesyesyes 
SwarmCacheyesyes  
JBoss TreeCacheyes  yes

查询缓存

查询缓存是针对普通属性结果集的缓存

对实体对象的结果集只缓存id

查询缓存的生命周期,当前关联的表发生修改,那么查询缓存生命周期结束

查询缓存的配置和使用:

1. 启用查询缓存:在hibernate.cfg.xml中加入:

<property name=”hibernate.cache.use_query_cache”>true</property>

  2. 在程序中必须手动启用查询缓存,如:query.setCacheable(true);

eg:

Query query = session.createQuery(“select s.name from Student s”);

//启用查询缓存

query.setCacheable(true);

List names = query.list();

for(Iterator iter = names.terator();iter.hasNext();){

       String name = (String)iter.next();

       System.out.println(name);

}

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();){

       String name = (String)iter.next();

       System.out.println(name);

}

注意:

1.查询缓存生命周期与session生命周期无关,session关闭仍然缓存

2.查询缓存只对query.list()起作用,对query.iterate()不起作用,也就是说query.iterate()不使用查询缓存

3.如果查询需要强行刷新其查询缓存区域,那么你应该调用Query.setCacheMode(CacheMode.REFRESH)方法

4.在查询缓存中,它并不缓存结果集中所包含的实体的确切状态;它只缓存这些实体的标识符属性的值、以及各值类型的结果。 所以查询缓存通常会和二级缓存一起使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值