Hibernate 缓存机制GOOD

26 篇文章 0 订阅

http://my.oschina.net/u/1398304/blog/261589


hibernate  提供的一级缓存

hibernate是一个线程对应一个session,一个线程可以看成一个用户。也就是说session级缓存(一级缓存)只能给一个线程用,别的线程用不了,一级缓存就是和线程绑定了。

hibernate一级缓存生命周期很短,和session生命周期一样,一级缓存也称session级的缓存或事务级缓存。如果tb事务提交或回滚了,我们称session就关闭了,生命周期结束了。

缓存和连接池的区别缓存和池都是放在内存里,实现是一样的,都是为了提高性能的。但有细微的差别,池是重量级的,里面的数据是一样的,比如一个池里放100个Connection连接对象,这个100个都是一样的。缓存里的数据,每个都不一样。比如读取100条数据库记录放到缓存里,这100条记录都不一样。

缓存主要是用于查询

//同一个session中,发出两次load方法查询

Student student = (Student)session.load(Student.class, 1);

System.out.println("student.name=" + student.getName());

//不会发出查询语句,load使用缓存

student = (Student)session.load(Student.class, 1);

System.out.println("student.name=" + student.getName());

第二次查询第一次相同的数据,第二次load方法就是从缓存里取数据,不会发出sql语句到数据库里查询。

//同一个session,发出两次get方法查询

Student student = (Student)session.get(Student.class, 1);

System.out.println("student.name=" + student.getName());

//不会发出查询语句,get使用缓存

student = (Student)session.get(Student.class, 1);

System.out.println("student.name=" + student.getName());

第二次查询第一次相同的数据,第二次不会发出sql语句查询数据库,而是到缓存里取数据。

//同一个session,发出两次iterate查询实体对象

Iterator iter = session.createQuery

("from Student s where s.id<5").iterate();

while (iter.hasNext()) {

Student student = (Student)iter.next();

System.out.println(student.getName());

}

System.out.println("--------------------------------------");

//它会发出查询id的语句,但不会发出根据id查询学生的语句,因为iterate使用缓存

iter = session.createQuery("from Student s where s.id<5").iterate();

while (iter.hasNext()) {

Student student = (Student)iter.next();

System.out.println(student.getName());

}

一说到iterater查询就要立刻想起:iterater查询在没有缓存的情况下会有N+1的问题。

执行上面代码查看控制台的sql语句,第一次iterate查询会发出N+1条sql语句,第一条sql语句查询所有的id,然后根据id查询实体对象,有N个id就发出N条语句查询实体。

第二次iterate查询,却只发一条sql语句,查询所有的id,然后根据id到缓存里取实体对象,不再发sql语句到数据库里查询了。

//同一个session,发出两次iterate查询,查询普通属性

Iterator iter = session.createQuery(

"select s.name from Student s where s.id<5").iterate();

while (iter.hasNext()) {

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

System.out.println(name);

}

System.out.println("--------------------------------------");

//iterate查询普通属性,一级缓存不会缓存,所以发出查询语句

//一级缓存是缓存实体对象的

iter = session.createQuery

("select s.name from Student s where s.id<5").iterate();

while (iter.hasNext()) {

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

System.out.println(name);

}

执行代码看控制台sql语句,第一次发出N+1条sql语句,第二次还是发出了N+1条sql语句。因为一级缓存只缓存实体对象tb不会缓存普通属性,所以第二次还是发出sql查询语句。

//两个session,每个session发出一个load方法查询实体对象

try {

session = HibernateUtils.getSession();

session.beginTransaction();

Student student = (Student)session.load(Student.class, 1);

System.out.println("student.name=" + student.getName());

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

第二个session调用load方法

try {

session = HibernateUtils.getSession();

session.beginTransaction();

Student student = (Student)session.load(Student.class, 1);

//会发出查询语句,session间不能共享一级缓存数据

//因为他会伴随着session的消亡而消亡

System.out.println("student.name=" + student.getName());

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

第一个session的load方法会发出sql语句查询实体对象,第二个session的load方法也会发出sql语句查询实体对象。因为session间不能共享一级缓存的数据,所以第二个session的load方法查询相同的数据还是要到数据库中查询,因为它找不到第一个session里缓存的数据。

//同一个session,先调用save方法再调用load方法查询刚刚save的数据

Student student = new Student();

student.setName("张三");

//save方法返回实体对象的id

Serializable id = session.save(student);

student = (Student)session.load(Student.class, id);

//不会发出查询语句,因为save支持缓存

System.out.println("student.name=" + student.getName());

先save保存实体对象,再用load方法查询刚刚save的实体对象,则load方法不会发出sql语句到数据库查询的,而是到缓存里取数据,因为save方法也支持缓存。当然前提是同一个session。

//大批量的数据添加

for (int i=0; i<100; i++) {

Student student = new Student();

student.setName("张三" + i);

session.save(student);

//每20条更新一次

if (i % 20 == 0) {

session.flush();

//清除缓存的内容

session.clear();

}

}

大批量数据添加时,会造成内存溢出的,因为save方法支持缓存,每save一个对象就往缓存里放,如果对象足够多内存肯定要溢出。一般的做法是先判断一下save了多少个对象,如果save了20个对象就对缓存手动的清理缓存,这样就不会造成内存溢出。

注意:清理缓存前,要手动调用flush方法同步到数据库,否则save的对象就没有保存到数据库里。

注意:大批量数据的添加还是不要使用hibernate,这是hibernate弱项。可以使用jdbc(速度也不会太快,只是比hibernate好一点),或者使用工具产品来实现,比如oracle的Oracle SQL Loader,导入数据特别快。 



Hibernate   二级缓存

二级缓存需要sessionFactory来管理,它是进程级的缓存,所有人都可以使用,它是共享的。

二级缓存比较复杂,一般用第三方产品。hibernate提供了一个简单实现,用Hashtable做的,只能作为我们的测试使用,商用还是需要第三方产品。

使用缓存,肯定是长时间不改变的数据,如果经常变化的数据放到缓存里就没有太大意义了。因为经常变化,还是需要经常到数据库里查询,那就没有必要用缓存了。

hibernate做了一些优化,和一些第三方的缓存产品做了集成。老师采用EHCache缓存产品。

和EHCache二级缓存产品集成:EHCache的jar文件在hibernate的lib里,我们还需要设置一系列的缓存使用策略,需要一个配置文件ehcache.xml来配置。这个文件放在类路径下。

//默认配置,所有的类都遵循这个配置

<defaultCache

        //缓存里可以放10000个对象

        maxElementsInMemory="10000"

        //过不过期,如果是true就是永远不过期

        eternal="false"

        //一个对象被访问后多长时间还没有访问就失效(120秒还没有再次访问就失效)

        timeToIdleSeconds="120"

        //对象存活时间(120秒),如果设置永不过期,这个就没有必要设了

        timeToLiveSeconds="120"

        //溢出的问题,如果设成true,缓存里超过10000个对象就保存到磁盘里

        overflowToDisk="true"

        />

我们也可以对某个对象单独配置:

<cache name="com.bjpowernode.hibernate.Student"

        maxElementsInMemory="100"

        eternal="false"

        timeToIdleSeconds="10000"

        timeToLiveSeconds="10000"

        overflowToDisk="true"

        />

还需要在hibernate.cfg.xml配置文件配置缓存,让hibernate知道我们使用的是那个二级缓存。

<!-- 配置缓存提供商 -->

<property name="hibernate.cache.provider_class">

org.hibernate.cache.EhCacheProvider</property>

<!-- 启用二级缓存,这也是它的默认配置 -->

<property name="hibernate.cache.use_second_level_cache">

true</property>

启用二级缓存的配置可以不写的,因为默认就是true开启二级缓存。

必须还手动指定那些实体类的对象放到缓存里在hibernate.cfg.xml里:

//在<sessionfactory>标签里,在<mapping>标签后配置

<class-cache class="com.bjpowernode.hibernate.Student"

usage="read-only"/>

或者在实体类映射文件里:

//在<class>标签里,<id>标签前配置

<cache usage="read-only"/>

usage属性表示使用缓存的策略,一般优先使用read-only,表示如果这个数据放到缓存里了,则不允许修改,如果修改就会报错。这就要注意我们放入缓存的数据不允许修改。因为放缓存里的数据经常修改,也就没有必要放到缓存里。

使用read-only策略效率好,因为不能改缓存。但是可能会出现脏数据的问题,这个问题解决方法只能依赖缓存的超时,比如上面我们设置了超时为120秒,120后就可以对缓存里对象进行修改,而在120秒之内访问这个对象可能会查询脏数据的问题,因为我们修改对象后数据库里改变了,而缓存却不能改变,这样造成数据不同步,也就是脏数据的问题。

第二种缓存策略read-write,当持久对象发生变化,缓存里就会跟着变化,数据库中也改变了。这种方式需要加解锁,效率要比第一种慢。

还有两种策略,请看hibernate文档,最常用还是第一二种策略。

二级缓存测试代码演示:注意上面我们讲的两个session分别调用load方法查询相同的数据,第二个session的load方法还是发了sql语句到数据库查询数据,这是因为一级缓存只在当前session中共享,也就是说一级缓存不能跨session访问。

//开启二级缓存,二级缓存是进程级的缓存,可以共享

//两个session分别调用load方法查询相同的实体对象

try {

session = HibernateUtils.getSession();

session.beginTransaction();

Student student = (Student)session.load(Student.class, 1);

System.out.println("student.name=" + student.getName());

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

try {

session = HibernateUtils.getSession();

session.beginTransaction();

Student student = (Student)session.load(Student.class, 1);

//不会发出查询语句,因为配置二级缓存,session可以共享二级缓存中的数据

//二级缓存是进程级的缓存

System.out.println("student.name=" + student.getName());

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

如果开启了二级缓存,那么第二个session调用的load方法查询第一次查询的数据,是不会发出sql语句查询数据库的,而是去二级缓存中取数据。

//开启二级缓存

//两个session分别调用get方法查询相同的实体对象

try {

session = HibernateUtils.getSession();

session.beginTransaction();

Student student = (Student)session.get(Student.class, 1);

System.out.println("student.name=" + student.getName());

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

try {

session = HibernateUtils.getSession();

session.beginTransaction();

Student student = (Student)session.get(Student.class, 1);

//不会发出查询语句,因为配置二级缓存,session可以共享二级缓存中的数据

//二级缓存是进程级的缓存

System.out.println("student.name=" + student.getName());

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

注意:二级缓存必须让sessionfactory管理,让sessionfactory来清除二级缓存。sessionFactory.evict(Student.class);//清除二级缓存中所有student对象,sessionFactory.evict(Student.class,1);//清除二级缓存中id为1的student对象。

如果在第一个session调用load或get方法查询数据后,把二级缓存清除了,那么第二个session调用load或get方法查询相同的数据时,还是会发出sql语句查询数据库的,因为缓存里没有数据只能到数据库里查询。

我们查询数据后会默认自动的放到二级和一级缓存里,如果我们想查询的数据不放到缓存里,也是可以的。也就是说我们可以控制一级缓存和二级缓存的交换。

session.setCacheMode(CacheMode.IGNORE);禁止将一级缓存中的数据往二级缓存里放。

还是用上面代码测试,在第一个session调用load方法前,执行session.setCacheMode(CacheMode.IGNORE);这样load方法查询的数据不会放到二级缓存里。那么第二个session执行load方法查询相同的数据,会发出sql语句到数据库中查询,因为二级缓存里没有数据,一级缓存因为不同的session不能共享,所以只能到数据库里查询。

上面我们讲过大批量的数据添加时可能会出现溢出,解决办法是每当天就20个对象后就清理一次一级缓存。如果我们使用了二级缓存,光清理一级缓存是不够的,还要禁止一二级缓存交互,在save方法前调用session.setCacheMode(CacheMode.IGNORE)。

二级缓存也不会存放普通属性的查询数据,这和一级缓存是一样的,只存放实体对象。session级的缓存对性能的提高没有太大的意义,因为生命周期太短了。



Hibernate    查询缓存

一级缓存和二级缓存都只是存放实体对象的,如果查询实体对象的普通属性的数据,只能放到查询缓存里,查询缓存还存放查询实体对象的id。

查询缓存的生命周期不确定,当它关联的表发生修改,查询缓存的生命周期就结束。这里表的修改指的是通过hibernate修改,并不是通过数据库客户端软件登陆到数据库上修改。

hibernate的查询缓存默认是关闭的,如果要使用就要到hibernate.cfg.xml文件里配置:

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

并且必须在程序中手动启用查询缓存,在query接口中的setCacheable(true)方法来启用。

//关闭二级缓存,没有开启查询缓存,采用list方法查询普通属性

//同一个sessin,查询两次

List names = session.createQuery("select s.name from Student s")

.list();

for (int i=0; i<names.size(); i++) {

String name = (String)names.get(i);

System.out.println(name);

}

System.out.println("-----------------------------------------");

//会发出sql语句

names = session.createQuery("select s.name from Student s")

.setCacheable(true)

.list();

for (int i=0; i<names.size(); i++) {

String name = (String)names.get(i);

System.out.println(name);

}

上面代码运行,由于没有使用查询缓存,而一、二级缓存不会缓存普通属性,所以第二次查询还是会发出sql语句到数据库中查询。

现在开启查询缓存,关闭二级缓存,并且在第一次的list方法前调用setCacheable(true),并且第二次list查询前也调用这句代码,可以写出下面这样:

List names = session.createQuery("select s.name from Student s")

.setCacheable(true)

.list();

其它代码不变,运行代码后发现第二次list查询普通属性没有发出sql语句,也就是说没有到数据库中查询,而是到查询缓存中取数据。

//开启查询缓存,关闭二级缓存,采用list方法查询普通属性

//在两个session中调用list方法

try {

session = HibernateUtils.getSession();

session.beginTransaction();

List names = session.createQuery("select s.name from Student s")

.setCacheable(true)

.list();

for (int i=0; i<names.size(); i++) {

String name = (String)names.get(i);

System.out.println(name);

}

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

System.out.println("----------------------------------------");

try {

session = HibernateUtils.getSession();

session.beginTransaction();

//不会发出查询语句,因为查询缓存和session的生命周期没有关系

List names = session.createQuery("select s.name from Student s")

.setCacheable(true)

.list();

for (int i=0; i<names.size(); i++) {

String name = (String)names.get(i);

System.out.println(name);

}

session.getTransaction().commit();

}catch(Exception e) {

e.printStackTrace();

session.getTransaction().rollback();

}finally {

HibernateUtils.closeSession(session);

}

运行结果是第二个session发出的list方法查询普通属性,没有发出sql语句到数据库中查询,而是到查询缓存里取数据,这说明查询缓存和session生命周期没有关系

//开启缓存,关闭二级缓存,采用iterate方法查询普通属性

//在两个session中调用iterate方法查询

运行结果是第二个session的iterate方法还是发出了sql语句查询数据库,这说明iterate迭代查询普通属性不支持查询缓存

//关闭查询缓存,关闭二级缓存,采用list方法查询实体对象

//在两个session中调用list方法查询

运行结果第一个session调用list方法查询实体对象会发出sql语句查询数据,因为关闭了二级缓存,所以第二个session调用list方法查询实体对象,还是会发出sql语句到数据库中查询。

//开启查询缓存,关闭二级缓存

//在两个session中调用list方法查询实体对象

运行结果第一个session调用list方法查询实体对象会发出sql语句查询数据库的。第二个session调用list方法查询实体对象,却发出了很多sql语句查询数据库,这跟N+1的问题是一样的,发出了N+1条sql语句。为什么会出现这样的情况呢?这是因为我们现在查询的是实体对象,查询缓存会把第一次查询的实体对象的id放到缓存里,当第二个session再次调用list方法时,它会到查询缓存里把id一个一个的拿出来,然后到相应的缓存里找(先找一级缓存找不到再找二级缓存),如果找到了就返回,如果还是没有找到,则会根据一个一个的id到数据库中查询,所以一个id就会有一条sql语句。

注意:如果配置了二级缓存,则第一次查询实体对象后,会往一级缓存和二级缓存里都存放。如果没有二级缓存,则只在一级缓存里存放。(一级缓存不能跨session共享)

//开启查询缓存,开启二级缓存

//在两个session中调用list方法查询实体对象

运行结果是第一个session调用list方法会发出sql语句到数据库里查询实体对象,因为配置了二级缓存,则实体对象会放到二级缓存里,因为配置了查询缓存,则实体对象所有的id放到了查询缓存里。第二个session调用list方法不会发出sql语句,而是到二级缓存里取数据。

查询缓存意义不大,查询缓存说白了就是存放由list方法或iterate方法查询的数据。我们在查询时很少出现完全相同条件的查询,这也就是命中率低,这样缓存里的数据总是变化的,所以说意义不大。除非是多次查询都是查询相同条件的数据,也就是说返回的结果总是一样,这样配置查询缓存才有意义。

 

1.    Cache简介:
缓存(Cache )是计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝,应用程序在运行时直接读写缓存中的数据,只在某些特定时刻按照缓存中的数据来同步更新数据存储源。
缓存的物理介质通常是内存,而永久性数据存储源的物理介质通常是硬盘或磁盘,应用程序读写内在的速度显然比读写硬盘的速度快,如果缓存中存放的数据量非常大,也会用硬盘作为缓存的物理介质。
缓存的实现不仅需要作为物理介质的硬件,同时还需要用于管理缓存的并发访问和过期等策略的软件。因此,缓存是通过软件和硬件共同实现的。
1.1.    持久化层的缓存的范围
缓存的范围决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。
1) 事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的对象形式。
2) 进程范围:缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于进程的生命周期,进程结束时,缓存也就结束了生命周期。进程范围的缓存可能会存放大量的数据,所以存放的介质可以是内存或硬盘。缓存内的数据既可以是相互关联的对象形式也可以是对象的松散数据形式。松散的对象数据形式有点类似于对象的序列化数据,但是对象分解为松散的算法比对象序列化的算法要求更快。
3) 集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式。
对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问的速度不一定会比直接访问数据库数据的速度快多少。
 
持久化层可以提供多种范围的缓存。如果在事务范围的缓存中没有查到相应的数据,还可以到进程范围或集群范围的缓存内查询,如果还是没有查到,那么只有到数据库中查询。事务范围的缓存是持久化层的第一级缓存,通常它是必需的;进程范围或集群范围的缓存是持久化层的第二级缓存,通常是可选的。
1.2.    持久化层的缓存的并发访问策略
当多个并发的事务同时访问持久化层的缓存的相同数据时,会引起并发问题,必须采用必要的事务隔离措施。
在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。因此可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。
1) 事务型(Transactional)策略:仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。
2) 读写型(read-write)策略:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。
3) 非严格读写型(nonstrict-read-write)策略:不保证缓存与数据库中数据的一致性。如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。
4) 只读型策略(read-only):对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。
事务型并发访问策略是事务隔离级别最高,只读型的隔离级别最低。事务隔离级别越高,并发性能就越低。
2.    Hibernate中的缓存:
Hibernate中提供了两级Cache,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的,一般情况下无需进行干预;第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。
Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存。
2.1.    一级缓存和二级缓存的比较:
  第一级缓存 第二级缓存 
存放数据的形式 相互关联的持久化对象 对象的散装数据 
缓存的范围 事务范围,每个事务都有单独的第一级缓存 进程范围或集群范围,缓存被同一个进程或集群范围内的所有事务共享 
并发访问策略 由于每个事务都拥有单独的第一级缓存,不会出现并发问题,无需提供并发访问策略 由于多个事务会同时访问第二级缓存中相同数据,因此必须提供适当的并发访问策略,来保证特定的事务隔离级别 
数据过期策略 没有提供数据过期策略。处于一级缓存中的对象永远不会过期,除非应用程序显式清空缓存或者清除特定的对象 必须提供数据过期策略,如基于内存的缓存中的对象的最大数目,允许对象处于缓存中的最长时间,以及允许对象处于缓存中的最长空闲时间 
物理存储介质 内存 内存和硬盘。对象的散装数据首先存放在基于内在的缓存中,当内存中对象的数目达到数据过期策略中指定上限时,就会把其余的对象写入基于硬盘的缓存中。 
缓存的软件实现 在Hibernate的Session的实现中包含了缓存的实现 由第三方提供,Hibernate仅提供了缓存适配器(CacheProvider)。用于把特定的缓存插件集成到Hibernate中。 
启用缓存的方式 只要应用程序通过Session接口来执行保存、更新、删除、加载和查询数据库数据的操作,Hibernate就会启用第一级缓存,把数据库中的数据以对象的形式拷贝到缓存中,对于批量更新和批量删除操作,如果不希望启用第一级缓存,可以绕过Hibernate API,直接通过JDBC API来执行指操作。 用户可以在单个类或类的单个集合的粒度上配置第二级缓存。如果类的实例被经常读但很少被修改,就可以考虑使用第二级缓存。只有为某个类或集合配置了第二级缓存,Hibernate在运行时才会把它的实例加入到第二级缓存中。 
用户管理缓存的方式 第一级缓存的物理介质为内存,由于内存容量有限,必须通过恰当的检索策略和检索方式来限制加载对象的数目。Session的evit()方法可以显式清空缓存中特定对象,但这种方法不值得推荐。 第二级缓存的物理介质可以是内存和硬盘,因此第二级缓存可以存放大量的数据,数据过期策略的maxElementsInMemory属性值可以控制内存中的对象数目。管理第二级缓存主要包括两个方面:选择需要使用第二级缓存的持久类,设置合适的并发访问策略:选择缓存适配器,设置合适的数据过期策略。


2.2.    一级缓存的管理:
当应用程序调用Session的save()、update()、savaeOrUpdate()、get()或load(),以及调用查询接口的list()、iterate()或filter()方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate会根据缓存中对象的状态变化来同步更新数据库。
Session为应用程序提供了两个管理缓存的方法:
evict(Object obj):从缓存中清除参数指定的持久化对象。
clear():清空缓存中所有持久化对象。
2.3.    二级缓存的管理:
2.3.1.      Hibernate的二级缓存策略的一般过程如下:
1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。 
2) 把获得的所有数据对象根据ID放入到第二级缓存中。 
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。 
4) 删除、更新、增加数据的时候,同时更新缓存。
  Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache。
2.3.2.      什么样的数据适合存放到第二级缓存中?
1 很少被修改的数据
2 不是很重要的数据,允许出现偶尔并发的数据
3 不会被并发访问的数据
4 参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。
2.3.3.      不适合存放到第二级缓存的数据?
1 经常被修改的数据
2 财务数据,绝对不允许出现并发
3 与其他应用共享的数据。
 
2.3.4.      常用的缓存插件
Hibernater 的二级缓存是一个插件,下面是几种常用的缓存插件:
l EhCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。
l OSCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持。
l SwarmCache:可作为群集范围内的缓存,但不支持Hibernate的查询缓存。
l JBossCache:可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。
 
2.3.5.      配置二级缓存的主要步骤:
1)      选择需要使用二级缓存的持久化类,设置它的命名缓存的并发访问策略。这是最值得认真考虑的步骤。
2)      选择合适的缓存插件,然后编辑该插件的配置文件。
2.4.    使用EhCache配置二级缓存:
2.4.1.      配置准备:
1)      把ehcache-1.2.3.jar加入到当前应用的classpath中。
2)      在hibernate.cfg.xml文件中加入EhCache缓存插件的提供类。
< !--配置缓存插件 -->
< property name="hibernate.cache.provider_class">
    org.hibernate.cache.EhCacheProvider
< /property>

3)      挎贝ehcache.xml文件到类路径(项目工程的src目录下),这个文件在Hibernate安装目录的etc下。
2.4.2.      配置步骤:
Hibernate允许在类和集合的粒度上设置第二级缓存。在映射文件中,< class>和< set>元素都有一个< cache>子元素,这个子元素用来配置二级缓存。
示例:以category(产品类别)和product(产品)的映射为例:
1)      修改要配置缓存的那个持久化类的对象关系映射文件:
Category.hbm.xml
< ?xml version="1.0" encoding="utf-8"?>
< !DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 
< hibernate-mapping>
    < class name="org.qiujy.domain.cachedemo.Category" table="categories">
       < !?
             配置缓存,必须紧跟在class元素后面
            对缓存中的Category对象采用读写型的并发访问策略
        -->
       < cache usage="read-write"/>
       
       < id name="id" type="java.lang.Long">
           < column name="id" />
           < generator class="native" />
       < /id>
       < !-- 配置版本号,必须紧跟在id元素后面 -->
       < version name="version" column="version" type="java.lang.Long" />
       
       < property name="name" type="java.lang.String">
           < column name="name" length="32" not-null="true"/>
       < /property>
       
       < property name="description" type="java.lang.String">
           < column name="description" length="255"/>
       < /property>
       
       < set name="products" table="products" cascade="all" inverse="true">
           < !-- Hibernate只会缓存对象的简单属性的值,
       要缓存集合属性,必须在集合元素中也加入< cache>子元素
       而Hibernate仅仅是把与当前持久对象关联的对象的OID存放到缓存中。
如果希望把整个关联的对象的所有数据都存入缓存,
则要在相应关联的对象的映射文件中配置< cache>元素
           -->
           < cache usage="read-write"/>
           
           < key column="categoryId" not-null="true"/>
           < one-to-many class="org.qiujy.domain.cachedemo.Product"/>
       < /set>
       
    < /class>
< /hibernate-mapping>
 

Product.hbm.xml
< ?xml version="1.0" encoding="utf-8"?>
< !DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 
< hibernate-mapping>
    < class name="org.qiujy.domain.cachedemo.Product" table="products">
       
       < cache usage="read-write"/>
       
       < id name="id" type="java.lang.Long">
           < column name="id" />
           < generator class="native" />
       < /id>
       < !-- 配置版本号,必须紧跟在id元素后面 -->
       < version name="version" column="version" type="java.lang.Long" />
       
       < property name="name" type="java.lang.String">
           < column name="name" length="32" not-null="true"/>
       < /property>
       
       < property name="description" type="java.lang.String">
           < column name="description" length="255"/>
       < /property>
       
       < property name="unitCost" type="java.lang.Double">
           < column name="unitCost" />
       < /property>
       
       < property name="pubTime" type="java.util.Date">
           < column name="pubTime" not-null="true" />
       < /property>
       
       < many-to-one name="category" 
                column="categoryId"
               class="org.qiujy.domain.cachedemo.Category"
               cascade="save-update"
                not-null="true">
        < /many-to-one>
       
    < /class>
< /hibernate-mapping>
 


2)      编辑ehcache.xml文件:
< ehcache>
    < diskStore path="c:\\ehcache\"/> 
    < defaultCache 
        maxElementsInMemory="10000" 
        eternal="false" 
        timeToIdleSeconds="120" 
        timeToLiveSeconds="120" 
        overflowToDisk="true"   
        />
        
    < !-- 设置Category类的缓存的数据过期策略 -->
    < cache name="org.qiujy.domain.cachedemo.Category"
        maxElementsInMemory="100"
        eternal="true"
        timeToIdleSeconds="0"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        />
        
     < !-- 设置Category类的products集合的缓存的数据过期策略 -->
     < cache name="org.qiujy.domain.cachedemo.Category.products"
        maxElementsInMemory="500"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />
        
    < cache name="org.qiujy.domain.cachedemo.Product"
        maxElementsInMemory="500"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />
    
< /ehcache>

配置的元素说明:
元素或属性 描述 
< diskStore> 设置缓存数据文件的存放目录 
< defaultCache> 设置缓存的默认数据过期策略 
< cache> 设定具体的命名缓存的数据过期策略
每个命名缓存代表一个缓存区域,每个缓存区域有各自的数据过期策略。命名缓存机制使得用户能够在每个类以及类的每个集合的粒度上设置数据过期策略。 
cache元素的属性   
name 设置缓存的名字,它的取值为类的全限定名或类的集合的名字 
maxInMemory 设置基于内存的缓存中可存放的对象最大数目 
eternal 设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds和timeToLiveSeconds属性;
默认值是false 
timeToIdleSeconds 设置对象空闲最长时间,超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。
如果此值为0,表示对象可以无限期地处于空闲状态。 
timeToLiveSeconds 设置对象生存最长时间,超过这个时间,对象过期。
如果此值为0,表示对象可以无限期地存在于缓存中。 
overflowToDisk 设置基于内在的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中


3)      写一测试类:
package org.qiujy.test.cache;
 
import java.util.List;
 
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.qiujy.common.HibernateSessionFactory;
import org.qiujy.domain.cachedemo.Product;
 
public class TestCache {
 
       public static void main(String[] args) {
        
              //test cache.........
              Session session2 = HibernateSessionFactory.getSession();
              Transaction tx2 =null;
              
              try{ 
                     tx2 = session2.beginTransaction();
                     
                     List list = session2.createQuery("from Product").list();
                     
                     for(int i = 0 ; i <  list.size(); i++){
                            Product prod = (Product)list.get(i);
                            System.out.println(prod.getName());
                     }
                     
                    tx2.commit();
              }catch(HibernateException e){
                     if(tx2 != null){
                            tx2.rollback();
                     }
                     e.printStackTrace();
              }finally{
                     HibernateSessionFactory.closeSession();
              }
              
              //-------------------
              Session session3 = HibernateSessionFactory.getSession();
              Transaction tx3 =null;
              
              try{ 
                     tx3 = session3.beginTransaction();
                     
                     Product prod = (Product)session3.get(Product.class, new Long(1));
                     System.out.println("从cache中得到,不执行SQL---" + prod.getName());
                  
                    tx3.commit();
              }catch(HibernateException e){
                     if(tx3 != null){
                            tx3.rollback();
                     }
                     e.printStackTrace();
              }finally{
                     HibernateSessionFactory.closeSession();
              }
       }
}

首先数据库插入1000条产品记录和1条类别记录。此1000个产品都属于这一类别。然后执行以上测试类,在Session2中查询所有的产品,输出它的产品名,Session2会把这些数据加载到二级缓存中,由于有1000个对象,而配置中定义内存中只能存放500个,剩下的对象就会写到指定的磁盘目录中缓存起来。所以在磁盘相应位置可看到数据文件:
2.5.    查询缓存(Query Cache):
对于经常使用的查询语句,如果启用了查询缓存,当第一次执行查询语句时,Hibernate会把查询结果存放在第二缓存中。以后再次执行该查询语句时,只需从缓存中获得查询结果,从而提高查询性能。
2.5.1.      查询缓存适用于以下场合:
l 在应用程序运行时经常使用的查询语句。
l 很少对与查询语句关联的数据库数据进行插入、删除或更新操作。
2.5.2.      Hibernate的Query缓存策略的过程如下:
1) Hibernate首先根据这些信息组成一个Query Key,Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。 
2) Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在,那么返回这个结果列表;如果不存在,查询数据库,获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。 
3) Query Key中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的Query Key都要从缓存中清空。
只有当经常使用同样的参数进行查询时,这才会有些用处。 
启用查询缓存的步骤:
1)      配置二级缓存:
Hibernate提供了三种和查询相关的缓存区域:
l 默认的查询缓存区域:org.hibernate.cache.StandardQueryCache
l 用户自定义的查询缓存区域:
l 时间戳缓存区域:org.hibernate.cache.UpdateTimestampCache
默认的查询缓存区域以及用户自定义的查询缓存区域都用于存放查询结果。而时间戳缓存区域存放了对与查询结果相关的表进行插入、更新或删除操作的时间戳。Hibernate通过时间戳缓存区域来判断被缓存的查询结果是否过期。所以,当应用程序对数据库的相关数据做了修改,Hibernate会自动刷新缓存的查询结果。但是如果其他应用程序对数据库的相关数据做了修改,则无法监测,此时必须由应用程序负责监测这一变化,然后手工刷新查询结果。Query接口的setForceCacheRefresh(true)可以手工刷新查询结果。
在ehcache.xml中添加如下配置:
< !-- 设置默认的查询缓存的数据过期策略 -->
    < cache name="org.hibernate.cache.StandardQueryCache" 
       maxElementsInMemory="50"
       eternal="false" 
       timeToIdleSeconds="3600" 
       timeToLiveSeconds="7200" 
       overflowToDisk="true"/>
        
    < !-- 设置时间戳缓存的数据过期策略 -->
    < cache name="org.hibernate.cache.UpdateTimestampsCache" 
       maxElementsInMemory="5000"
       eternal="true" 
       overflowToDisk="true"/>
    
    < !-- 设置自定义命名查询缓存customerQueries的数据过期策略 -->
    < cache name="myCacheRegion"
        maxElementsInMemory="1000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />


2)      打开查询缓存:在hibernate.cfg.xml添加如下配置
< !--启用查询缓存 -->
< property name="cache.use_query_cache">true< /property>


3)      在程序中使用:
虽然按以上设置好了查询缓存,但Hibernate在执行查询语句语句时仍不会启用查询缓存。对于希望启用查询缓存的查询语句,应该调用Query接口的setCacheeable(true)方法:
       测试类如下:
package org.qiujy.test.cache;
 
import java.util.List;
 
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.qiujy.common.HibernateSessionFactory;
import org.qiujy.domain.cachedemo.Product;
 
public class TessQueryCache {
 
       public static void main(String[] args) {
              Session session = HibernateSessionFactory.getSession();
              Transaction tx =null;
              
              try{ 
                     tx = session.beginTransaction(); 
                     Query query = session.createQuery("from Product");
                     //激活查询缓存
                     query.setCacheable(true);
                     //使用自定义的查询缓存区域,若不设置,则使用标准查询缓存区域
                     query.setCacheRegion("myCacheRegion");
                     
                     List list = query.list();
                     for(int i = 0 ; i <  list.size(); i++){
                            Product prod = (Product)list.get(i);
                            System.out.println(prod.getName());
                     }
                     
                    tx.commit();
              }catch(HibernateException e){
                     if(tx != null){
                            tx.rollback();
                     }
                     e.printStackTrace();
              }finally{
                     HibernateSessionFactory.closeSession();
              }
       }
}


缓存是位于应用程序与物理数据源之间,用于临时存放复制数据的内存区域,目的是为了减少应用程序对物理数据源访问的次数,从而提高应用程序的运行性能. 
  Hibernate在查询数据时,首先到缓存中去查找,如果找到就直接使用,找不到的时候就会从物理数据源中检索,所以,把频繁使用的数据加载到缓存区后,就可以大大减少应用程序对物理数据源的访问,使得程序的运行性能明显的提升.
 
  
Hibernate缓存分类: 

Session缓存,一级缓存. 

SessionFactory的缓存分为内置缓存和外置缓存.内置缓存中存放的是SessionFactory对象的一些集合属性包含的数据(映射元素据及预定义SQL语句等),对于应用程序来说,它是只读的.外置缓存中存放的是数据库数据的副本,其作用和一级缓存类似.二级缓存除了以内存作为存储介质外,还可以选用硬盘等外部存储设备. 

Hibernate的缓存范围 

Hibernate的一级缓存和二级缓存都位于均位于持久层,且均用于存放数据库数据的副本,最大的区别就是缓存的范围各不一样. 

缓存的范围分为3类: 

1.事务范围 
   事务范围的缓存只能被当前事务访问,每个事务都有各自的缓存,缓存内的数据通常采用相互关联的对象形式.缓存的生命周期依赖于事务的生命周期,只有当事务结束时,缓存的生命周期才会结束.事务范围的缓存使用内存作为存储介质,一级缓存就属于事务范围. 
2.应用范围 
   应用程序的缓存可以被应用范围内的所有事务共享访问.缓存的生命周期依赖于应用的生命周期,只有当应用结束时,缓存的生命周期才会结束.应用范围的缓存可以使用内存或硬盘作为存储介质,二级缓存就属于应用范围. 
3.集群范围 
   在集群环境中,缓存被一个机器或多个机器的进程共享,缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致,缓存中的数据通常采用对象的松散数据形式. 

  Hibernate的缓存管理 

一级缓存的管理: 

  evit(Object obj)  将指定的持久化对象从一级缓存中清除,释放对象所占用的内存资源,指定对象从持久化状态变为脱管状态,从而成为游离对象. 
  clear()  将一级缓存中的所有持久化对象清除,释放其占用的内存资源 
  contains(Object obj) 判断指定的对象是否存在于一级缓存中. 
  flush() 刷新一级缓存区的内容,使之与数据库数据保持同步. 

  二级缓存的管理: 
   
   evict(Class arg0, Serializable arg1)  将某个类的指定ID的持久化对象从二级缓存中清除,释放对象所占用的资源. 
  

sessionFactory.evict(Customer.class, new Integer(1));


   evict(Class arg0)  将指定类的所有持久化对象从二级缓存中清除,释放其占用的内存资源. 
  

sessionFactory.evict(Customer.class);


   evictCollection(String arg0)  将指定类的所有持久化对象的指定集合从二级缓存中清除,释放其占用的内存资源. 
  

sessionFactory.evictCollection("Customer.orders");



Hibernate的二级缓存的配置 

首先,不是所有的数据都适合放在二级缓存中,看一下,什么样的数据适合放在二级缓存中来?什么样的数据不适合放在二级缓存中来? 
  下面这几种情况就不适合加载到二级缓存中: 
  1.经常被修改的数据 
  2.绝对不允许出现并发访问的数据 
  3.与其他应用共享的数据 
  下面这己种情况合适加载到二级缓存中: 
  1.数据更新频率低 
  2.允许偶尔出现并发问题的非重要数据 
  3.不会被并发访问的数据 
  4.常量数据 
  5.不会被第三方修改的数据 

Hibernate的二级缓存功能是靠配置二级缓存插件来实现的,Hibernate为了集成这些插件,Hibernate提供了org.hibernate.cache.CacheProvider借口,它充当缓存插件与Hibernate之间的适配器 . 

常用的二级缓存插件 
EHCache  org.hibernate.cache.EhCacheProvider 
OSCache  org.hibernate.cache.OSCacheProvider 
SwarmCahe  org.hibernate.cache.SwarmCacheProvider 
JBossCache  org.hibernate.cache.TreeCacheProvider 

简单介绍一下EHCache的配置 
hibernate.cfg.xml 

<hibernate-configuration>
    <session-factory>
       <!-- 设置二级缓存插件EHCache的Provider类-->
       <property name="hibernate.cache.provider_class">
          org.hibernate.cache.EhCacheProvider
       </property>
       <!-- 启动"查询缓存" -->
       <property name="hibernate.cache.use_query_cache">
          true
       </property>
    </session-factory>
  </hibernate-configuration>



ehcache.xml 

<ehcache>
  <!-- maxElementsInMemory为缓存对象的最大数目, eternal设置是否永远不过期,timeToIdleSeconds对象处于空闲状态的最多秒数,timeToLiveSeconds对象处于缓存状态的最多秒数 -->
  <diskStore path="java.io.tmpdir"/>
    <defaultCache maxElementsInMemory="10000" eternal="false"  timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true"/>
</ehcache>



****.hbm.xml 

<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
                            "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
                            "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

<hibernate-mapping>
     
   <class>
       <!-- 设置该持久化类的二级缓存并发访问策略 read-only read-write nonstrict-read-write transactional-->
       <cache usage="read-write"/>    
   </class>

</hibernate-mapping>


 

1.     关于hibernate缓存的问题:

1.1.1.         基本的缓存原理

Hibernate缓存分为二级,第一级存放于session中称为一级缓存,默认带有且不能卸载。

 

第二级是由sessionFactory控制的进程级缓存。是全局共享的缓存,凡是会调用二级缓存的查询方法 都会从中受益。只有经正确的配置后二级缓存才会发挥作用。同时在进行条件查询时必须使用相应的方法才能从缓存中获取数据。比如Query.iterate()方法、loadget方法等。必须注意的是session.find方法永远是从数据库中获取数据,不会从二级缓存中获取数据,即便其中有其所需要的数据也是如此。

 

查询时使用缓存的实现过程为:首先查询一级缓存中是否具有需要的数据,如果没有,查询二级缓存,如果二级缓存中也没有,此时再执行查询数据库的工作。要注意的是:此3种方式的查询速度是依次降低的。

1.2.   存在的问题

1.2.1.      一级缓存的问题以及使用二级缓存的原因

     因为Session的生命期往往很短,存在于Session内部的第一级最快缓存的生命期当然也很短,所以第一级缓存的命中率是很低的。其对系统性能的改善也是很有限的。当然,这个Session内部缓存的主要作用是保持Session内部数据状态同步。并非是hibernate为了大幅提高系统性能所提供的。

为了提高使用hibernate的性能,除了常规的一些需要注意的方法比如:

使用延迟加载、迫切外连接、查询过滤等以外,还需要配置hibernate的二级缓存。其对系统整体性能的改善往往具有立竿见影的效果!

(经过自己以前作项目的经验,一般会有3~4倍的性能提高)

 

1.2.2.      N+1次查询的问题

执行条件查询时,iterate()方法具有著名的 n+1次查询的问题,也就是说在第一次查询时iterate方法会执行满足条件的查询结果数再加一次(n+1)的查询。但是此问题只存在于第一次查询时,在后面执行相同查询时性能会得到极大的改善。此方法适合于查询数据量较大的业务数据。

但是注意:当数据量特别大时(比如流水线数据等)需要针对此持久化对象配置其具体的缓存策略,比如设置其存在于缓存中的最大记录数、缓存存在的时间等参数,以避免系统将大量的数据同时装载入内存中引起内存资源的迅速耗尽,反而降低系统的性能!!!

 

1.3.   使用hibernate二级缓存的其他注意事项:

1.3.1.      关于数据的有效性

另外,hibernate会自行维护二级缓存中的数据,以保证缓存中的数据和数据库中的真实数据的一致性!无论何时,当你调用save()update() saveOrUpdate()方法传递一个对象时,或使用load() get()list()iterate() scroll()方法获得一个对象时该对象都将被加入到Session的内部缓存中。 当随后flush()方法被调用时,对象的状态会和数据库取得同步。

 

也就是说删除、更新、增加数据的时候,同时更新缓存。当然这也包括二级缓存!

 

只要是调用hibernate API执行数据库相关的工作。hibernate都会为你自动保证 缓存数据的有效性!!

 

但是,如果你使用了JDBC绕过hibernate直接执行对数据库的操作。此时,Hibernate不会/也不可能自行感知到数据库被进行的变化改动,也就不能再保证缓存中数据的有效性!!

 

这也是所有的ORM产品共同具有的问题。幸运的是,Hibernate为我们暴露了Cache的清除方法,这给我们提供了一个手动保证数据有效性的机会!!

一级缓存,二级缓存都有相应的清除方法。

 

其中二级缓存提供的清除方法为:

按对象class清空缓存

                按对象class和对象的主键id清空缓存

                清空对象的集合中的缓存数据等。

   

1.3.2.      适合使用的情况

并非所有的情况都适合于使用二级缓存,需要根据具体情况来决定。同时可以针对某一个持久化对象配置其具体的缓存策略。

 

适合于使用二级缓存的情况:

1、数据不会被第三方修改;

 

一般情况下,会被hibernate以外修改的数据最好不要配置二级缓存,以免引起不一致的数据。但是如果此数据因为性能的原因需要被缓存,同时又有可能被第3方比如SQL修改,也可以为其配置二级缓存。只是此时需要在sql执行修改后手动调用cache的清除方法。以保证数据的一致性

 

  2、数据大小在可接收范围之内;

 

     如果数据表数据量特别巨大,此时不适合于二级缓存。原因是缓存的数据量过大可能会引起内存资源紧张,反而降低性能。

 

如果数据表数据量特别巨大,但是经常使用的往往只是较新的那部分数据。此时,也可为其配置二级缓存。但是必须单独配置其持久化类的缓存策略,比如最大缓存数、缓存过期时间等,将这些参数降低至一个合理的范围(太高会引起内存资源紧张,太低了缓存的意义不大)。

 

  3、数据更新频率低;

 

     对于数据更新频率过高的数据,频繁同步缓存中数据的代价可能和 查询缓存中的数据从中获得的好处相当,坏处益处相抵消。此时缓存的意义也不大。

 

 

  4、非关键数据(不是财务数据等)

 

  财务数据等是非常重要的数据,绝对不允许出现或使用无效的数据,所以此时为了安全起见最好不要使用二级缓存。

  因为此时 “正确性”的重要性远远大于 “高性能”的重要性。

 

2.     目前系统中使用hibernate缓存的建议

1.4.   目前情况

 一般系统中有三种情况会绕开hibernate执行数据库操作:

1、多个应用系统同时访问一个数据库

   此种情况使用hibernate二级缓存会不可避免的造成数据不一致的问题,

   此时要进行详细的设计。比如在设计上避免对同一数据表的同时的写入操作,

   使用数据库各种级别的锁定机制等。

 

2、动态表相关

   所谓“动态表”是指在系统运行时根据用户的操作系统自动建立的数据表。

   比如“自定义表单”等属于用户自定义扩展开发性质的功能模块,因为此时数据表是运行时建立的,所以不能进行hibernate的映射。因此对它的操作只能是绕开hibernate的直接数据库JDBC操作。

      如果此时动态表中的数据没有设计缓存,就不存在数据不一致的问题。

   如果此时自行设计了缓存机制,则调用自己的缓存同步方法即可。

3、使用sql对hibernate持久化对象表进行批量删除时

     此时执行批量删除后,缓存中会存在已被删除的数据。

分析: 

   当执行了第3条(sql批量删除)后,后续的查询只可能是以下三种方式:

a. session.find()方法:

根据前面的总结,find方法不会查询二级缓存的数据,而是直接查询数据库。

所以不存在数据有效性的问题。

b. 调用iterate方法执行条件查询时:

根据iterate查询方法的执行方式,其每次都会到数据库中查询满足条件的id值,然后再根据此id 到缓存中获取数据,当缓存中没有此id的数据才会执行数据库查询;

如果此记录已被sql直接删除,则iterate在执行id查询时不会将此id查询出来。所以,即便缓存中有此条记录也不会被客户获得,也就不存在不一致的情况。(此情况经过测试验证)

 

c. getload方法按id执行查询:

 

客观上此时会查询得到已过期的数据。但是又因为系统中执行sql批量删除一般是

针对中间关联数据表,对于

中间关联表的查询一般都是采用条件查询 ,id来查询某一条关联关系的几率很低,所以此问题也不存在!

 

   如果某个值对象确实需要按id查询一条关联关系,同时又因为数据量大使用 sql执行批量删除。当满足此两个条件时,为了保证按id 的查询得到正确的结果,可以使用手动清楚二级缓存中此对象的数据的方法!!

(此种情况出现的可能性较小)

 

1.5.   建议

1、建议不要使用sql直接执行数据持久化对象的数据的更新,但是可以执行 批量删除。(系统中需要批量更新的地方也较少)

 

2、如果必须使用sql执行数据的更新,必须清空此对象的缓存数据。调用

SessionFactory.evict(class)

SessionFactory.evict(class,id)

等方法。

 

3、在批量删除数据量不大的时候可以直接采用hibernate的批量删除,这样就不存在绕开hibernate执行sql产生的缓存数据一致性的问题。

 

4、不推荐采用hibernate的批量删除方法来删除大批量的记录数据。

原因是hibernate的批量删除会执行1条查询语句外加 满足条件的n条删除语句。而不是一次执行一条条件删除语句!!

当待删除的数据很多时会有很大的性能瓶颈!!!如果批量删除数据量较大,比如超过50,可以采用JDBC直接删除。这样作的好处是只执行一条sql删除语句,性能会有很大的改善。同时,缓存数据同步的问题,可以采用 hibernate清除二级缓存中的相关数据的方法。

调用 SessionFactory.evict(class) SessionFactory.evict(class,id)等方法。

 

所以说,对于一般的应用系统开发而言(不涉及到集群,分布式数据同步问题等),因为只在中间关联表执行批量删除时调用了sql执行,同时中间关联表一般是执行条件查询不太可能执行按id查询。所以,此时可以直接执行sql删除,甚至不需要调用缓存的清除方法。这样做不会导致以后配置了二级缓存引起数据有效性的问题。

 

退一步说,即使以后真的调用了按id查询中间表对象的方法,也可以通过调用清除缓存的方法来解决。

 

4、具体的配置方法 

根据我了解的很多hibernate的使用者在调用其相应方法时都迷信的相信“hibernate会自行为我们处理性能的问题”,或者“hibernate会自动为我们的所有操作调用缓存”,实际的情况是hibernate虽然为我们提供了很好的缓存机制和扩展缓存框架的支持,但是必须经过正确的调用其才有可能发挥作用!!所以造成很多使用hibernate的系统的性能问题,实际上并不是hibernate不行或者不好,而是因为使用者没有正确的了解其使用方法造成的。相反,如果配置得当hibernate的性能表现会让你有相当“惊喜的”发现。下面我讲解具体的配置方法.

 Hibernate提供了二级缓存的接口: 
net.sf.hibernate.cache.Provider, 
同时提供了一个默认的 实现net.sf.hibernate.cache.HashtableCacheProvider, 
也可以配置 其他的实现 比如ehcache,jbosscache等。

具体的配置位置位于hibernate.cfg.xml文件中 
<property name="hibernate.cache.use_query_cache">true</property> 
<property name="hibernate.cache.provider_class">net.sf.hibernate.cache.HashtableCacheProvider</property>

很多的hibernate使用者在 配置到 这一步 就以为 完事了, 
注意:其实光这样配,根本 就没有使用hibernate的二级缓存。同时因为他们在使用hibernate时大多时候是马上关闭session,所以,一级缓存也没有起到任何作用。结果就是没有使用任何缓存,所有的hibernate操作都是直接操作的数据库!!性能可以想见。

正确的办法是除了以上的配置外还应该配置每一个vo对象的具体缓存策略,在影射文件中配置。例如:

<hibernate-mapping> 
<class name="com.sobey.sbm.model.entitySystem.vo.DataTypeVO" table="dcm_datatype"> 
<cache usage="read-write"/> 
<id name="id" column="TYPEID" type="java.lang.Long"> 
<generator class="sequence"/> 
</id>

<property name="name" column="NAME" type="java.lang.String"/> 
<property name="dbType" column="DBTYPE" type="java.lang.String"/> 
</class> 
</hibernate-mapping>


关键就是这个<cache usage="read-write"/>,其有几个选择 
read-only,read-write,transactional,等 
然后在执行查询时 注意了 ,如果是条件查询,或者返回所有结果的查询,此时session.find()方法 不会获取缓存中的数据。只有调用query.iterate()方法时才会调缓存的数据。

同时 get 和 load方法 是都会查询缓存中的数据 .

对于不同的缓存框架具体的配置方法会有不同,但是大体是以上的配置


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值