`

Hibernate所有缓存机制详解(转)

阅读更多

Hibernate 所有缓存机制详解

原文地址:http://www.blogjava.net/tbwshc/articles/380013.html

 

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方法查询的数据。我们在查询时很少出现完全相同条件的查询,这也就是命中率低,这样缓存里的数据总是变 化的,所以说意义不大。除非是多次查询都是查询相同条件的数据,也就是说返回的结果总是一样,这样配置查询缓存才有意义。

分享到:
评论

相关推荐

    Hibernate缓存深入详解

    Hibernate缓存深入详解,详细介绍Hibernate缓存机制。 1.Hibernate缓存概述 2.Hibernate一级缓存(Session缓存) 3.Hibernate二级缓存 4.查询缓存 5.二级缓存的高级应用(分布式缓存)

    详解Hibernate的缓存机制及其配置代码

    Hibernate在查询数据时,首先到缓存中去查找,如果找到就直接使用,找不到的时候就会从物理数据源中检索,所以,把频繁使用的数据加载到缓存区后,就可以大大减少应用程序对物理数据源的访问,使得程序的运行性能...

    Hibernate框架 jar 架包 开发详解

    Hibernate 简介 Hibernate 开发流程 Hibernate 配置文件 Hibernate 核心接口和类 Hibernate ORM映射 HQL Hibernate 懒加载机制与抓取策略 Hibernate 缓存 Hibernate 锁机制

    Hibernate学习笔记和资料

    hibernate概述,hibernate入门Demo,hibernate配置文件详解(全局配置,实体类映射配置),配置实体规则,核心API详解(Configuration,sessionFactory,session,Transaction),hibernate中的对象状态以及刷新能缓存机制 ...

    Hibernate框架中的缓存技术详解

    主要介绍了Hibernate框架中的缓存技术,结合实例形式详细分析了Hibernate框架缓存机制的原理与具体使用技巧,需要的朋友可以参考下

    精通 Hibernate:Java 对象持久化技术详解(第2版).part2

     8.2.2 脏检查及清理缓存的机制  8.3 Java对象在Hibernate持久化层的状态  8.3.1 临时对象的特征  8.3.2 持久化对象的特征  8.3.3 被删除对象的特征  8.3.4 游离对象的特征  8.4 Session接口的详细用法  ...

    Hibernate详解

    该文档详细讲解了Hibernate的几个核心技术:Hibernate的配置文件、缓存机制、查询策略、事务管理、HQL和QBC、一对多、一对一、多对多映射关系、继承映射等,并且还有配有实例。

    hibernate 框架详解

    Hibernate的统计(statistics)机制 4.5. 日志 4.6. 实现NamingStrategy 4.7. XML配置文件 4.8. J2EE应用程序服务器的集成 4.8.1. 事务策略配置 4.8.2. JNDI绑定的SessionFactory 4.8.3. JTA和Session的...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part4

     8.2.2 脏检查及清理缓存的机制  8.3 Java对象在Hibernate持久化层的状态  8.3.1 临时对象的特征  8.3.2 持久化对象的特征  8.3.3 被删除对象的特征  8.3.4 游离对象的特征  8.4 Session接口的详细用法  ...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part3

     8.2.2 脏检查及清理缓存的机制  8.3 Java对象在Hibernate持久化层的状态  8.3.1 临时对象的特征  8.3.2 持久化对象的特征  8.3.3 被删除对象的特征  8.3.4 游离对象的特征  8.4 Session接口的详细用法  ...

    SSH框架详解

    Hibernate工作原理、Hibernate的核心组件、Persistent Object、Hibernate的运行过程、Hibernate缓存的作用、Hibernate缓存分类、Hibernate优点、 Hibernate缺点、spring原理 、AOP(Aspect-Oriented Programming): ...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part1.rar

     8.2.2 脏检查及清理缓存的机制  8.3 Java对象在Hibernate持久化层的状态  8.3.1 临时对象的特征  8.3.2 持久化对象的特征  8.3.3 被删除对象的特征  8.3.4 游离对象的特征  8.4 Session接口的详细用法  ...

    低清版 大型门户网站是这样炼成的.pdf

    5.8 hibernate的缓存机制 332 5.8.1 hibernate的缓存分类 332 5.8.2 hibernate的缓存范围 332 5.8.3 hibernate的缓存管理 333 5.8.4 hibernate二级缓存的并发访问策略 333 5.8.5 hibernate的二级缓存配置 334 ...

    mybatis入门到精通详解

    视频包含:mybatis入门;...MyBatis配置文件编写,MyBatis动态SQL,MyBatis缓存机制,MyBatis-Spring整合,MyBatis逆向工程,MyBatis高级映射等等,适合入门级java学员(由于文件过大,此处放网盘下载链接)

    从零开始学Spring Boot

    1.40 Spring Boot集成EHCache实现缓存机制 1.41 Spring Boot分布式Session状态保存Redis 1.42 Spring Boot Shiro权限管理 1.43 Spring Boot Shiro权限管理 1.44 Spring Boot Shiro权限管理 1.45 Spring Boot Shiro...

    Java Web程序设计教程

    10.2hibernate缓存 213 10.2.1缓存的工作原理 213 10.2.2应用一级缓存 214 10.2.3应用二级缓存 214 10.2.4应用第三方缓存 216 10.3项目实战——借还图书 217 本章小结 224 课后练习 224 第11章spring框架...

    Spring.3.x企业应用开发实战(完整版).part2

    17.4.5 使用Hibernate二级缓存 17.5 对持久层进行测试 17.5.1 配置Unitils测试环境 17.5.2 准备测试数据库及测试数据 17.5.3 编写DAO测试基类 17.5.4 编写BoardDao测试用例 17.6 服务层开发 17.6.1 UserService的...

    Spring3.x企业应用开发实战(完整版) part1

    17.4.5 使用Hibernate二级缓存 17.5 对持久层进行测试 17.5.1 配置Unitils测试环境 17.5.2 准备测试数据库及测试数据 17.5.3 编写DAO测试基类 17.5.4 编写BoardDao测试用例 17.6 服务层开发 17.6.1 UserService的...

Global site tag (gtag.js) - Google Analytics