数据库缓存
1、什么是缓存?
缓存是介于物理数据源与应用程序之间,是对数据库中的数据复制一份临时放在内存中的容器,其作用是为了减少应用程序对物理数据源访问的次数,从而提高了应用程序的运行性能。
Hibernate在进行读取数据的时候,根据缓存机制在相应的缓存中查询,如果在缓存中找到了需要的数据(我们把这称做“缓存命中"),则就直接把命中的数据作为结果加以利用,避免了大量发送SQL语句到数据库查询的性能损耗。
2、缓存能解决的问题或为什么需要缓存?
- 性能
将相应数据存储起来以避免数据的重复创建、处理和传输,可有效提高性能。
- 稳定性
同一个应用中,对同一数据、逻辑功能和用户界面的多次请求时经常发生的。当用户基数很大时,如果每次请求都进行处理,消耗的资源是很大的浪费,也同时造成系统的不稳定。
- 可用性
有时,提供数据信息的服务可能会意外停止,如果使用了缓存技术,可以在一定时间内仍正常提供对最终用户的支持,提高了系统的可用性。
在应用程序中缓存数据有以下好处:
·减少交互的通讯量——缓存数据能有效减少在进程和机器间的传输量;
·降低系统中的处理量——减少处理次数;
·降低需要做的磁盘访问次数——比如缓存在内存中的数据。
3、存储类型
缓存有很多实现方法,所有这些可以被分为两类,基于内存的缓存和基于磁盘的缓存:
1、 内存驻留缓存——包含在内存中临时存储数据的所有实现方法,通常在以下情况下使用:
a) 应用程序频繁使用同样的数据;
b) 应用程序需要经常获取数据;
通过将数据保留在内存中,你可以有效降低昂贵的磁盘访问操作,也可以通过将数据保留在使用者进程中来最大程度的减少跨进程的数据传输。
2、 磁盘驻留缓存——这种技术包含所有使用磁盘作为存储介质的缓存技术,如文件和数据库。在以下情况下基于磁盘的缓存是很有效的:
a) 处理大数据量时;
b) 应用服务提供的数据可能并不是总能使用(比如离线的情况);
c) 缓存的数据必须能在进程回收和机器重启的情况下保持有效;
通过缓存处理过的数据,你可以有效降低数据处理的负担,同时可减少数据交互的代价。
4、缓存策略提供商
提供了HashTable缓存,EHCache,OSCache,SwarmCache,jBoss Cathe2,这些缓存机制,其中EHCache,OSCache是不能用于集群环境(Cluster Safe)的,而SwarmCache,jBoss Cathe2是可以的。HashTable缓存主要是用来测试的,只能把对象放在内存中,EHCache,OSCache可以把对象放在内存(memory)中,也可以把对象放在硬盘(disk)上(为什么放到硬盘上?上面解释了)。
5、缓存的两个特性
5.1、缓存的范围
决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。
事务范围
进程范围
集群范围
注:
对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问的速度不一定会比直接访问数据库数据的速度快多少。
事务范围的缓存是持久化层的第一级缓存,通常它是必需的;进程范围或集群范围的缓存是持久化层的第二级缓存,通常是可选的。
5.2、缓存的并发访问策略
当多个并发的事务同时访问持久化层的缓存的相同数据时,会引起并发问题,必须采用必要的事务隔离措施。
在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。
因此可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。
事务型并发访问策略是事务隔离级别最高,只读型的隔离级别最低。事务隔离级别越高,并发性能就越低。
A 事务型:仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。
对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。
B 读写型:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。
对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。
C 非严格读写型:不保证缓存与数据库中数据的一致性。
如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。
对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。
D 只读型:对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。
6、Hibernate缓存分类
6.1、Session缓存(又称作事务缓存)
Hibernate内置的,不能卸除。
缓存范围:缓存只能被当前Session对象访问。缓存的生命周期依赖于Session的生命周期,当Session被关闭后,缓存也就结束生命周期。
6.2、SessionFactory缓存(又称作应用缓存)
使用第三方插件,可插拔。
缓存范围:缓存被应用范围内的所有session共享,不同的Session可以共享。这些session有可能是并发访问缓存,因此必须对缓存进行更新。缓存的生命周期依赖于应用的生命周期,应用结束时,缓存也就结束了生命周期,二级缓存存在于应用程序范围。
7、一级缓存
Hibernate一些与一级缓存相关的操作:
数据放入缓存:
1. save()。当session对象调用save()方法保存一个对象后,该对象会被放入到session的缓存中。
2. get()和load()。当session对象调用get()或load()方法从数据库取出一个对象后,该对象也会被放入到session的缓存中。
3. 使用HQL和QBC等从数据库中查询数据。
例如:数据库有一张表如下:

使用get()或load()证明缓存的存在:
1 public class Client 2 { 3 public static void main(String[] args) 4 { 5 Session session = HibernateUtil.getSessionFactory().openSession(); 6 Transaction tx = null; 7 try 8 { 9 /*开启一个事务*/ 10 tx = session.beginTransaction(); 11 /*从数据库中获取id="402881e534fa5a440134fa5a45340002"的Customer对象*/ 12 Customer customer1 = (Customer)session.get(Customer.class, "402881e534fa5a440134fa5a45340002"); 13 System.out.println("customer.getUsername is"+customer1.getUsername()); 14 /*事务提交*/ 15 tx.commit(); 16 17 System.out.println("-------------------------------------"); 18 19 /*开启一个新事务*/ 20 tx = session.beginTransaction(); 21 /*从数据库中获取id="402881e534fa5a440134fa5a45340002"的Customer对象*/ 22 Customer customer2 = (Customer)session.get(Customer.class, "402881e534fa5a440134fa5a45340002"); 23 System.out.println("customer2.getUsername is"+customer2.getUsername()); 24 /*事务提交*/ 25 tx.commit(); 26 27 System.out.println("-------------------------------------"); 28 29 /*比较两个get()方法获取的对象是否是同一个对象*/ 30 System.out.println("customer1 == customer2 result is "+(customer1==customer2)); 31 } 32 catch (Exception e) 33 { 34 if(tx!=null) 35 { 36 tx.rollback(); 37 } 38 } 39 finally 40 { 41 session.close(); 42 } 43 } 44 }
程序控制台输出结果:
Hibernate:
select
customer0_.id as id0_0_,
customer0_.username as username0_0_,
customer0_.balance as balance0_0_
from
customer customer0_
where
customer0_.id=?
customer.getUsername islisi
-------------------------------------
customer2.getUsername islisi
-------------------------------------
customer1 == customer2 result is true
其原理是:在同一个Session里面,第一次调用get()方法, Hibernate先检索缓存中是否有该查找对象,发现没有,Hibernate发送SELECT语句到数据库中取出相应的对象,然后将该对象放入缓存中,以便下次使用,第二次调用get()方法,Hibernate先检索缓存中是否有该查找对象,发现正好有该查找对象,就从缓存中取出来,不再去数据库中检索,没有再次发送select语句。
数据从缓存中清除:
1. evict()将指定的持久化对象从缓存中清除,释放对象所占用的内存资源,指定对象从持久化状态变为脱管状态,从而成为游离对象。
2. clear()将缓存中的所有持久化对象清除,释放其占用的内存资源。
其他缓存操作:
1. contains()判断指定的对象是否存在于缓存中。
2. flush()刷新缓存区的内容,使之与数据库数据保持同步。
8、二级缓存
1 @Test 2 public void testCache2() { 3 Session session1 = sf.openSession();//获得Session1 4 session1.beginTransaction(); 5 Category c = (Category)session1.load(Category.class, 1); 6 System.out.println(c.getName()); 7 session1.getTransaction().commit(); 8 session1.close(); 9 10 11 Session session2 = sf.openSession();//获得Session2 12 session2.beginTransaction(); 13 Category c2 = (Category)session2.load(Category.class, 1); 14 System.out.println(c2.getName()); 15 session2.getTransaction().commit(); 16 session2.close(); 17 }
当我们重启一个Session,第二次调用load或者get方法检索同一个对象的时候会重新查找数据库,会发select语句信息。
原因:一个session不能取另一个session中的缓存。
性能上的问题:假如是多线程同时去取Category这个对象,load一个对象,这个对像本来可以放到内存中的,可是由于是多线程,是分布在不同的session当中的,所以每次都要从数据库中取,这样会带来查询性能较低的问题。
解决方案:使用二级缓存。
8.1、什么是二级缓存?
SessionFactory级别的缓存,可以跨越Session存在,可以被多个Session所共享。
8.2、适合放到二级缓存中:
(1)经常被访问
(2)改动不大
(3)数量有限
(4)不是很重要的数据,允许出现偶尔并发的数据。
这样的数据非常适合放到二级缓存中的。
用户的权限:用户的数量不大,权限不多,不会经常被改动,经常被访问。
例如组织机构。
思考:什么样的类,里面的对象才适合放到二级缓存中?
改动频繁,类里面对象特别多,BBS好多帖子,这些帖子20000多条,哪些放到缓存中,不能确定。除非你确定有一些经常被访问的,数据量并不大,改动非常少,这样的数据非常适合放到二级缓存中的。
8.3、二级缓存实现原理:
Hibernate如何将数据库中的数据放入到二级缓存中?注意,你可以把缓存看做是一个Map对象,它的Key用于存储对象OID,Value用于存储POJO。首先,当我们使用Hibernate从数据库中查询出数据,获取检索的数据后,Hibernate将检索出来的对象的OID放入缓存中key 中,然后将具体的POJO放入value中,等待下一次再次向数据查询数据时,Hibernate根据你提供的OID先检索一级缓存,若有且配置了二级缓存,则检索二级缓存,如果还没有则才向数据库发送SQL语句,然后将查询出来的对象放入缓存中。
8.4、使用二级缓存
8.4.1、打开二级缓存:
为Hibernate配置二级缓存:
在主配置文件中hibernate.cfg.xml :
<!-- 使用二级缓存 -->
1 <!-- 使用二级缓存 --> 2 <property name="hibernate.cache.use_second_level_cache">true</property> 3 <!--设置缓存的类型,设置缓存的提供商--> 4 <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
或者当hibernate与Spring整合后直接配到Spring配置文件applicationContext.xml中
1 <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 2 <property name="dataSource" ref="dataSource"/> 3 <property name="mappingResources"> 4 <list> 5 <value>com/lp/ecjtu/model/Employee.hbm.xml</value> 6 <value>com/lp/ecjtu/model/Department.hbm.xml</value> 7 </list> 8 </property> 9 <property name="hibernateProperties"> 10 <value> 11 hibernate.dialect=org.hibernate.dialect.OracleDialect 12 hibernate.hbm2ddl.auto=update 13 hibernate.show_sql=true 14 hibernate.format_sql=true 15 hibernate.cache.use_second_level_cache=true 16 hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider 17 hibernate.generate_statistics=true 18 </value> 19 </property> 20 </bean>
8.4.2、配置ehcache.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <ehcache> 3 <!-- 4 缓存到硬盘的路径 5 --> 6 <diskStore path="d:/ehcache"></diskStore> 7 8 <!-- 9 默认设置 10 maxElementsInMemory : 在內存中最大緩存的对象数量。 11 eternal : 缓存的对象是否永远不变。 12 timeToIdleSeconds :可以操作对象的时间。 13 timeToLiveSeconds :缓存中对象的生命周期,时间到后查询数据会从数据库中读取。 14 overflowToDisk :内存满了,是否要缓存到硬盘。 15 --> 16 <defaultCache maxElementsInMemory="200" eternal="false" 17 timeToIdleSeconds="50" timeToLiveSeconds="60" overflowToDisk="true"></defaultCache> 18 19 <!-- 20 指定缓存的对象。 21 下面出现的的属性覆盖上面出现的,没出现的继承上面的。 22 --> 23 <cache name="com.suxiaolei.hibernate.pojos.Order" maxElementsInMemory="200" eternal="false" 24 timeToIdleSeconds="50" timeToLiveSeconds="60" overflowToDisk="true"></cache> 25 26 </ehcache>
8.4.3、使用二级缓存需要在实体类中加入注解:
需要ehcache-1.2.3.jar包:
还需要 commons_loging1.1.1.jar包
在实体类中通过注解可以配置实用二级缓存:
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
Load默认使用二级缓存,就是当查一个对象的时候,它先会去二级缓存里面去找,如果找到了就不去数据库中查了。
Iterator默认的也会使用二级缓存,有的话就不去数据库里面查了,不发送select语句了。
List默认的往二级缓存中加数据,假如有一个query,把数据拿出来之后会放到二级缓存,但是执行查询的时候不会到二级缓存中查,会在数据库中查。原因每个query中查询条件不一样。
8.4.4、也可以在需要被缓存的对象中hbm文件中的<class>标签下添加一个<cache>子标签:
1 <hibernate-mapping> 2 <class name="com.suxiaolei.hibernate.pojos.Order" table="orders"> 3 <cache usage="read-only"/> 4 <id name="id" type="string"> 5 <column name="id"></column> 6 <generator class="uuid"></generator> 7 </id> 8 9 <property name="orderNumber" column="orderNumber" type="string"></property> 10 <property name="cost" column="cost" type="integer"></property> 11 12 <many-to-one name="customer" class="com.suxiaolei.hibernate.pojos.Customer" 13 column="customer_id" cascade="save-update"> 14 </many-to-one> 15 </class> 16 </hibernate-mapping>
存在一对多的关系,想要在在获取一方的时候将关联的多方缓存起来,需要再集合属性下添加<cache>子标签,这里需要将关联的对象的 hbm文件中必须在存在<class>标签下也添加<cache>标签,不然Hibernate只会缓存OID。
1 <hibernate-mapping> 2 <class name="com.suxiaolei.hibernate.pojos.Customer" table="customer"> 3 <!-- 主键设置 --> 4 <id name="id" type="string"> 5 <column name="id"></column> 6 <generator class="uuid"></generator> 7 </id> 8 9 <!-- 属性设置 --> 10 <property name="username" column="username" type="string"></property> 11 <property name="balance" column="balance" type="integer"></property> 12 13 <set name="orders" inverse="true" cascade="all" lazy="false" fetch="join"> 14 <cache usage="read-only"/> 15 <key column="customer_id" ></key> 16 <one-to-many class="com.suxiaolei.hibernate.pojos.Order"/> 17 </set> 18 </class> 19 </hibernate-mapping>

浙公网安备 33010602011771号