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)

时间: 2024-10-09 09:59:26

Hibernate学习---第四节:一级缓存的相关文章

hibernate学习笔记之三(一级缓存的使用和管理)

(hibernate学习笔记系列来自于 "疯狂Java" Hibernate视频学习) 一级缓存 一级缓存生命周期很短,与session生命周期一致,所以一级缓存也叫session级缓存或事务级缓存.位于缓存中的对象处于持久化状态,它与表中的相关记录对应,session能够在某些时间点,按照缓存中持久化对象的属性来同步数据库中表的记录,这一过程称为清理缓存. 一级缓存实现原理. session缓存是由它的实现类sessionImpl中定义的一些集合属性构成的,原理是保证有一个引用在关联

【Java EE 学习第47天】【Hibernate学习第四天】【sesion】【一级缓存】【懒加载】

一.Session概述 1.Session 接口是 Hibernate 向应用程序提供的操纵对数据库的最主要的接口, 它提供了基本的保存, 更新, 删除和加载Java 对象的方法. 2.理解Session的缓存 使用缓存的目的:尽量减少访问数据库的频率 (1)在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存. 只要 Session 实例没有结束生命周期, 存放在它缓存中的对象也不会结束生命周期 (2)当session的save()方

Linux学习第四节课-文件管理基本概念及相关部分指令

Linux学习第四节课 --------------------------------------------------------------------------------------------------------------------------------------------------------------------- 十九.文件管理基本概念 文件和目录被组织成一个单根倒置树结构: 文件系统从根目录开始,用/表示: 根文件系统(rootfs):root file

hibernate学习(四)hibernate的一级缓存&amp;快照

缓存:提高效率 硬件的 CPU缓存   硬盘缓存   内存 软件的  io流缓存 hibernate  的一级缓存   也是为了操作数据库的效率. 证明一级缓存在  : Person p=session .get(Person.class, 1); Person p1=session.get(Person.class,2); Person  p2=session.get(Person.class,3); System.out.println(p=p1); 控制台输出为: select   * 

Hibernate学习(九)———— 二级缓存和事务级别详讲

序言 这算是hibernate的最后一篇文章了,下一系列会讲解Struts2的东西,然后说完Struts2,在到Spring,然后在写一个SSH如何整合的案例.之后就会在去讲SSM,在之后我自己的个人博客应该也差不多可以做出来了.基本上先这样定下来,开始完成hibernate的东西把.这章结束后,我会将我一些hibernate的资料奉上,供大家一起学习. ---WH 一.概述 这章总的分两大块来讲解, 第一大块,hibernate的事务管理.,对于hibernate的事务管理来说,如果之前学过数

Hibernate学习笔记(四) — session的产生方式 与 一对多关系映射

一.session的创建 Session是由SessionFactory负责创建的,而SessionFactory的实现是线程安全的,多个并发的线程可以同时访问一个SessionFactory并从中获取Session实例,但Session不是线程安全的. 每次openSession,产生的都是一个新的session,相当于创建一个新的连接.但是有很多时候,并不希望这样.比如在淘宝购物,在付账的一瞬间,至少有三件事情发生,转账,仓库数据变化,购物历史记录.而这三件事有必须在同一事务下.自然我们会联

VUE2.0实现购物车和地址选配功能学习第四节

第四节 v-on实现金额动态计算 用¥金额 进行格式处理,可以使用原生js进行转换,但是在vuei,使用filter过滤器更加方便 注: 1.es6语法=>和import等 好处在于res参数后的function函数内的this作用域,不弄在外部声明变量了. methods:{ cartView:function(){ let _this=this; this.$http.get("data/cartData.json",{"id":123}).then( r

Mybatis框架学习(四)—查询缓存与spring的整合开发

1       项目整体目录 2       查询缓存 2.1     缓存的意义 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题. 2.1    mybatis持久层缓存 mybatis提供一级缓存和二级缓存 mybatis一级缓存是一个SqlSession级别,sqlsession只能访问自己的一级缓存的数据,二级缓存是跨sqlSession,是mapper级别的缓存,对于mappe

Hibernate学习---第十一节:Hibernate之数据抓取策略&amp;批量抓取

1.hibernate 也可以通过标准的 SQL 进行查询 (1).将SQL查询写在 java 代码中 /** * 查询所有 */ @Test public void testQuery(){ // 基于标准的 sql 语句查询 String sql = "select * from t_person"; // 通过 createSQLQuery 获取 SQLQuery,而 SQLQuer 是 Query的子类 SQLQuery query = session.createSQLQue