Hibernate中的持久态实际上就利用了缓存的处理机制。
所谓的缓存实际上指的就是一种查询性能的有效提升手段。可以避免数据重复查询所带来的性能开销,通过此方式来进行数据的快速存储,但是并不意味着缓存一定能够提升性能,相反,如果处理不当,也可能造成致命伤害。
在项目的开发之中有两种比较常见的缓存组件:OSCache、EHCache,其中Hibernate之中主要使用的是EHCache组件,之所以使用此组件主要是考虑到简洁问题,在缓存之中至少需要考虑以下几种情况: (1)缓存的个数; (2)缓存数据如何让保存; (3)缓存数据如何同步。
在Hibernate之中主要支持的是两类缓存: (1)Session级缓存(一级缓存、first_level_cache):在使用get()或load()方法的时候,对象实际上一直就处于持久态的状态,那么这一点就是一级缓存的配置,但是如果Session关闭了,那么一级缓存的内容就将消失,一级缓存永远存在。 (2)SessionFactory级缓存(二级缓存、second_level_cache):指的是跨越Session的缓存存在,可以在多个Session对象之中共享数据,二级缓存是需要进行额外配置的。
并且如果你的Hibernate需要使用缓存,那么必须在创建项目的时候就准备好相应的第三方组件包。
一级缓存是Session级的缓存,永远都会存在,也就是说如果用户执行了save()或者是get()方法后,那么这个对象在Session关闭前会一直被保留下来。 范例:观察一级缓存
package org.lks.test; import java.text.SimpleDateFormat; import org.lks.dbc.HibernateSessionFactory; import org.lks.pojo.Member; public class TestSessionCacheDemoA { public static void main(String[] args) throws Exception { Member vo = new Member(); vo.setMid("1001"); vo.setMname("hhy"); vo.setMage(20); vo.setMsalary(2000.0); vo.setMbirthday(new SimpleDateFormat("yyyy-MM-dd").parse("1999-08-21")); vo.setMnote("big fool!"); HibernateSessionFactory.getSession().save(vo); //变为了持久态 HibernateSessionFactory.getSession().beginTransaction().commit(); // 在保存完成之后,并且没有关闭Session的前提下发出了一个查询操作 Member temp = (Member)HibernateSessionFactory.getSession().get(Member.class, "1001"); System.out.println(temp); } } Hibernate: insert into hedb.member (mage, mbirthday, mname, mnote, msalary, mid) values (?, ?, ?, ?, ?, ?) org.lks.pojo.Member@6b5f8707保存完成之后随后进行查询,但是发现Hibernate并没有发出另外一条SELECT语句,因为此时的对象一旦被保存了,就将在缓存中存在,而如果缓存中存在的内容,进行查询的时候将不在重复发出查询。 范例:观察缓存存在
package org.lks.test; import org.lks.dbc.HibernateSessionFactory; import org.lks.pojo.Member; public class TestSessionCacheDemoB { public static void main(String[] args) throws Exception { // 在保存完成之后,并且没有关闭Session的前提下发出了一个查询操作 Member vo = (Member)HibernateSessionFactory.getSession().get(Member.class, "1001"); //此时没有关闭Session,再一次发出同样的查询操作 Member temp = (Member)HibernateSessionFactory.getSession().get(Member.class, "1001"); } } Hibernate: select 第一次查询发出的查询指令 member0_.mid as mid1_0_0_, member0_.mage as mage2_0_0_, member0_.mbirthday as mbirthda3_0_0_, member0_.mname as mname4_0_0_, member0_.mnote as mnote5_0_0_, member0_.msalary as msalary6_0_0_ from hedb.member member0_ where member0_.mid=?一级缓存的操作控制都在Session接口里面定义了,定义有如下的几个操作方法: (1)清空所有缓存数据:public void clear() (2)强制刷新缓冲区: (3)清除一个缓存对象: 范例:观察如何清除一个对象
package org.lks.test; import org.lks.dbc.HibernateSessionFactory; import org.lks.pojo.Member; public class TestSessionCacheDemoC { public static void main(String[] args) throws Exception { // 在保存完成之后,并且没有关闭Session的前提下发出了一个查询操作 Member vo = (Member)HibernateSessionFactory.getSession().get(Member.class, "1001"); //将之前查询出来的对象(是保存在缓存中的)进行缓存的清除 HibernateSessionFactory.getSession().evict(vo); //此时没有关闭Session,再一次发出同样的查询操作,此时缓存没有数据 Member temp = (Member)HibernateSessionFactory.getSession().get(Member.class, "1001"); } } Hibernate: select member0_.mid as mid1_0_0_, member0_.mage as mage2_0_0_, member0_.mbirthday as mbirthda3_0_0_, member0_.mname as mname4_0_0_, member0_.mnote as mnote5_0_0_, member0_.msalary as msalary6_0_0_ from hedb.member member0_ where member0_.mid=? Hibernate: select member0_.mid as mid1_0_0_, member0_.mage as mage2_0_0_, member0_.mbirthday as mbirthda3_0_0_, member0_.mname as mname4_0_0_, member0_.mnote as mnote5_0_0_, member0_.msalary as msalary6_0_0_ from hedb.member member0_ where member0_.mid=?此时的代码发出了两条查询指令,原因在于:第一个对象查询出来之后将其进行了删除缓存对象的操作,所以在第二次查询同样主键的时候,如果发现此时数据在缓存里面不存在,那么就将重新发出查询指令。
实际意义:如果说现在使用了save()方法保存了新增加的数据,那么按照道理来讲,这些数据应该都会被缓存(不关闭Session的前提下),那么也就是说如果缓存的数据很多,那么程序就会出现问题。 范例:观察问题引出
package org.lks.test; import java.text.SimpleDateFormat; import org.lks.dbc.HibernateSessionFactory; import org.lks.pojo.Member; public class TestSessionCacheDemoD { public static void main(String[] args) throws Exception { for(int i = 0; i < 10; i++){ Member vo = new Member(); vo.setMid("200" + i); vo.setMname("hhy"); vo.setMage(20); vo.setMsalary(2000.0); vo.setMbirthday(new SimpleDateFormat("yyyy-MM-dd").parse("1999-08-21")); vo.setMnote("big fool!"); HibernateSessionFactory.getSession().save(vo); //变为了持久态 } HibernateSessionFactory.getSession().beginTransaction().commit(); // 在保存完成之后,并且没有关闭Session的前提下发出了一个查询操作 Member vo = (Member)HibernateSessionFactory.getSession().get(Member.class, "2001"); //此时没有关闭Session,再一次发出同样的查询操作,此时缓存没有数据 Member temp = (Member)HibernateSessionFactory.getSession().get(Member.class, "2002"); } } Hibernate: insert into hedb.member (mage, mbirthday, mname, mnote, msalary, mid) values (?, ?, ?, ?, ?, ?) x10此时保存了10条记录,但是由于save()方法保存对象之后,对象会保存在缓存之中,所以此时如果发出了查询指定的主键对象的时候就不会再出现查询操作了。
那么请思考一下,如果说现在要求你批量保存100000行记录呢?按照此时的道理来讲,这100000行的记录都要缓存起来,这样明显会造成一个非常危险的举动,所以在进行实际的开发过程之中,必须要考虑数据的分批处理。 范例:批量数据增加
package org.lks.test; import java.text.SimpleDateFormat; import org.lks.dbc.HibernateSessionFactory; import org.lks.pojo.Member; public class TestSessionCacheDemoE { public static void main(String[] args) throws Exception { for(int i = 0; i < 200; i++){ Member vo = new Member(); vo.setMid("lk" + i); vo.setMname("hhy"); vo.setMage(20); vo.setMsalary(2000.0); vo.setMbirthday(new SimpleDateFormat("yyyy-MM-dd").parse("1999-08-21")); vo.setMnote("big fool!"); HibernateSessionFactory.getSession().save(vo); //变为了持久态 if(i != 0 && i % 10 == 0){ //每10条记录进行一次缓冲区的刷新 HibernateSessionFactory.getSession().flush(); HibernateSessionFactory.getSession().beginTransaction().commit(); HibernateSessionFactory.getSession().clear(); } } HibernateSessionFactory.getSession().beginTransaction().commit(); } }如果以后遇见了数据的批量处理,那么一定要注意按照数据的分批保存以及分批清空的原则来进行处理。
面试题:如果使用Hibernate进行数据批处理的话,需要注意哪些问题? (1)Hibernate之中一级的Session缓存会永远存在,也就是说如果使用了save()方法,那么对象信息将一直保留到Session关闭,所以如果存放的内容太多,则有可能造成程序出现问题; (2)可以使用Session接口中的flush()和clear()两个方法进行强制性的缓存清空,保证缓存的容量不会占用过多。
在Hibernate的默认状态下,每一个Session缓存的数据是不能够进行共享的。 范例:观察不同Session的数据访问
package org.lks.test; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.lks.dbc.HibernateSessionFactory; import org.lks.pojo.Member; public class TestSessionFactoryCacheDemoA { public static void main(String[] args) throws Exception { // 取得的是SessionFactory类对象 SessionFactory factory = HibernateSessionFactory.getSessionFactory(); Session sessionA = factory.openSession(); // 取得一个Session对象 Member mea = sessionA.get(Member.class, "3161301220"); // 数据的查询 System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++"); Session sessionB = factory.openSession(); // 取得另一个Session对象 Member meb = sessionB.get(Member.class, "3161301220"); // 查询同一个数据 } } Hibernate: select member0_.mid as mid1_0_0_, member0_.mage as mage2_0_0_, member0_.mbirthday as mbirthda3_0_0_, member0_.mname as mname4_0_0_, member0_.mnote as mnote5_0_0_, member0_.msalary as msalary6_0_0_ from hedb.member member0_ where member0_.mid=? +++++++++++++++++++++++++++++++++++++++++++++++++++++++ Hibernate: select member0_.mid as mid1_0_0_, member0_.mage as mage2_0_0_, member0_.mbirthday as mbirthda3_0_0_, member0_.mname as mname4_0_0_, member0_.mnote as mnote5_0_0_, member0_.msalary as msalary6_0_0_ from hedb.member member0_ where member0_.mid=?此时一共产生了两行查询语句,也就是说,每当使用不同的Session查询数据的时候都会重复的执行,也就得出一个结论,此时不同的Session无法实现数据的共享操作。
那么如果现在希望即使在不同的Session上也可以实现数据的缓存操作,那么就需要使用二级缓存完成。但是二级缓存必须由用户单独进行配置,在之前所设置的一系列的开发包,都属于二级缓存的准备。
范例:定义一个缓存的配置文件——ehcache.xml文件
<ehcache> <!-- 指的是缓存的临时保存目录 --> <diskStore path="java.io.tmpdir"/> <!-- 此处进行默认的缓存配置 --> <defaultCache maxElementsInMemory="10000" →可以缓存的最大的POJO类对象个数 eternal="false" →是否允许该缓存自动失效 timeToIdleSeconds="120" → 最小的失效时间 timeToLiveSeconds="120" →最大的保存时间 overflowToDisk="true" /> →如果保存过多则自动利用磁盘存储缓存 </ehcache>虽然项目里面已经存在有缓存的支持包以及缓存的配置文件,但是如果要想让缓存可以使用,还需要再hibernate.cfg.xml文件里面配置使用的缓存类型。
<property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.EhCacheRegionFactory </property>此时已经可以使用二级缓存进行开发操作。但是与一级缓存相比,二级缓存的使用更加麻烦,因为可能在实际的开发之中,并不一定所有的POJO类都可能需要使用到二级缓存。所以来讲还应该在支持缓存的位置上进行启用,有以下三种启用的操作形式: (1)如果你的项目是基于*.hbm.xml文件配置的,则可以在Member.hbm.xml文件中增加以下配置:
<cache usage="read-only" />(2)如果现在使用的是Annotation,则可以利用以下注解在POJO类中配置;
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)(3)但是很多时候考虑到你项目有可能经常会需要修改,如果都去找每一个文件配置会比较麻烦,那么也可以直接在hibernate.cfg.xml文件里面进行综合的配置:
<class-cache usage="read-only" class="org.lks.pojo.Member" />第三种形式只需要维护一个文件就可以了,不需要到处维护。
范例:观察二级缓存
package org.lks.test; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.lks.dbc.HibernateSessionFactory; import org.lks.pojo.Member; public class TestSessionFactoryCacheDemoA { public static void main(String[] args) throws Exception { // 取得的是SessionFactory类对象 SessionFactory factory = HibernateSessionFactory.getSessionFactory(); Session sessionA = factory.openSession(); // 取得一个Session对象 Member mea = sessionA.get(Member.class, "3161301220"); // 数据的查询 System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++"); Session sessionB = factory.openSession(); // 取得另一个Session对象 Member meb = sessionB.get(Member.class, "3161301220"); // 查询同一个数据 } } Hibernate: select member0_.mid as mid1_0_0_, member0_.mage as mage2_0_0_, member0_.mbirthday as mbirthda3_0_0_, member0_.mname as mname4_0_0_, member0_.mnote as mnote5_0_0_, member0_.msalary as msalary6_0_0_ from hedb.member member0_ where member0_.mid=? +++++++++++++++++++++++++++++++++++++++++++++++++++++++此时的代码只发出了一次的查询操作,所以二级缓存的配置起作用了。
实际上对于缓存的操作一共有以下几种常用模式: (1)READ_ONLY(只读,唯一推荐使用的):只读缓存,在数据查询的时候使用,将查询结果保存在缓存之中,以供其它的Session使用; (2)READ_WRITE(读、写缓存):当数据库里面的数据发生变化之后,那么可以进行及时的数据追踪。但是这种操作性能是最差的; (3)NONSTRICT_READ_WRITE(不严格的读写操作):在数据库发生变化之后不会立即进行缓存的更新,而是等待一段时间之后再进行数据的更新操作; (4)TRANSACTIONAL(事务处理缓存):如果数据库中的数据发生了回滚操作后,内存中的数据也要一起发生回滚操作。
如果项目中配置了二级缓存之后,对于程序而言,就相当于只要是通过Session查询的数据都会默认的保存在二级缓存之中,以供其它的Session使用,但是这些都是默认的配置形式,而用户也可以根据自己的需要来配置是否需要进行二级缓存,而配置的方法主要是在Session接口中:public void setCacheMode(CacheMode cacheMode)
而在setCacheMode()方法里面会接收一个CacheMode类型的参数,那么这属于枚举类型,所以在这个枚举类里面就定义有如下的几种缓存模式: (1)public static final CacheMode GET:指的是从缓存取数据,但是不保存; (2)public static final CacheMode PUT:指的是向缓存中保存数据,但是不取; (3)public static final CacheMode NORMAL:正常进行缓存数据的读、写(默认情况); (4)public static final CacheMode REFRESH:可以针对于缓存中的重复数据进行刷新操作。 范例:观察缓存模式
package org.lks.test; import org.hibernate.CacheMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.lks.dbc.HibernateSessionFactory; import org.lks.pojo.Member; public class TestSessionFactoryCacheDemoA { public static void main(String[] args) throws Exception { // 取得的是SessionFactory类对象 SessionFactory factory = HibernateSessionFactory.getSessionFactory(); Session sessionA = factory.openSession(); // 取得一个Session对象 sessionA.setCacheMode(CacheMode.GET); Member mea = sessionA.get(Member.class, "3161301220"); // 数据的查询 System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++"); Session sessionB = factory.openSession(); // 取得另一个Session对象 Member meb = sessionB.get(Member.class, "3161301220"); // 查询同一个数据 } } Hibernate: select member0_.mid as mid1_0_0_, member0_.mage as mage2_0_0_, member0_.mbirthday as mbirthda3_0_0_, member0_.mname as mname4_0_0_, member0_.mnote as mnote5_0_0_, member0_.msalary as msalary6_0_0_ from hedb.member member0_ where member0_.mid=? +++++++++++++++++++++++++++++++++++++++++++++++++++++++ Hibernate: select member0_.mid as mid1_0_0_, member0_.mage as mage2_0_0_, member0_.mbirthday as mbirthda3_0_0_, member0_.mname as mname4_0_0_, member0_.mnote as mnote5_0_0_, member0_.msalary as msalary6_0_0_ from hedb.member member0_ where member0_.mid=?因为此时第一个查询使用的缓存模式为GET,所以内容不会向二级缓存中进行保存。 范例:也可以控制第二个Session,让其不读取缓存
package org.lks.test; import org.hibernate.CacheMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.lks.dbc.HibernateSessionFactory; import org.lks.pojo.Member; public class TestSessionFactoryCacheDemoA { public static void main(String[] args) throws Exception { // 取得的是SessionFactory类对象 SessionFactory factory = HibernateSessionFactory.getSessionFactory(); Session sessionA = factory.openSession(); // 取得一个Session对象 Member mea = sessionA.get(Member.class, "3161301220"); // 数据的查询 System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++"); Session sessionB = factory.openSession(); // 取得另一个Session对象 sessionB.setCacheMode(CacheMode.PUT); Member meb = sessionB.get(Member.class, "3161301220"); // 查询同一个数据 } } Hibernate: select member0_.mid as mid1_0_0_, member0_.mage as mage2_0_0_, member0_.mbirthday as mbirthda3_0_0_, member0_.mname as mname4_0_0_, member0_.mnote as mnote5_0_0_, member0_.msalary as msalary6_0_0_ from hedb.member member0_ where member0_.mid=? +++++++++++++++++++++++++++++++++++++++++++++++++++++++ Hibernate: select member0_.mid as mid1_0_0_, member0_.mage as mage2_0_0_, member0_.mbirthday as mbirthda3_0_0_, member0_.mname as mname4_0_0_, member0_.mnote as mnote5_0_0_, member0_.msalary as msalary6_0_0_ from hedb.member member0_ where member0_.mid=?此时同样发出了两次查询语句,因为第一次查询的时候正常读写,而第二次查询的时候没有读,直接进行了写操作。
在之前所做的全部查询都有一个特点,都是基于Session完成的,但是从实际的开发来讲,数据的查询首选的一定是Query接口,那么在默认状态下,能否实现缓存呢? 范例:观察问题
package org.lks.test; import org.hibernate.Query; import org.hibernate.SessionFactory; import org.lks.dbc.HibernateSessionFactory; public class TestQueryCacheDemoA { public static void main(String[] args) { SessionFactory factory = HibernateSessionFactory.getSessionFactory(); Query queryA = factory.openSession().createQuery("FROM Member AS m WHERE m.mid=?"); queryA.setParameter(0, "3161301220"); queryA.setCacheable(true); System.out.println(queryA.uniqueResult()); System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++"); Query queryB = factory.openSession().createQuery("FROM Member AS m WHERE m.mid=?"); queryB.setParameter(0, "3161301220"); queryA.setCacheable(true); System.out.println(queryB.uniqueResult()); } } Hibernate: select member0_.mid as mid1_0_, member0_.mage as mage2_0_, member0_.mbirthday as mbirthda3_0_, member0_.mname as mname4_0_, member0_.mnote as mnote5_0_, member0_.msalary as msalary6_0_ from hedb.member member0_ where member0_.mid=? org.lks.pojo.Member@293cde83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ Hibernate: select member0_.mid as mid1_0_, member0_.mage as mage2_0_, member0_.mbirthday as mbirthda3_0_, member0_.mname as mname4_0_, member0_.mnote as mnote5_0_, member0_.msalary as msalary6_0_ from hedb.member member0_ where member0_.mid=? org.lks.pojo.Member@293cde83此时可以发现,如果利用的是Query的查询,那么所配置的二级缓存无效,也就是说此时并没有使用到缓存的配置。
因为Query接口默认情况下并没有启动缓存的支持,在Query接口中定义有如下的方法: (1)配置是否支持缓存:public Query<R> setCacheable(boolean cacheable) (2)配置缓存模式:public Query<R> setCacheMode(CacheMode cacheMode) 默认情况下Hibernate同样是没有启用查询缓存支持,所以如果要想使用查询缓存,还需要在hibernate.cfg.xml文件里面增加一个属性。
<property name="cache.use_query_cache">true</property>范例:启用查询缓存
package org.lks.test; import org.hibernate.Query; import org.hibernate.SessionFactory; import org.lks.dbc.HibernateSessionFactory; public class TestQueryCacheDemoA { public static void main(String[] args) { SessionFactory factory = HibernateSessionFactory.getSessionFactory(); Query queryA = factory.openSession().createQuery("FROM Member AS m WHERE m.mid=?"); queryA.setParameter(0, "3161301220"); queryA.setCacheable(true); System.out.println(queryA.uniqueResult()); System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++"); Query queryB = factory.openSession().createQuery("FROM Member AS m WHERE m.mid=?"); queryB.setParameter(0, "3161301220"); queryB.setCacheable(true); System.out.println(queryB.uniqueResult()); } } Hibernate: select member0_.mid as mid1_0_, member0_.mage as mage2_0_, member0_.mbirthday as mbirthda3_0_, member0_.mname as mname4_0_, member0_.mnote as mnote5_0_, member0_.msalary as msalary6_0_ from hedb.member member0_ where member0_.mid=? org.lks.pojo.Member@6b474074 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ org.lks.pojo.Member@2a22ad2b对于Criteria的查询也都有相应的方法支持,使用的形式也是一样的。