新随笔  :: 订阅 订阅  :: 管理

Hibernate3回顾-6-hibernate缓存(性能优化策略)

Posted on 2014-04-02 15:53  redcoatjk  阅读(238)  评论(0编辑  收藏  举报

主要来源:   

 http://blog.csdn.net/csh624366188/article/details/7612142  (比较详细)

 http://www.cnblogs.com/200911/archive/2012/10/09/2716873.html

 http://dododo1234321-163-com.iteye.com/blog/1828173

什么是缓存?

  缓存是介于物理数据源与应用程序之间,是对数据库中的数据复制一份临时放在内存中的容器,其作用是为了减少应用程序对物理数据源访问的次数,从而提高了应用程序的运行性能。Hibernate在进行读取数据的时候,根据缓存机制在相应的缓存中查询,如果在缓存中找到了需要的数据(我们把这称做缓存命 中"),则就直接把命中的数据作为结果加以利用,避免了大量发送SQL语句到数据库查询的性能损耗。

  缓存策略提供商:

提供了HashTable缓存,EHCacheOSCacheSwarmCachejBoss Cathe2,这些缓存机制,其中EHCacheOSCache是不能用于集群环境(Cluster Safe)的,而SwarmCachejBoss Cathe2是可以的。HashTable缓存主要是用来测试的,只能把对象放在内存中,EHCacheOSCache可以把对象放在内存(memory)中,也可以把对象放在硬盘(disk)上(为什么放到硬盘上?上面解释了)

  

6. Hibernate缓存

总体分为:  一级缓存、二级缓存、查询缓存。 

缓存是为了提高性能

      变化不是很大,相对静态的对象放入缓存

 

6.1 一级缓存 

 

Hibernate向我们提供的主要的操纵数据库的接口,Session就是其中的一个,它提供了基本的增,删,改,查方法(如session.save , session.delete , session.update , session.get , session.load方法).对应的它有一个缓存机制(一级缓存) ,能够按照某个时间点,按照缓存中的持久化对象属性的变化来更新数据库,这就是Session的缓存清理过程.

 

一级缓存的生命周期跟Session的生命周期一样,所以也可以理解为一级Hibernate缓存是session缓存,因此将一级缓存称为session级缓存或事务级缓存。

 TIPS: 每一个Hibernate Session实例和一个数据库事务绑定通常将每一个Hibernate Session实例和一个数据处理库事务绑定就是说,每执行一个数据库事务(操作),都应该先创建一个新的Hibernate Session实例.
如果事务执行中出现异常,应该撤消事务.不论事务执行成功与否,最后都应该调用Session的close()方法,从而释放Hibernate Session实例占用的资源.

 

6.1.1 Session缓存的作用

将相关数据映射为对象放到内存中,减轻对数据库的操作次数。

一般情况下调用session.get / load 通过id加载对象的时候, Hibernate会首先到一级缓存中查找是否存在该对象/对象的代理.如没有则在二级缓存中查找. 如找到则直接返回.如找不到才会去数据库查询.

所以在第一次session.get()查询出某对象后, 只要同一session并未close. 再次调用get/load查询同一id的对象.数据库不再发出SQL语句.(相关细节涉及get/load的异同, 以及延迟加载).

总而言之: 一级缓存的主要作用为

(1)减少访问数据库的频率。应用程序从内存中读取持久化对象的速度显然比到数据库中查询数据的速度快多了,因此Session的缓存可以提高数据访问的性能。

(2)保证缓存中的对象与数据库中的相关记录保持同步。当缓存中持久化对象的状态发生了变化,Session并不会立即执行相关的SQL语句,这使得Session能够把几条相关的SQL语句合并为一条SQL语句,以便减少访问数据库的次数,从而提高应用程序的性能。

 

 

6.1.2 Session缓存(一级缓存)的实现

 

  一级缓存采用的是key-value的Map方式来实现的,在缓存实体对象时,对象的主关键字ID是Map的key,实体对象就是对应的值。所以说,一级缓存是以实体对象为单位进行存储的,在访问的时候使用的是主关键字ID。
虽然,Hibernate对一级缓存使用的是自动维护的功能,没有提供任何配置功能,但是可以通过Session中所提供的方法来对一级缓存的管理进行手工干预。

 

6.1.3 Session的清理缓存

    (即一级缓存清理并同步更新数据库)

  清理缓存是指按照缓存中对象的状态的变化来同步更新数据库。

 6.1.3.1 Session会在下面的时间点清理缓存(一级缓存将变化同步至数据库):

1.当应用程序调用org.hibernate.Transaction的commit()方法的时候,commit()方法先清理缓存(通过自动调用session.flush),然后再向  

   数据库提交事务。
2.当应用程序显式调用Session的flush()方法的时候,其实这个方法我们几乎很少用到,因为我们一般都是在完成一个事务才去清理缓存,提交数

   据更改,这样我们直接提交事务就可以。

 

 6.1.3.2 支持一级缓存的方法

* get()

使用例子

1 Student s = (Student)session.get(Student.class, 1);  
2             System.out.println(s.getName());  
3            System.out.println("---------------------");  
4             s = (Student)session.load(Student.class, 1);  
5             System.out.println(s.getName());

跟1中load一样,只发出一条sql语句。

* load()

使用例子

1 Student student = (Student)session.createQuery("from Student s where s.id=1").iterate().next();  
2            System.out.println("student.name=" + student.getName());  
3               
4             //会发出查询id的sql,不会发出查询实体对象的sql,因为iterate使用缓存  
5            student = (Student)session.createQuery("from Student s where s.id=1").iterate().next();  
6            System.out.println("student.name=" + student.getName()); 

 

* iterate(查询实体对象)

iterate方法只对实体对象查询才支持一级缓存,如果使用iterate来查询对象里面的相关属性,则查询的时候不支持一级缓存。

 

1 String name = (String)session.createQuery("select s.name from Student s where s.id=1").iterate().next();  
2             System.out.println("student.name=" + name);  
3               
4             //iterate查询普通属性,一级缓存不会缓存,所以发出sql  
5             //一级缓存是缓存实体对象的  
6             name = (String)session.createQuery("select s.name from Student s where s.id=1").iterate().next();  
7             System.out.println("student.name=" + name); 

由于iterate()方法查询实体对象属性,一级缓存不会产生作用,所以发出两条sql语句。

 

 6.1.3.3 如何手动干预,管理一级缓存

 *session.flush()

在hibernate中也存在flush这个功能,在默认的情况下session.commit()之前时,其实执行了一个flush命令。

Session.flush功能:

①   清理缓存;

②   执行sql(确定是执行SQL语句(确定生成update、insert、delete语句等),然后执行SQL语句。)

 

*session.clear()

  无论是Load 还是 Get 都会首先查找缓存(一级缓存) 如果没有,才会去数据库查找,调用Clear() 方法,可以强制清除Session缓存(不管缓存与数据库的同步)

 

*session.evict()

  将某个对象从hibernate  session剔除,使该对象成为游离态。该对象之后的变更不会同步至DB,而evict之前对象的变化在事务提交时仍

      将影响DB。

 

 6.1.3.4 大批量处理

在进行大批量数据一次性更新的时候,会占用非常多的内存来缓存被更新的对象。这时就应该阶段性地调用clear()方法来清空一级缓存中的对象,控制一级缓存的大小,以避免产生内存溢出的情况。
Hibernate大批量更新时缓存的处理方法:

          * 先flush,再clear

 1 for(int i=0;i<10000;i++)  
 2             {  
 3                 Student s = new Student();  
 4                 s.setName("s"+i);  
 5                 session.save(s);  
 6                 if(i%20==0)  
 7                 {  
 8                     session.flush();  
 9                     session.clear();  
10                }  
11            } 

在数据量比较大的情况下管理一级缓存的做法,一般都是设定一定数量的记录给更新或者保存等操作之后,避免一次性大量的实体数据入库导致内存溢出,所以才去先是用第8行的flush和第9行的clear方法来实现比较好的缓存管理。这里

在数据量特别大的时候,可以使用jdbc来实现,因为hibernate不太适合于数据量特别大的场合使用,如果连jdbc都满足不了数据量的效率要求,只好利用相关的数据库机制来实现。

如果数据量特别大,考虑采用jdbc实现,如果jdbc也不能满足要求可以考虑采用数据本身的特定导入工具。

 以下跳过Hibernate API  而直接通过JDBC API来执行:

1 Session session=SessionFactory.openSession();
2 Transaction tx =session.beginTransaction();
3 Connection conn =session.connection();
4 PreparedStatement    pstmt = conn.prepareStatement("update users set age=age+1 "+"where age >0");
5 pstmt.executeUpdate();
6 tx.commit();

虽说这是通过JDBC API.但本质上还是通过Hibernater Transaction的事务这个接口来声明事务的边界的...

其实最好的解决方法就是以创建存储过程,用底层的数据库运行..这样性能好,速度快....

 我就简单的以Oracle数据库为例子.创建一个名为UserUpdate的存储过程...然后在程序中进行调用... 

1 UserUpdate的存储过程代码:
2 create or  replace procadure UserUpdate(u_age in number) as
3 begin  update users set age=age+1 where age>u_age;
4 end;  

 下面的是在程序中如何调用我们命名的存储过程 

1 Session session =SessionFactory.openSession();
2    Transaction tx =session.beginTransaction();
3    Connection conn=session.connection();
4    String str="{call UserUpdate(?)}";
5    CallableStatement cstmt= conn.prepareCall(str);
6    cstmt.setInt(1,0);
7    cstmt.executeUpdate();
8    tx.commit();

注意.开源的MySQL中不支持存储过程的..
用JDBC API的好处是这样的..
它不用把大批量的数据事先加载到内存中,然后再进行更新与修改..所以不会消耗大量内存....
(小程序中是看不出什么差别的..当数据的记录达到一定的数据量的时候自然会发现用Hibernate API 与JDBC API的差别)
在一个就是只能一条记录进行批量更新..不像Hibernate中更新每一条的..