Hibernate学习---第四节:一级缓存

一、Hibernate 一级缓存的概念:

一级缓存生命周期很短与 session 生命周期一致,所以一级缓存也叫 session 级缓存或事务级缓存。位于缓存中的对象处于持久化状态,它和表中的相关记录对应,session 能够在某些时间点,按照缓存中持久化对象的属性变化来同步数据库中表的记录,这一过程称为清理缓存。

(1)、一级缓存实现原理。session 缓存是由它的实现类 sessionImpl 中定义的一些集合属性构成的,原理是保证有一个引用在关联着某个持久化对象,保持它的生命周期不会结束。

(2)、session 缓存的作用。减少数据库访问,从内存中取数据比从数据库中要快的多。缓存中的数据与数据库中的同步:缓存会把改变的 sql 语句合并,减少访问次数。缓存中的对象存在循环关联时,session 会保证不出现访问对象图的死循环。

session 缓存对象的存储位置:session--> persistenceContext--> entityEntries--> map

二、一级缓存测试小例子,代码如下:

@Test
public 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 支持一级缓存
 */
@Test
public 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 一样,支持一级缓存。

@Test
public 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() 剔除某个持久化对象

如果一次性向数据库写入多条数据,那么可以用以下方式处理:

/**
 * 巧妙的利用内存
 */
@Test
public 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)

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。