首页 > 代码库 > Hibernate学习---第四节:一级缓存
Hibernate学习---第四节:一级缓存
一、Hibernate 一级缓存的概念:
一级缓存生命周期很短与 session 生命周期一致,所以一级缓存也叫 session 级缓存或事务级缓存。位于缓存中的对象处于持久化状态,它和表中的相关记录对应,session 能够在某些时间点,按照缓存中持久化对象的属性变化来同步数据库中表的记录,这一过程称为清理缓存。
(1)、一级缓存实现原理。session 缓存是由它的实现类 sessionImpl 中定义的一些集合属性构成的,原理是保证有一个引用在关联着某个持久化对象,保持它的生命周期不会结束。
(2)、session 缓存的作用。减少数据库访问,从内存中取数据比从数据库中要快的多。缓存中的数据与数据库中的同步:缓存会把改变的 sql 语句合并,减少访问次数。缓存中的对象存在循环关联时,session 会保证不出现访问对象图的死循环。
session 缓存对象的存储位置:session--> persistenceContext--> entityEntries--> map
二、一级缓存测试小例子,代码如下:
@Testpublic void testSession() { // 从数据库中查询出一条记录,并放入到缓存中 Person p = (Person)session.get(Person.class, 1); System.out.println(p); System.out.println("--------------"); // 当查询相同的记录时会从缓存中直接返回数据,不会发起 sql 查询;前提条件是 在同一个 session 中 Person person = (Person)session.get(Person.class, 1); System.out.println(person);}
测试上面的代码,控制台打印如下:
说明,session 只发起了一次查询。
/** * get 和 load 支持一级缓存 */@Testpublic void testSessionLoad(){ // 从数据库中查询出一条记录,并放入到缓存中 Person p = (Person)session.load(Person.class, 1); System.out.println(p); System.out.println("--------------"); // 当查询相同的记录时会从缓存中直接返回数据,不会发起 sql 查询;前提条件是 在同一个 session 中 Person person = (Person)session.load(Person.class, 1); System.out.println(person);}
如果执行以上代码,控制台的打印与上面一样,说明 get 和 load 一样,支持一级缓存。
@Testpublic void testSession() { // 从数据库中查询出一条记录,并放入到缓存中 Person p = (Person)session.get(Person.class, 1); Person p1 = (Person)session.get(Person.class, 2); System.out.println(p+"----1----"+p1); System.out.println("--------------"); // 清除 session 缓存 session.clear(); // 剔除持久化对象 p session.evict(p); // 当查询相同的记录时会从缓存中直接返回数据,不会发起 sql 查询;前提条件是 在同一个 session 中 Person person = (Person)session.get(Person.class, 1); Person p2 = (Person)session.get(Person.class, 2); System.out.println(person+"----2----"+p2);}
clear() 清除 session 缓存,evict() 剔除某个持久化对象
如果一次性向数据库写入多条数据,那么可以用以下方式处理:
/** * 巧妙的利用内存 */@Testpublic void testSessionObj(){ tx = session.beginTransaction(); for(int i = 0; i < 1000; i++){ Person p = new Person("asdf-"+i,12,123456+1, new Date()); session.persist(p); // i 是 10 的倍数的时候,刷新 session,将数据同步到数据库,同时清除session缓存,释放内存 if(i % 10 == 0){ // 刷新 session, 将数据同步到数据库中 session.flush(); // 清除 session 缓存 session.clear(); System.out.println("---------------------"); } } tx.commit();}
三、一级缓存详解(一):
通过在做查询的时候,有几个查询方法支持一级Hibernate缓存,它们分别是:load(),get(),iterate(),其中要注意的是iterate方法只对实体对象查询才支持一级缓存,如果使用iterate来查询对象里面的相关属性,则查询的时候不支持一级缓存。
1、load()方法:
Student s = (Student)session.load(Student.class, 1); System.out.println(s.getName()); System.out.println("---------------------"); s = (Student)session.load(Student.class, 1); System.out.println(s.getName());
只发出一条sql语句,虽然这里使用两次load方法,但是第一次load方法对于id为1的记录进行加载,到第3行才发出一条sql语句及符合的数据,这样就把数据放在一级缓存里面,在第4行开始继续使用load就从一级缓存里面抽取。
2、get()方法:
Student s = (Student)session.get(Student.class, 1); System.out.println(s.getName()); System.out.println("---------------------"); s = (Student)session.load(Student.class, 1); System.out.println(s.getName());
跟1中load一样,只发出一条sql语句。
3、iterate()方法查询实体对象:
Student student = (Student)session.createQuery("from Student s where s.id=1").iterate().next(); System.out.println("student.name=" + student.getName()); //会发出查询id的sql,不会发出查询实体对象的sql,因为iterate使用缓存 student = (Student)session.createQuery("from Student s where s.id=1").iterate().next(); System.out.println("student.name=" + student.getName());
总共发出3条sql语句,1、2行发出两条语句,1条是查询实体对象的sql,另一条是查询实体对象的name属性,由于使用一级缓存,之前1、2行查询的实体存放在一级缓存里面,所以5、6行利用一级缓存里面的数据只发出一条查询id的sql。
4、iterate()方法查询实体对象属性(不支持一级缓存):
String name = (String)session.createQuery("select s.name from Student s where s.id=1").iterate().next(); System.out.println("student.name=" + name); //iterate查询普通属性,一级缓存不会缓存,所以发出sql //一级缓存是缓存实体对象的 name = (String)session.createQuery("select s.name from Student s where s.id=1").iterate().next(); System.out.println("student.name=" + name);
由于iterate()方法查询实体对象属性,一级缓存不会产生作用,所以发出两条sql语句。
一级缓存是存在的,所以要更加合理的管理好,提高程序的效率,通常都是通过clear(),evict()方法来清除缓存,当不需要使用一级缓存或者是更加高效率使用一级缓存。
如果在一次性更新或者加入数量量比较大的情况下,更加要管理好一级缓存。
for(int i=0;i<10000;i++) { Student s = new Student(); s.setName("s"+i); session.save(s); if(i%20==0) { session.flush(); session.clear(); } }
在数据量比较大的情况下管理一级缓存的做法,一般都是设定一定数量的记录给更新或者保存等操作之后,避免一次性大量的实体数据入库导致内存溢出,所以才去先是用第8行的flush和第9行的clear方法来实现比较好的缓存管理。
在数据量特别大的时候,可以使用jdbc来实现,因为hibernate不太适合于数据量特别大的场合使用,如果连jdbc都满足不了数据量的效率要求,只好利用相关的数据库机制来实现。
(来自:http://developer.51cto.com/art/200909/154261.htm)
四、一级缓存详解(二):
1、Session 级别的缓存,它同session邦定。它的生命周期和session相同。Session消毁,它也同时消毁;管理一级缓存,一级缓存无法取消,用两个方法管理,clear(),evict()
2、两个session 不能共享一级缓存,因它会伴随session的生命周期的创建和消毁;
3、Session缓存是实体级别的缓存,就是只有在查询对象级别的时候才使用,如果使用HQL和SQL是查询属性级别的,是不使用一级缓存的!切记!!!!
4、iterate 查询使用缓存,会发出查询Id的SQL和HQL语句,但不会发出查实体的,它查询完会把相应的实体放到缓存里边,一些实体查询如果缓存里边有,就从缓存中查询,但还是会发出查询id的SQL和HQL语句。如果缓存中没有它会数据库中查询,然后将查询到的实体一个一个放到缓存中去,所以会有N+1问题出现。
每一个Hibernate Session实例和一个数据库事务绑定通常将每一个Hibernate Session实例和一个数据处理库事务绑定就是说,每执行一个数据库事务(操作),都应该先创建一个新的Hibernate Session实例。
如果事务执行中出现异常,应该撤消事务.不论事务执行成功与否,最后都应该调用Session的close()方法,从而释放Hibernate Session实例占用的资源。
如何获得Session对象
首先创建SessionFactory对象,如果访问多个数据源时,则应该产生多个SessionFactory;
但是仅仅为了服务与某个请求时,不要创建一个新的SessionFactory,因为创建SessionFactory需要消耗大量的资源,然后根据SessionFactory再创建Session对象
Session session=factory.openSession();Transaction ts = null;try { ts=session.beginTransaction(); //开始一个事务 ................... //执行事务 ts.commit(); //提交事务} catch (HibernateException e) { //如果出现异常就撤销事务 if(ts!=null){ ts.rollback(); //回滚事务 } e.printStackTrace();} finally{ session.close();//不管事务执行成功与否,最后都关闭Session并且放在finally中以提高安全性}
下面我们重点看一下Session级的缓存
Hibernate缓存分为二级,第一级存放于session中称为一级缓存,默认带有且不能卸载
因为Session的生命期往往很短,存在于Session内部的最快缓存的生命期当然也很短,所以第一级缓存的命中率是很低的。其对系统性能的改善也是很有限的。当然,这个Session内部缓存的主要作用是保持Session内部数据状态同步。并非是hibernate为了大幅提高系统性能所提供的。
为了提高使用hibernate的性能,除了常规的一些需要注意的方法比如:使用延迟加载、迫切外连接、查询过滤等以外,还需要配置hibernate的二级缓存。其对系统整体性能的改善往往具有立竿见影的效果。.
应用
Hibernate的一级缓存是由Session提供的,因此它只存在于Session的生命周期中,当程序调用save(),update(),saveorupdate()等方法 及调用查询接口list,filter,iterate时,如session缓存中还不存在相应的对象,Hibernate会把该对象加入到一级缓存中,
当Session关闭的时候该Session所管理的一级缓存也会立即被清除
Hibernate的一级缓存是Session所内置的,不能被卸载,也不能进行任何配置
一级缓存采用的是key-value的Map方式来实现的,在缓存实体对象时,对象的主关键字ID是Map的key,实体对象就是对应的值。所以说,一级缓存是以实体对象为单位进行存储的,在访问的时候使用的是主关键字ID
虽然,Hibernate对一级缓存使用的是自动维护的功能,没有提供任何配置功能,但是可以通过Session中所提供的方法来对一级缓存的管理进行手工干预。Session中所提供的干预方法包括以下两种
●evict() :用于将某个对象从Session的一级缓存中清除
evict()方法适用于以下二种情况:
1)不需要该对象进行同步的数据更新
2)在批量进行更新与删除时,当更新删除每一个对象后,要释对此对象所占用的内存.
●clear() :用于将一级缓存中的所有对象全部清除。
在进行大批量数据一次性更新的时候,会占用非常多的内存来缓存被更新的对象。这时就应该阶段性地调用clear()方法来清空一级缓存中的对象,控制一级缓存的大小,以避免产生内存溢出的情况。
Hibernate大批量更新时缓存的处理方法:
(假设我们user表的age有5000条大于0的记录)
Session session =SessionFactory.openSession();Transaction tx =session.beginTransaction();Itertaor users=session.find("from User u where u.age>0").itertaor();//HSL语句就不做解释了while(user.hasNext()){ User user =(User)users.next(); user.setAge(user.getAge()+1); //将本批插入的对象立即写入数据库并释放内存 session.flush(); session.clear();}tx.commit();session.close();
用Hibernate处理大批数据时..都必须先执行5000次的update语句,然后才能更新5000个user 对象..
这样就影响到了操作上的性能....在项目当我们遇到性能与空间的问题时,,,要以性能为主..这也就是说要牺牲空间
所以程序最好跳过Hibernate API 而直接通过JDBC API来执来...
我们改一下上面的代码:
Session session=SessionFactory.openSession();Transaction tx =session.beginTransaction();Connection conn =session.connection();PreparedStatement pstmt = conn.prepareStatement("update users set age=age+1 "+"where age >0");pstmt.executeUpdate();tx.commit();
虽说这是通过JDBC API.但本质上还是通过Hibernater Transaction的事务这个接口来声明事务的边界的...
其实最好的解决方法就是以创建存储过程,,用底层的数据库运行..这样性能好,速度快....
我就简单的以Oracle数据库为例子.创建一个名为UserUpdate的存储过程...然后在程序中进行调用...
UserUpdate的存储过程代码:
create or replace procadure UserUpdate(u_age in number) as
begin
update users set age=age+1 where age>u_age;
end;
下面的是在程序中如何调用我们命名的存储过程
Session session =SessionFactory.openSession();Transaction tx =session.beginTransaction();Connection conn=session.connection();String str="{call UserUpdate(?)}";CallableStatement cstmt= conn.prepareCall(str);cstmt.setInt(1,0);cstmt.executeUpdate();tx.commit();
注意.开源的MySQL中不支持存储过程的..
用JDBC API的好处是这样的..
它不用把大批量的数据事先加载到内存中,然后再进行更新与修改..所以不会消耗大量内存....
(小程序中是看不出什么差别的..当数据的记录达到一定的数据量的时候自然会发现用Hibernate API 与JDBC API的差别)
在一个就是只能一条记录进行批量更新..不像Hibernate中更新每一条的..
(来自:http://dododo1234321-163-com.iteye.com/blog/1828173)
Hibernate学习---第四节:一级缓存