hibernate -- 缓存机制
缓存简介
缓存是介于应用程序和物理数据源之间,缓存内的数据是对物理数据源中的数据的复制,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。
缓存的范围
事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的的对象形式。
进程范围:缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于进程的生命周期,进程结束时,缓存也就结束了生命周期。进程范围的缓存可能会存放大量的数据,所以存放的介质可以是内存或硬盘。缓存内的数据既可以是相互关联的对象形式也可以是对象的松散数据形式。松散的对象数据形式有点类似于对象的序列化数据,但是对象分解为松散的算法比对象序列化的算法要求更快。
集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式。
hibernate缓存
一级缓存
:缓存位置:session中
:生命周期:一个事务中。
:缓存规格:{ID:实体}
:默认开启
2.1 何时数据会进入缓存:事务中加载过的数据,都会进入缓存,并以{ID:实体}存储在session中。
2.2 何时可以检查缓存:以ID为条件的查询可以检查缓存。
*session.get();//可以检查
*Query.list();//不能检查
.uniqueResult();//不能检查
.iterate();//可以检查
*细节:iterate()运作流程
String hql="from User u where u.name=?";
Query.iterate(hql);
1>保留查询条件,到数据库中查询ID,
select id from t_user where t_name=?
[1,2,3,4,5]
2>通过查到的ID去检查缓存。如果有缓存可用,则不用再查询数据库。
但是,注意,如果没有缓存可用,则要再次发起对数据库的查询:
select * from t_user where t_id=5;
select * from t_user where t_id=4;
select * from t_user where t_id=3;
select * from t_user where t_id=2;
select * from t_user where t_id=1;
综上,再使用iterate()方法时,可能导致n+1次查询问题。n=满足条件的数据行数。
3>使用:
Iterator it=query2.iterate();
while(it.hasNext()){
User user=(User)it.next();
System.out.println(user);
}
二级缓存
:缓存位置:SessionFactory中
:生命周期:全局可用
:缓存规格:{ID:实体}
:默认关闭:通过配置开启。
:*开启二级缓存
<!-- 开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 二级缓存类别:EhCache,OSCache,JbossCache -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
*导包 并 引入ehcahe.xml
*为要进入二级缓存的实体,增加权限。
//只读缓存权限
//@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
//读写缓存权限
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
3.1 何时数据会进入缓存:事务中加载过的数据,都会进入缓存,并以{ID:实体}存储在session中。
3.2 何时可以检查缓存:以ID为条件的查询可以检查缓存。
get();
iterate();
查询缓存:依赖二级缓存
: 缓存位置:SessionFactory中
: 生命周期:全局可用,但不稳定,如果和缓存数据相关的表有任何的改动,则缓存数据失效,在一般的应用程序中,sessionfactory会以单例的形式存在,所以在整个应用程序的生命周期里,sessionfactory会一直存在。既二级缓存也一直存在直到关闭应用程序
: 缓存规格: {hql:查询结果(字段)}
: 默认关闭:<property name="hibernate.cache.use_query_cache">true</property>
在查询前://本次查询要使用查询缓存
query.setCacheable(true);
4.1 何时数据会进入缓存:用hql查询字段的查询结果,都可以进入查询缓存:{HQL:结果(字段)}
4.2 何时可以检查缓存:只要再次用同样的hql查询,则可以检查查询缓存。
4.3 使用场景
什么样的数据适合存放到第二级缓存中?
1、很少被修改的数据
2、不是很重要的数据,允许出现偶尔并发的数据
3、不会被并发访问的数据
4、参考数据
不适合存放到第二级缓存的数据?
1、经常被修改的数据
2、财务数据,绝对不允许出现并发
3、与其他应用共享的数据。
*细节:如果查询的实体,则查询缓存只能缓存:{HQL:实体的ID字段}
缓存总结
5.1 缓存规格:
*一级缓存,二级缓存,缓存的是实体:{ID:实体}
session.get(User.class,1);
"from User u";
*查询缓存:{HQL:查询结果}
tx
"select u.id,u.age from User u";
commit();
5.2 使用:
*如果查询时是查询字段的话:select a,b,c,d from XXX;
查询缓存足矣。
*如果查询时是查询实体的话:from User u;
二级缓存+查询缓存。
5.3 查询缓存和二级缓存的联合使用:
*如果查询时是查询实体的话:from User u;
初次query.list();时,检查查询缓存,没有可用数据,则转向数据库,获得一个实体User,
将{HQL:User的ID}存入查询缓存,将{User的ID:User}存入二级缓存
再次query.list();时,检查查询缓存,获得缓存数据:User的ID,通过UserID检查二级缓存,
如果有数据,则直接使用,否则以各个ID为条件分别发起查询
为什么这样设计
一般情况下,我们查询的数据一般是实时的,使用二级缓存肯定不行,使用一级缓存既利用了缓存又不会影响实时。使用二级缓存是为了存储一些比较稳定的数据,二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。
图解缓存
分区:是以全类名为单位分区
测试代码
插件缓存用的是ecache
实体:
package com.c50.entity; import java.util.Date; import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.GenericGenerator; @Entity @Table(name="user50") //只读缓存权限 //@Cache(usage=CacheConcurrencyStrategy.READ_ONLY) //读写缓存权限 @Cache(usage=CacheConcurrencyStrategy.READ_WRITE) public class User { @Id @Column(name="id") @GenericGenerator(name="inc50",strategy="increment") @GeneratedValue(generator="inc50") private Integer userID; private String name ; private Integer age; @Column(name="birth") @Temporal(TemporalType.DATE) private Date birthday; public User(){} public User(Integer userID, String name, Integer age, Date birthday) { super(); this.userID = userID; this.name = name; this.age = age; this.birthday = birthday; } public Integer getUserID() { return userID; } public void setUserID(Integer userID) { this.userID = userID; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "User [userID=" + userID + ", name=" + name + ", age=" + age + ", birthday=" + birthday + "]"; } }
二级缓存ecache:
<?xml version="1.0" encoding="UTF-8"?> <!-- maxEntriesLocalHeap: 在内存中缓存的element的最大数目。 maxEntriesLocalDisk: 在磁盘上缓存的element的最大数目,默认值为0,表示不限制。 eternal: 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效, 如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断。 <persistence strategy="localTempSwap"/> 内存存满后将数据存入硬盘 timeToIdleSeconds="10" 缓存空闲时间 默认值0 一直存活 timeToLiveSeconds="15" 缓存最大存活时间 默认值0 一直存活 diskExpiryThreadIntervalSeconds:磁盘数据的有效时间 memoryStoreEvictionPolicy="LFU" FIFO ,first in first out (先进先出). LFU , Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存。 LRU ,Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了, 而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。 --> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <!-- <diskStore path="java.io.tmpdir"/> --> <diskStore path="E:\\cache4"/> <defaultCache maxEntriesLocalHeap="2" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </defaultCache> </ehcache>
hibernate配置文件:
<?xml version="1.0" encoding="utf-8"?> <!-- 文档约束:DTD Document Type Definition :标签,属性,层级,先后顺序 --> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- 数据库连接相关 --> <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521:xe</property> <property name="hibernate.connection.driver_class">oracle.jdbc.OracleDriver</property> <property name="hibernate.connection.username">hr</property> <property name="hibernate.connection.password">hr</property> <!-- 最大连接数 --> <property name="hibernate.c3p0.max_size">3</property> <!-- 最下连接数 --> <property name="hibernate.c3p0.min_size">1</property> <!-- 获取链接等待超时时间: 毫秒--> <property name="checkoutTimeout">3000</property> <!-- 指示连接池的类型 --> <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property> <!-- hibernate自身配置信息 方言:指示数据库种类,便于hibernate对不同的数据库做出相应的适应。 --> <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property> <!-- 控制台打印出sql语句 --> <property name="hibernate.show_sql">true</property> <!-- 格式化sql语句 --> <property name="hibernate.format_sql">true</property> <!-- 禁用掉javaEE6的bean-validator --> <property name="javax.persistence.validation.mode">none</property> <!-- getCurrentSession --> <property name="hibernate.current_session_context_class">thread</property> <!-- 开启二级缓存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 二级缓存类别:EhCache,OSCache,JbossCache --> <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property> <!-- 开启查询缓存 --> <property name="hibernate.cache.use_query_cache">true</property> <!-- 映射信息的注册 --> <mapping class="com.c50.entity.User"></mapping> </session-factory> </hibernate-configuration>
测试代码:
package com.c50.test; import java.util.Iterator; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.Transaction; import org.junit.Test; import com.c50.entity.User; import com.c50.util.HibernateUtil; /** * * @author Administrator * 缓存测试 */ public class TestCache { /** * 一级缓存测试:不能跨事务 */ @Test public void testFirstLevelCache(){ Session session=HibernateUtil.getCurrentSession(); Transaction tx=session.beginTransaction(); //1.检查缓存,没有可用的数据,转向数据库,查询出的数据,进入一级缓存 User user=(User)session.get(User.class,1); System.out.println(user); user.setAge(41); //2.再次发起同样的查询,检查缓存,有可用的数据,则不在查询数据库。 User user2=(User)session.get(User.class,1); System.out.println(user2); tx.commit(); /*Session session2=HibernateUtil.openSession(); Transaction tx2=session2.beginTransaction(); User user3=(User)session2.get(User.class,109); System.out.println(user3); tx2.commit(); session2.close();*/ } /** * 一级缓存测试:HQL语句执行查询不能检查缓存 */ @Test public void testFirstLevelCache2(){ Session session=HibernateUtil.getCurrentSession(); Transaction tx=session.beginTransaction(); //1.检查缓存,没有可用的数据,转向数据库,查询出的数据,进入一级缓存 User user=(User)session.get(User.class,1); //2.list和uniqueResult不能检查缓存,直接转向数据库。 String hql="from User u where u.name='zhangjifeng'"; Query query=session.createQuery(hql); User user2=(User)query.uniqueResult(); System.out.println(user); System.out.println(user2); tx.commit(); } /** * 一级缓存测试:iterate(): */ @Test public void testFirstLevelCache3(){ Session session=HibernateUtil.getCurrentSession(); Transaction tx=session.beginTransaction(); //1.HQL查询多条数据,数据进入一级缓存 String hql="from User u where u.name like ?"; Query query=session.createQuery(hql); query.setString(0,"ji%"); query.list(); String hql2="from User u where u.name like ?"; Query query2=session.createQuery(hql); query2.setString(0,"ji%"); //迭代器中存储ID //只查询ID,通过ID检查缓存。 Iterator it=query2.iterate(); while(it.hasNext()){ User user=(User)it.next(); System.out.println(user); } tx.commit(); } /** * 二级缓存测试 */ @Test public void testSecondLevelCache(){ Session session=HibernateUtil.openSession(); Transaction tx=session.beginTransaction(); User user=(User)session.get(User.class,1); User user3=(User)session.get(User.class,2); System.out.println(user); tx.commit(); session.close(); Session session2=HibernateUtil.openSession(); Transaction tx2=session2.beginTransaction(); User user2=(User)session2.get(User.class,1); user2.setName("second222"); System.out.println(user2); tx2.commit(); session2.close(); } /** * 查询缓存测试 */ @Test public void testQueryCache(){ Session session=HibernateUtil.openSession(); Transaction tx=session.beginTransaction(); String hql="select u.name,u.age from User u where u.age>=?"; Query query=session.createQuery(hql); query.setInteger(0,20); //本次查询要使用查询缓存 query.setCacheable(true); //检查查询缓存,没有可用的缓存,则转向数据库,将结果缓存在查询缓存中:{HQL:结果(字段)} query.list(); tx.commit(); session.close(); // Session session3=HibernateUtil.openSession(); // Transaction tx3=session3.beginTransaction(); // session3.save(new User()); // tx3.commit(); // session3.close(); Session session2=HibernateUtil.openSession(); Transaction tx2=session2.beginTransaction(); String hql2="select u.name,u.age from User u where u.age>=?"; Query query2=session2.createQuery(hql2); query2.setInteger(0,20); //本次查询要使用查询缓存 query2.setCacheable(true); //检查查询缓存,发现可用缓存数据,直接使用,不在查询数据库 query2.list(); tx2.commit(); session2.close(); } /** * 查询缓存+二级缓存 */ @Test public void testQueryCache2(){ Session session=HibernateUtil.openSession(); Transaction tx=session.beginTransaction(); String hql="from User u where u.age>=?"; Query query=session.createQuery(hql); query.setInteger(0,20); //本次查询要使用查询缓存 query.setCacheable(true); //检查查询缓存,没有可用数据,则转向数据库,获得一个实体User,将{HQL:User的ID}存入查询缓存 // 将{User的ID:User}存入二级缓存 query.list(); tx.commit(); session.close(); Session session2=HibernateUtil.openSession(); Transaction tx2=session2.beginTransaction(); String hql2="from User u where u.age>=?"; Query query2=session2.createQuery(hql2); query2.setInteger(0,20); //本次查询要使用查询缓存 query2.setCacheable(true); //检查查询缓存,获得缓存数据:User的ID,通过UserID检查二级缓存,如果有数据,则直接使用,否则 //以各个ID为条件分别发起查询 query2.list(); tx2.commit(); session2.close(); } }