Hibernate一级缓存

简介

缓存,介于应用程序和永久数据存储源之间,作用是为了降低应用程序对物理数据源访问的频率,从而提高应用的运行性能。
例如我们cpu执行效率每秒处理的数据高达上千兆,而我们的硬盘读取速度却没那么高,读取几百兆,这时候我们使用缓存来存储数据,存储满后一次性交由cpu处理。

Hibernate中也存在缓存,同样是为了提高效率。Hibernate的缓存包括Session的缓存和SessionFactory的缓存

Session的缓存是内置的,不能被卸载,也被称为Hibertnate的一级缓存。

SessionFactory有一个内置缓存和外置缓存。SessionFactory的外置缓存是一个可配置的缓存插件。默认情况下,Hibernate不会启用这个缓存插件。被称为Hibernate的二级缓存。

缓存的范围

缓存的范围决定了缓存的生命周期以及可以被谁访问。

    事务范围:缓存只能被当前事务访问。一级缓存是Session的缓存,Session对象生命周期通常对应一个事务,因此是事务范围的缓存。 进程范围:缓存被进程内的所有事务共享。二级缓存是可配置的缓存插件,由SessionFactory管理,SessionFactory生命周期和应用程序的进程对应,因此是进程范围的缓存。集群范围:在集群环境中,缓存被同一个机器或者多个机器上的多个进程共享。

Session缓存

Session缓存是Hibernate的一级缓存。Session对象中具有一个缓存。Session的缓存是一块内存空间,存放的是持久化对象。

当Session通过save方法持久化一个对象时,该对象被加入到Session缓存中。
当Session通过get方法获取一个持久化对象时,Session会先判断Session缓存中是否存在这个对象,如果存在,就不需要再从数据库中查找。

========我们来测试一下缓存的存在==============

        //开启事务Transaction ts=session.beginTransaction();//加上断点,当我们执行完这一步,会打印select语句,而后面的都不会打印,说明并没有从数据库中获取User user1=session.get(User.class, 5);   //这次get方法会先从session缓存中查找,由于已经存在,直接返回引用 User user2=session.get(User.class, 5);User user3=session.get(User.class, 5);System.out.println(user1==user2);//trueSystem.out.println(user1==user3);//truesession.close();

脏检查及清理缓存的机制

我们先来看下面的例子

        Transaction ts=session.beginTransaction();User user1=session.get(User.class, 5);user1.setName("swaggy");ts.commit();

我们发现我们改变了Name属性,这时候session缓存中的对象的name属性和数据库表中的NAME字段不一致了。但是我们并没有进行更新操作,而是直接提交了事务

幸运的是,Session中在清理缓存的时候,会自动进行脏检查。如果发现Session缓存中的持久化对象和数据库中的记录不一致,就会根据对象的最新属性去更新数据库。
所以在本例中,Session会自动提交一个update语句对数据库进行更新。


Session是怎样进行脏检查的呢?

当一个对象被加入到Sesion缓存中时,Session会为该对象复制一份快照。当Session清理缓存时,会比较当前对象的属性和快照来判断是否发生变化,如果发生变化,就会根据最新属性来执行相关的更新操作。

我们看下面一个例子加深对快照的理解

        //我们从数据库中取出 id为5,name为tom,password为123456的对象Transaction ts=session.beginTransaction();User user1=session.get(User.class, 5);session.update(user1);session.close(); 过程:获取了持久化对象,放入缓存中,并创建了快照,我们执行更新,Session缓存中的对象会和快照进行比较,没有任何变化,所以不会执行update语句。
        //我们自己设置一个和数据库中一模一样的对象,这时候会打印update语句Transaction ts=session.beginTransaction();User user=new User();user.setId(5);user.setName("tom");user.setPassword("123456");session.update(user);ts.commit();session.close();过程:因为此时我们执行update语句时会将对象直接放入缓存中,但是没有持久化对象的快照,所以进行对比结果就是不一致,所以尽管什么都没更改,还是会执行update语句,在控制台打印。

什么时候会清理缓存呢?

-默认情况下,在调用commit()方法时会先清理缓存。再向数据库提交事务。 

-当执行查询操作时,如果缓存中的持久化对象属性已经发生了改变,就会先清理缓存,同步数据,保证查询到的是正确的结果。 

-当应用程序显式调用Session的flush()方法时  

-Session清理缓存的例外情况,如果对象使用的是native生成策略生成OID,那么调用Session的save()方法来保存该对象时,会立刻执行向数据库的插入语句。

如果不希望Session在以上默认的时间点清理缓存,可以通过Session的setFlushMode()方法来设定清理缓存的时间点。
FlushMode类定义了三种清理模式。

                            各种查询方法          commit()方法        flush()方法
-FlushMode.AUTO(默认模式)        清理                 清理                清理
-FlushMode.COMMIT              不清理                清理                清理
-FlushMode.NEVER               不清理                不清理              清理   

例如Session.setFlushMode(FlushMode.AUTO)

Java对象在Hibernate持久化层的状态

-临时状态:刚用new语句创建对象,还没有被持久化,并且不处于Session缓存中。处于临时状态的java对象被称为临时对象。

-持久化状态:已经被持久化,并且加入到Session的缓存中。处于持久化状态的java对象被成为持久化对象。

-游离状态:已经被持久化,但不再处于Session的缓存中。处于游离状态的java对象被成为游离对象。

        //对象状态转换的过程Transaction ts=session.beginTransaction();User user =new User();                  //临时状态user.setName("tom");user.setPassword("123456");session.save(user);                     //转变为持久化状态ts.commit();                            //持久化状态 session.close();                        //转变为游离状态sessionfactory.close();System.out.println(user.getName());     //游离状态 
临时对象的特征:-OID为null
        -不处于Session的缓存中,也可以说,不被任何一个Session实例关联
        -在数据库中没有对应的记录

持久化对象的特征:-OID不为null
        -位于一个Session实例的缓存中,持久化对象总是被一个Session实例关联
        -持久化对象和数据库中的相关记录对应
        -会根据持久化对象的属性变化,同步更新数据库。
            User  user=(User)session.get(User.class,1);//获取持久化对象 持久状态user.setName("jerry");transaction.commit();我们发现我们没有执行update语句,却打印了update语句。Hibernate会自动将持久化对象的状态同步到数据库中。游离对象的特征:-OID不为null
        -不再位于Session的缓存中,也可以说,游离对象不被Session关联
        -数据库中有对应的记录

三种状态的转换

    1)临时状态转换持久化状态-Session的save()方法会将临时状态转换成持久状态。把要保存的对象放入Session缓存中,使它进入持久化状态。使用映射文件指定的主键生成策略,为持久化对象分配唯一OID。save方法只是为对象分配UID。我们可以在save方法处打断点。当我们的主键生成策略为native时,由于我们使用mysql数据库,主键自增,所以执行完save方法后,打印insert语句,mysql数据库为我们对象自增OID 当我们的主键生成策略为incrementt时,increment是由Hibernate维护,先去表中查最大ID然后+1,我们执行完save方法之后,发现打印select查找最大id的语句,执行commit时才打印插入语句2)临时状态转换成游离状态-将临时状态的对象OID设置为数据库中对应的记录.User user=new User();user.setId(1);3)持久化状态转换成临时状态第一种:User  user=(User)session.get(User.class,1);//获取持久化对象 持久状态session.close(); //游离状态user.setId(null);//临时状态 第二种:User  user=(User)session.get(User.class,1);//获取持久化对象 持久状态session.evict(user); //游离状态,此方法会将session缓存中清除持久化对象,使其变为游离状态user.setId(null);//临时状态  4)持久化状态转换成游离状态第一:调用session的close方法,持久化状态变为游离状态第二: 调用session的evict()方法方法,将持久状态转变为游离状态5)游离状态转换成临时状态只需要将游离状态的对象OID变为null。6)游离状态转换成持久状态Session的update()方法使游离状态转换成持久状态。User  user=(User)session.get(User.class,1);//获取持久化对象 持久状态session.evict(user); //游离状态,此方法会将session缓存中清除持久化对象,使其变为游离状态session.update(user);

Session接口的其他API

--Session的save()和persist()方法两个方法都是用来保存对象,能把临时状态转变为持久化状态。 两个方法的区别在于:save方法持久化对象时,会返回持久化对象的OID。所以程序执行save时会立刻执行insert语句,来返回数据库生成的OID。 persist方法持久化对象时,不会保证立即为持久化对象的OID赋值,不会立即生成insert语句,而是有可能在Session清理缓存时才为OID赋值。--Session的clear()方法清空一级缓存 --Session的update方法update()方法可以将游离对象转变为持久化对象。用来执行修改操作。update()方法完成以下操作-把游离对象加入到当前缓存中,变为持久化对象-然后计划执行update语句只要通过update使游离对象转变为持久化对象,即使没有修改任何属性,在清理缓存时还是会执行update语句。如果希望Session仅当修改了属性时才执行update语句,可以在映射文件中的<class>元素中设置select-before-update="true",默认为false这样当Session清理缓存时,会先发送一条查询语句,然后判断缓存中的对象和记录是否一致,不一致才执行update语句。当update()方法将游离对象转变为持久化对象时,如果Session缓存中已经存在相同的OID持久化对象,那么会抛出异常。例如:Transaction ts=session.beginTransaction();User user1=session.get(User.class, 5);session.evict(user1);User user2=session.get(User.class, 5);session.update(user1);ts.commit();因为Session的缓存是一个Map结构,OID为key,对象为value。当执行session的update方法时,由于缓存中已经存在了OID为5的持久化对象,因此会抛出异常。 -Session的saveOrUpdate()方法 Session的saveOrUpdate()方法同时包含了save()和update()方法的功能如果传入的参数是临时对象(OID为null),就调用save方法。如果传入的参数是游离对象(OID不为null),就执行update方法。

Hibernate的一对多关联关系

映射一对多双向关联关系

例如,以客户(Customer)和订单(Order)为例,一个客户能有多个订单,一个订单只能有一个客户。 从Customer到Order是一对多关联,在java类中的面向对象设计应该一个Customer对象包含多个Order对象,因此应该定义一个集合,来包含所有的Order对象。  从Order到Customer是多对一关联,在java类中设计每个Order对象需要关联一个Customer对象,因此Order类中应该定义一个Cutomer类型的属性,来引用关联的customer对象。但是在关系数据库中,只存在主外键参照关系来表达两者的关联。
        第一步:我们先创建两个实体类 Customer和Orderpublic class Customer {private Integer id;private String name;private Set <Order> orders=new HashSet<Order>();public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Set getOrders() {return orders;}public void setOrders(Set orders) {this.orders = orders;}} public class Order {private Integer id;private String name;private Customer customer;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Customer getCustomer() {return customer;}public void setCustomer(Customer customer) {this.customer = customer;}}
        第二步:实体类写好了,使用Hibernate映射文件来映射关系。创建Customer.hbm.xml  <hibernate-mapping ><!--指定实体类和表的映射关系--><class name="com.cad.domain.Customer" table="customer"><id name="id" column="id"><generator class="native"></generator></id><property name="name" column="name"></property><!--使用<set>元素来映射set集合类型--><!--name:持久化类中的属性名--><set name="orders"><!--<key>元素设定所关联的持久化类对应的表的外键--><!--<one-to-many>元素设定关联的持久化类--><key column="cid"/><one-to-many class="com.cad.domain.Order"/> </set></class></hibernate-mapping>  Hibernate根据映射文件获得以下信息-<set>元素表明Customer类中的orders属性为java.util.Set集合-<one-to-many>元素表明orders集合中存放的是一组order对象-<key>元素表明orders表通过外键cid关联customer表
创建Order.hbm.xml <hibernate-mapping ><class name="com.cad.domain.Order" table="order"><id name="id" column="id"><generator class="native"></generator></id><property name="name" column="name"></property><!--<many-to-one>元素建立了customer属性和对应表中外键的映射--><!--name:持久化类中的属性名column:表中的外键class:关键的Customer对象实现类--><many-to-one name="customer" column="cid" class="com.cad.domain.Customer"></many-to-one></class></hibernate-mapping>   
            第三步:在hibernate.cfg.xml中配置映射文件 <mapping resource="com/cad/domain/Customer.hbm.xml"/><mapping resource="com/cad/domain/Order.hbm.xml"/>
            第四步: 我们来操作一下测试一下。 //添加方法  @Testpublic void test() {//读取配置文件Configuration conf=new Configuration().configure();//根据配置创建factorySessionFactory sessionfactory=conf.buildSessionFactory();session = sessionfactory.openSession(); Transaction ts=session.beginTransaction(); //创建CustomerCustomer c=new Customer();c.setName("张三"); //创建订单Order o1=new Order();o1.setName("矿泉水");Order o2=new Order();o2.setName("方便面"); //双向关联c.getOrders().add(o1);c.getOrders().add(o2);o1.setCustomer(c);o2.setCustomer(c);//保存session.save(c);session.save(o1);session.save(o2);ts.commit();session.close();sessionfactory.close();}然后执行,控制台会打印如下语句Hibernate: insert intocustomer(name) values(?)Hibernate: insert intoorders(name, cid) values(?, ?)Hibernate: insert intoorders(name, cid) values(?, ?)Hibernate: updateorders setcid=? whereid=?Hibernate: updateorders setcid=? whereid=?
                我们来执行删除操作,直接删除Customer,但由于还有Order关联着Customer会执行成功么?@Testpublic void test() {//读取配置文件Configuration conf=new Configuration().configure();//根据配置创建factorySessionFactory sessionfactory=conf.buildSessionFactory();session = sessionfactory.openSession(); Transaction ts=session.beginTransaction();Customer c=session.get(Customer.class, 2); //删除Customersession.delete(c);ts.commit();session.close();sessionfactory.close();}执行,打印如下语句Hibernate: selectcustomer0_.id as id1_0_0_,customer0_.name as name2_0_0_ fromcustomer customer0_ wherecustomer0_.id=?Hibernate: updateorders setcid=null wherecid=?Hibernate: delete fromcustomer whereid=? 我们会发现Hibernate会自动将Order中的cid设置为null,然后执行删除操作我们发现Hibernate还是挺智能的,但这是由inverse属性操控的。

< set >元素的inverse属性

inverse所描述的是对象之间关联关系的维护方式。 inverse属性指定由哪方来维护关联关系。
inverse默认为false,即关联关系由自己控制,若为true,则反转,关联关系由对方控制.
Inverse属性的作用是:是否将对集合对象的修改反映到数据库中。 在映射一对多的双向关联关系中,应该在"一"方把inverse属性设为true,由对方来维护主键关联关系.所以上述例子中,inverse默认是false.即Customer维护关联关系,所以Customer会执行两条更新语句来更新Order的cid.
但是我们Order在插入的时候已经插入cid,所以这样会影响性能,我们只需要将Customer的<set>元素的inverse属性改为true即可。 我们的删除案例中也是,Customer执行删除时,会先去把Order的主键约束解除,然后删除。
我们只需要将Customer的inverse设置为true,然后由对方维护关联关系,我们再进行删除时,就会出现异常,因为有主键约束,我们Customer不再维护关联关系。

级联操纵

在实际应用中,对象和对象之间是相互关联的。例如我们的一对多关联关系。
在关系-对象映射文件中,用于映射持久化类之间关联关系的元素,如 < set>,< many-to-one>,< one-to-many>,都有一个cascade属性,用来指定如何操纵与当前对象关联的其他对象。

    我们先看下面的例子,我们创建一个Customer,再创建两个Order,然后关联我们只保存Customer,会抛出org.hibernate.TransientObjectException异常,这是为什么呢?这是因为我们的Customer的inverse为false,关联关系由Customer维护。我们保存Customer时,会维护Customer中orders中的所有Order的主键,但是Order是临时对象,并没有转变为持久状态,这时候就会抛出异常。@Testpublic void test() {//读取配置文件Configuration conf=new Configuration().configure();//根据配置创建factorySessionFactory sessionfactory=conf.buildSessionFactory();session = sessionfactory.openSession(); Transaction ts=session.beginTransaction();Customer c=new Customer();c.setName("jack");Order o1=new Order();o1.setName("苹果");Order o2=new Order();o2.setName("香蕉");c.getOrders().add(o1);c.getOrders().add(o2);o1.setCustomer(c);o2.setCustomer(c);session.save(c);ts.commit();session.close();sessionfactory.close();}

当Hibernate持久化一个临时对象时,并不会自动持久化所关联的其他临时对象,所以会抛出异常。
如果我们希望Hibernate持久化对象时自动持久化所关联的其他对象,那么就需要指定cascade属性

级联保存和更新

    当我们持久化对象时自动持久化所关联的其他对象。 把cascade属性设置为save-update ,这时候我们再执行上面的代码就会自动帮我们保存Customer关联的Order对象。当cascade属性为save-update时,表明保存或更新当前对象时,会级联保存或更新与它关联的对象。

级联删除

    如果我们的cascade属性为delete时,我们删除当前对象,会自动删除与之关联的对象。慎用这个delete属性。例如:我们的Order配置了这个属性,Customer也配置了这个属性,我们删除订单时,因为是级联删除所以会查找Customer,删除Customer,但Customer也配置了级联删除,所以会查找所有关联的订单,最后会删除该客户的所有订单和该客户。

孤儿删除

    如果我们的对象和关联的对象解除关系后,希望自动删除不再关联的对象。需要将cascade设置为delete-orphan.例如 ,我们设置cascade="delete-orphan"Transaction ts=session.beginTransaction();Customer c=session.get(Customer.class, 7);Order order=(Order) c.getOrders().iterator().next();c.getOrders().remove(order);order.setCustomer(null);ts.commit(); 我们解除Customer和Order的关系,Hibernate就会自动删除Order。  当cascade的值为all时,是save-update和delete的整合。当cascade的值为all-dalete-orphan时,是all和delete-orphan的整合。

Hibernate 核心技术(二)相关推荐

  1. spring mvc+spring + hibernate 整合(二)

    在上篇文章中,我建立了工程并配置了spring + hibernate.今天我们检验下上篇文章的成果,如何检查呢?那就是进行单元测试.本篇文章就让大家和我一起来就前面的建的工程进行单元测试. 本项目使 ...

  2. Hibernate学习(二):heibernate核心接口

    Hibernate是一种对JDBC做了轻量级封装的对象---关系映射工具,所谓轻量级封装,是指Hibernate并没有完全封装JDBC,Java应用即可以通过Hibernate API访问数据库,还可 ...

  3. hibernate框架(二)核心配置API

    一:核心配置分为三大部分  必须的配置 .可选的配置和引入映射文件. 1.必须的配置 连接数据库的参数:驱动类  url路径  用户名  密码  方言 <property name=" ...

  4. 框架之 hibernate之二

    1. Hibernate持久化对象的状态 2. Hibernate的一级缓存 3. Hibernate操作持久化对象的方法 4. Hibernate的基本查询 Hibernate的持久化类 什么是持久 ...

  5. Hibernate入门(二)——hibernateAPI详解

    Hibernate API 详解 1.Configuration 功能:配置加载类,用于加载主配置,orm元数据加载 .创建: Configuration conf = new Configurati ...

  6. Hibernate(二)——一对多查询

    1. 前言 本章节我们讨论Hibernate一对多查询的处理. 在上一章节中(Hibernate(一)--入门),我们探讨了Hibernate执行最基本的增删改查操作.现在我们将情况复杂化:加入我们在 ...

  7. 夯实基础,彻底掌握js的核心技术(二):面向对象编程(Object Oriented Programming)

    单例设计模式(Singleton Pattern) 1. 单例模式解决的问题: 表现形式: Var obj = {xxx; xxx, - } 2. 作用: 把描述同一件事物的属性和特征进行" ...

  8. Java程序员从笨鸟到菜鸟之(五十一)细谈Hibernate(二)开发第一个hibernate基本详解...

    在上篇博客中,我们介绍了<hibernate基本概念和体系结构>,也对hibernate框架有了一个初步的了解,本文我将向大家简单介绍Hibernate的核心API调用库,并讲解一下它的基 ...

  9. hibernate入门二之单表操作

    友情提醒:以下所有的操作都是在hibernate入门一的基础上操作的,参考链接:https://www.cnblogs.com/lindaiyu/p/10990525.html 单一主键 assign ...

最新文章

  1. jmeter 自定义参数_jmeter参数化并在jenkins上执行
  2. 解决IE正常模式与兼容性模式的办法
  3. 软件构造学习笔记-第十二周
  4. LeetCode 1073. 负二进制数相加(负数进制)
  5. 【内容管理系统】之 Strapi
  6. IE 首页被改为www.1188.com的恢复办法
  7. $与一些特殊字符的组合用法
  8. python基础7-函数
  9. 去掉Scala的糖衣(4) -- Type Aliase
  10. SSH免密登陆:Win登陆linux
  11. Windows7 的激活命令小结
  12. 将两个顺序表合并为一个新的顺序表
  13. 【STM32F407F429H7的DSP教程】第34章 滤波器基础知识
  14. Java8 Stream(11)List转Map
  15. 泛泰A860版本的识别
  16. php 开源cms 族谱,20款最为知名的开源PHP CMS
  17. 【Python爬虫】:爬取58同城二手房的所有房产标题
  18. Blog Management System Based on JSP(基于JSP的实验室设备管理系统的开发)外文翻译
  19. Multisim基础 发光二极管 添加元件的位置
  20. oracle 按天数 均值,oracle 按天数统计数据

热门文章

  1. 强人工智能不能碰,那电影中的机器人只是一场梦?
  2. ACM准备之路(蓝桥杯5)杨辉三角公式解法
  3. ChatGPT中文版正式上线,掀起人工智能语言生成新热潮
  4. CLUSTER SLOTS
  5. vscode 编辑md文件 好用的插件
  6. font-icon各类图标的使用
  7. 谈谈在线团队协作工具 - Tower im
  8. 新课改计算机专业选课要求,新高考如何选科?各专业类选科要求对照表来了
  9. 5.12 汶川大地震记忆(2008-06-23 15:34:50| 分类: 心情故事)
  10. Win8.1系统“Windows 套接字初始化失败”解决方案