需求:
A查询数据库中id为1的雇员信息,B接着也查询id为1的雇员信息,我们知道数据库查询的成本是很高的,考虑: A查询后, B再查询的时候直接使用A查询出的结果,我们可以用缓存的方式实现,即是把A查询的结果保存在某个地方,当B查询的时候先去找这个地方,如果找到了直接返回,如果没找到再去数据库查询,
使用缓存的条件:
1,读取大于修改
2,数据量不能超过内存容量
3,数据要有独享的控制, (已存在缓存,不能另一边直接修改数据)
4,能容忍一定的无效数据
缓存可以简单的看成一个Map,通过key在缓存里面找value,下面写一个简单示例来说明原理。
public class Main {
/**模拟缓存存放池*/
private static Map map = new HashMap();
public static void main(String[] args) {
/**第一次查询*/
Emp emp1=getEmpById(7788L);
System.out.println("employee name:"+emp1.getEname());
/**第二次查询*/
Emp emp2=getEmpById(7788L);
System.out.println("employee name:"+emp2.getEname());
}
public static Emp getEmpById(Long id) {
Session session = null;
Transaction tx = null;
Emp emp = null;
try {
/**产生标识某emp对象的key*/
String empKey=Emp.class.getName() + id;
/**在Map里取出这个对象*/
emp=(Emp)map.get(empKey);
/**判断是否取到*/
if(emp!=null)
/**取到对象后返回,这里就不用再次查询数据库了*/
return emp;
/**如果没有取到*/
session = HibernateSessionFactory.getSession();
/**开始事务*/
tx = session.beginTransaction();
/**到数据库里查询*/
emp = (Emp) session.get(Emp.class, id);
/**保存到Map里,下次查询就不需要再次到数据库里查询了*/
map.put(empKey, emp);
tx.commit();
} catch (Exception ex)
{ if(tx!=null)
tx.rollback();
ex.printStackTrace();
} finally {
if(session!=null)
session.close();
}
/**返回结果*/
return emp;
}
}
执行两次查询可发现只执行了一次数据库查询
Hibernate:
select
emp0_.EMPNO as EMPNO1_0_,
.....
from
SCOTT.EMP emp0_
where
emp0_.EMPNO=?
employee name:SCOTT
employee name:SCOTT
在上面演示原理的例子中,可能你会想到如果第二次查询的时候数据库的数据被更新了,那么查询出来的数据就是无效数据了,是的,但是在Hibernate内部已经为我们解决了这个问题,在更新的时候也去更新了缓存,一级缓存是这样,二级缓存表现得更明显..
因此我们按以下的操作只会有一条查询语句输出,这是一级缓存的用法
session = HibernateSessionFactory.getSession();
tx = session.beginTransaction();
emp = (Emp) session.get(Emp.class, id);
emp = (Emp) session.get(Emp.class, id);
tx.commit();
hibernate的session提供了一级缓存,save,update,saveOrUpdate,load,get,list,iterate,lock这些方法都会将对象放入一级缓存中,evict,clear方法用来清除缓存,每个session,对同一个id进行两次load或get,不会发送两条sql给数据库,但是session关闭的时候,一级缓存就失效了,一级缓存是在一个session有效,由此不能满足我们开始提到的需求.
二级缓存是SessionFactory级别的全局缓存,它底下可以使用不同的缓存类库,(Hibernate不自己实现缓存功能,它交给第三方类库实现),比如ehcache、oscache等,
首先加入对应的jar包,这里使用oscache-2.1.jar,还要加入ehcache.xml配置文件,完整的
hibernate包下有这个文件
maxElementsInMemory="10000" <!-- 缓存最大数目 -->
eternal="false" <!-- 缓存是否持久 -->
overflowToDisk="true"<!-- 是否保存到磁盘,当系统当机时-->
timeToIdleSeconds="300" <!-- 当缓存闲置n秒后销毁 -->
timeToLiveSeconds="180" <!-- 当缓存存活n秒后销毁-->
我们在hibernate.properties中找到这样一段,这里即是实现缓存支持的第三方类库选项
#hibernate.cache.use_structured_entries true
## choose a cache implementation
#hibernate.cache.provider_class org.hibernate.cache.EhCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.EmptyCacheProvider
hibernate.cache.provider_class org.hibernate.cache.HashtableCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.TreeCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.OSCacheProvider
#hibernate.cache.provider_class org.hibernate.cache.SwarmCacheProvider
一般我们使用oscache,接下来开始配置使用,修改hibernate.cfg.xml
<mapping resource="com/sxy/dao/Dept.hbm.xml" />
<mapping resource="com/sxy/dao/Emp.hbm.xml" />
<!-- 打开二级缓存-->
<property name="cache.use_second_level_cache">true</property>
<!-- 配置使用哪一个缓存 -->
<property name="cache.provider_class">
org.hibernate.cache.OSCacheProvider
</property>
<!--需要缓存的类,可添加很多个,注意:这句必须放在mapping的下面 -->
<class-cache class="com.sxy.dao.Emp" usage="read-write"/>
配置需要缓存的类,还有个方法,在类的映射文件里配置
<hibernate-mapping>
<class name="com.sxy.dao.Emp" table="EMP" schema="SCOTT" >
<cache usage="read-write"/>
<id name="empno" type="java.lang.Long">
......
usage的取值:
read-only 只读的,不可修改,不需要更新缓存,效率高
read-writ 读写,能保证并发修改的正确性,大多时候采用的,因此效率有一定影响
nonstrict-read-write 非严格读写,并发修改可能性很小,对出现错误可以忍受,
Transactional 很少使用
下面我们来看一下效果,执行以下代码,可以发现只输出了一条查询语句.
这两次查询明显不是在一个Session内执行的,第一次查询后session已关闭,
public static void main(String[] args) {
/**第一次查询*/
getEmpById(7788L);
/**第二次查询*/
getEmpById(7788L);
}
public static Emp getEmpById(Long id) {
Session session = null;
Transaction tx = null;
Emp emp = null;
try {
session = HibernateSessionFactory.getSession();
tx = session.beginTransaction();
emp = (Emp) session.get(Emp.class, id);
tx.commit();
} catch (Exception ex)
{ if(tx!=null)
tx.rollback();
ex.printStackTrace();
} finally {
if(session!=null)
session.close();
}
return emp;
}
设置产生统计信息,监控内部信息
<property name="generate_statistics">true</property>
使用方法
public static void main(String[] args) {
/**第一次查询*/
getEmpById(7788L);
/**第二次查询*/
getEmpById(7788L);
SessionFactory sf=
HibernateSessionFactory.getSessionFactory();
/**获得统计信息,里面包含了很多内部信息*/
Statistics st=sf.getStatistics();
System.out.println("加入次数:"+st.getSecondLevelCachePutCount());
System.out.println("命中次数:"+st.getSecondLevelCacheHitCount());
System.out.println("错过次数:"+st.getSecondLevelCacheMissCount());
}
输出:
加入次数:1 //第一次查询缓存里没有,往里面加入一次
命中次数:1 //第二次查询缓存里已经存在,命中一次
错过次数:1 //第一次查询在缓存里没找到,错过一次
对象被加入到了二级缓存,但也加入到了一级缓存,只是一级缓存的生命周期太短无法利用
当使用save方法时,native的主键生成器不会把对象加入到二级缓存中,SessionFactory类提供了一些方法,用于清理缓存。
默认情况下 query查询不会利用缓存,也就是说它不到缓存里去找,而是直接访问数据库,因为这样的查询命中率是很低的,如果需要实现可以通过下面的方式
修改hibernate.cfg.xml
<property name="cache.use_query_cache">true</property>
public static void main(String[] args)
{
search();
search();
}
public static void search() {
Session session = null;
Transaction tx = null;
try {
session = HibernateSessionFactory.getSession();
tx = session.beginTransaction();
Query query=session.createQuery("from Emp");
/**设置支持查询缓存*/
query.setCacheable(true);
List<Emp> list=query.list();
for(Emp e:list)
System.out.println(e.getEname());
tx.commit();
} finally {
if (session != null)
session.close();
}
}
如果不设置“查询缓存”,那么hibernate只会缓存使用load()方法获得的单个持久化对象,如果想缓存使用findall()、list()、Iterator()、createCriteria()、createQuery()等方法获得的数据结果集的话,就需要设置hibernate.cache.use_query_cache true才行。
©石头