Hibernate 系列 05 - Session 类
引导目录:
前言:
Session是Hibernate运作的中心,对象的生命周期、事务的管理、数据库的存取都与Session息息相关。
就如同在编写JDBC时需要关心Connection的管理,以有效的方法创建、利用与回收Connection,以减少资源的消耗,增加系统的执行效能一样,有效的Session的管理也是Hibernate应用时需要关注的焦点。
本篇目录:
1. 使用threadLocal变量
Session是由SessionFactory创建的,SessionFactory是线程安全的(Thread-Safe),可以让多个执行线程同事存取SessionFactory而不会有数据共享的问题。
然而Session则不是设计为线程安全的,所以试图让多个执行线程共享一个Session,将会发生数据共享而发生混乱的问题。
在Hibernate参考手册中的第1章快速入门中,示范了一个HibernateUtil,它使用了ThreadLocal类来建立一个Session管理的辅助类,这是Hiberate的Session管理的一个广为应用的解决方案。
使用ThreadLocal可以有效隔离执行所使用的数据,所以避开了Session的多线程之间的数据共享问题。
以下列出Hibernate参考手册中的HibernateUtil类:
import org.hibernate.*; import org.hibernate.cfg.*; public class HibernateUtil { private static Log log = LogFactory.getLog(HibernateUtil.class); private static final SessionFactory sessionFactory; static { try { // 创建 SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); }catch (Throwable ex){ // 当SessionFactory被耗尽时,记录例外 log.error("Initial SessionFactory creation falied. ", ex); throw new ExceptionInInitializerError(ex); } } // 下一行的thread_var原本是Session,但Session字面上没有thread_var好理解 private static final ThreadLocal thread_var = new ThreadLocal(); public static Session currentSession(){ Session s = (Session)thread_var.get(); // 如果这个线程仍然为空,则打开一个新的Session if(s == null){ s = sessionFactory.openSession(); thread_var.set(s); } return s; } public static void closeSession(){ Session s = (Session)thread_var.get(); if (s != null) s.close(); thread_var.set(null); } }
使用 ThreadLocal thread_var = new ThreadLocal() 语句生成的thread_var变量是一个只在当前线程有效的变量,也就是说不同线程所拥有的thread_var变量是不一样的。
在这个变量内可以使用set(object)方法放置保存一个对象,只要这个线程没有结束,都可以通过thread_var变量的get()方法取出原先放入的对象。
如图所示:
对于Session的运用,应该是每一个请求使用一个单独的Session。
例如,修改学生资料,第一个Session取得学生信息,并将其在网页的表单中显示(假设是一个Web应用),然后Session关闭;
这时打开第二个Session,把修改号的资料同步到数据库中,之后第二个Session关闭。
整个过程如图所示:
如果只是打开一个Session,取得数据,然后等待资料管理员在网页中修改完资料后再提交,得到新数据后进行更新,这样的话,这个Session就占据了太多时间和太多资源。
注意:
Hibernate中的Session与Http请求的Session不一样,Http中的Session表示的是用户向服务器发送请求的一个会话,而Hibernate中Session表示对数据库操作的对象。
Session类的API操作可以实现具体的数据增加、删除和修改等,关于这些操作的详细内容咱们后面再慢慢讨论。
2. Session的缓存
Hibernate中的缓存分为两种:一级缓存(Session级别)和二级缓存(SessionFactory级别)。
关于二级缓存使我们后面详细讨论的内容,本篇内容咱们重点探讨一级缓存的用法。
每一个Session实例都可以看作为一个容器,无论何时,当给save()、update()或saveOrUpdate()方法传递一个对象时,或使用load()、get()、list()、iterate()或scroll()方法获得一个对象时,该对象豆浆被加入到Session的内部缓存中。
每一个处于持久化状态的对象,当应用程序需要使用对象时,先在本Session的缓存内查找,如果有此对象,则直接返回给应用程序;
如果没有,则发送SQL语句到数据库中查询,将记录的字段值组装成对象后存于Session中,以供应用程序调用。
简单地说,Session有如下两个作用:
1) 充当蓄水池的作用,减少程序访问数据库的次数。
很多对象数据不是经常改变的,第一次访问这些对象时,Hibernate将它放入缓存中,以后只要这个对象没有改动过,访问这个对象时,Hibernate就不回去数据库中加载它的数据,而是从内存中直接返回应用程序。
这样的效率当然比每次都访问数据库要高得多。
2) 保证缓存中的数据与数据库同步。
缓存毕竟不是数据库,它的数据有可能和数据库不一致,这时,Hibernate会负责将缓存中的数据同步到数据库。
当然,具体的情况要看程序员设置的FlushMode是什么。
一个事务中,对数据进行了操作使其改变了值,这种变化不会立即传递到数据库中。
Hibernate能把一些数据改变做一下结合,并对数据库进行最小数量的请求,例如少产生几句SQL语句,也可以减轻网络的负担。
例如,一个对象的某个属性发生了两次变化,但是Hibernate指向数据库发送一条update语句就可以把这些数据变化都包括了,这些都归功于Session的缓存。
清理缓存是指查看缓存中的数据与数据库是否同步,如果缓存数据与数据库不一样,则发送更新语句把缓存数据和数据库同步;如果一样,则不作操作。
在一个Session中,有两种Session的清理方法,也可以说是Session的两个不同清理点,如下代码所示:
// 省略代码:打开Session,开启事务... // code segment1 // code segment2 // 清理点1 - 清理缓存,这种清理经常是在查询语句之前改变了对象的属性值(AUTO, ALWAYS) // code segment3 // 清理点2 - 提交事务时,Hibernate调用flush()方法清理缓存(COMMIT) // 省略代码:提交事务,关闭Session...
在下面的情况中,Hibernate会调用Session.flush()以清理缓存:
- 事务提交时,如果flush模式不为FlushMode.MANUAL,commit()将调用flush()
- 在某些查询语句之前(此查询语句之前的语句已经改变了数据库状态,所以需要调用flush()方法以同步数据,使查询出来的数据使经过更改的)
- 当程序强制调用Session.flush()时
在调用Session.flush()时,涉及的SQL语句会按照下面的顺序发出执行:
- 所有对实体进行插入的语句,其顺序按照对象执行Session.save()的时间顺序
- 所有对实体进行更新的语句
- 所有进行集合删除的语句
- 所有对集合元素进行删除、更新或者插入的语句
- 所有进行集合插入的语句
- 所有对实体进行删除的语句,其顺序按照对象执行Session.delete()的时间顺序
- 有一个例外是,如果对象使用native方式来生成ID(持久化标识),则他们一执行save就会被插入
除非明确地指定了flush命令,否则关于Session何时会执行这些JDBC调用是完全无法保证的,只能保证他们执行的前后顺序。
当然,Hibernate保证Query.list()绝对不会返回已经失效的数据,也不会返回错误数据。
在事务结束时,调用flush()方法以执行事务成功的SQL语句是必需的,因为这样才能和数据库同步。
在一个事务中调用一个select查询,如果此查询之前已经有某个update语句做了数据修改(注意,此update语句并没有真正执行),或者直接改变了对象的属性,则Hibernate在查询语句执行前首先会调用flush()将缓存中的数据同步数据库,接着才返回查询数据。
如果select查询之前并没有改变数据的操作,则flush()不会被调用。
通过设置session.setFlushMode(),可以精确地控制Hibernate的FlushMode。
FlushMode有以下几种:
- FlushMode.AUTO:Hibernate判断对象属性有没有更改,如果被更改过变成了脏数据,则在一个查询语句前将更新此改动,以保证同步数据库;如果对象没有被改动,则在查询语句之前不用更新数据,这是Hibernate的默认清理格式。
- FlushMode.COMMIT:在事务结束之前清理Session的缓存,其他任何时候都不清理缓存。这样的设置将有可能使查询出来的数据使脏数据。例如,对对象做的修改存在于内存中,这可能会和从数据库查询出来的结果相冲突。
- FlushMode.MANUAL:(以前的版本里是NEVER,现在已被废弃)除非强制调用Session.flush()方法,否则永不清理缓存。这时对数据所做的修改只限于内存,不会同步到数据库中,数据库的数据相当于只读数据。
- FlushMode.ALWAYS:在每一个语句前都调用flush()方法进行缓存清理。这种模式经常是不必要并且低效的。
不推荐改变默认配置,除非在极少情况下配置会有性能提升。
大部分程序都不需要明确地去调用flush(),只有当使用触发器,或把Hibernate和JDBC语句混合使用,直接调用flush()方法才是很有用的。
下面通过几个例子,加深对上面FlushMode的理解。
1) FlushMode.MANUAL
编写如下程序:
Session session1 = HibernateUtil.currentSession(); Transaction tx = session1.beginTransaction(); session1.setFlushMode(FlushMode.MANUAL); // 设置清理模式为手动清理 Student stu = (Student)session1.get(Student.class, 12); // 通过学生主键值studentNo=12得到一个Student.class的实例 System.out.println(stu.getStudentName()); // 此时输出结果为:卡特琳娜 stu.setStudentName("卡特"); session1.save(stu); // 保存学生对象 tx.commit(); HibernateUtil.closeSession(); Session session2 = HibernateUtil.currentSession(); tx = session2.beginTransaction(); stu = (Student)session2.get(Student.class, 12); // 再次得到studentNo=12的学生对象 System.out.println(stu.getStudentName()); // 此时输出结果为:卡特琳娜 tx.commit(); HibernateUtil.closeSession();
运行以上的程序,得到的控制台信息如下:
卡特琳娜 卡特琳娜
可以看到,在session1中所做的更改并没有同步到数据库,所以在session2中取得的学生姓名才会是“卡特琳娜”,否则就应该是更改过的“卡特”了。
2) FlushMode.COMMIT
编写如下程序:
Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); session.setFlushMode(FlushMode.COMMIT); // 设置FlushMode Student stu = (Student)session.get(Student.class, 12); // 通过学生主键值student=12得到一个Student.class的实例 System.out.println(stu.getStudentName()); // 此时输出结果为:卡特琳娜 stu.setStudentName("卡特"); session.save(stu); // 这条语句并不会发送SQL语句到数据库 session.evict(stu); // 这句非常重要,它将stu对象从Session缓存中除去,以保证下一条语句得到的对象时从数据库中新组装出来的对象(否则将继续使用缓存中的对象) stu = (Student)session.get(Student.class, 12); // 重新获取studentNo=12的Student对象 System.out.println(stu.getStudentName()); // 此时输出结果为:卡特琳娜 tx.commit(); HibernateUtil.closeSession();
可以看到,session.save(stu)并没有把stu对象的变更同步到数据库,甚至程序结束也没有生成一条update语句。
原因就是使用Session.evict(stu)把变更过的stu从Session中除去了,后来再取得的stu是数据库字段值组装的对象,和数据库是一直的,所以在最后提交事务时,Hibernate县调用session.flush()进行缓存清理,而stu此时没有被修改过,所以不用发送update语句到数据库。
3) FlushMode.AUTO
Session session = HibernateUtil.currentSession(); Transaction tx = session.beginTransaction(); session.setFlushMode(FlushMode.AUTO); // 设置FlushMode Student stu = (Student)session.get(Student.class, 12); // 通过学生主键值studentNo=12得到一个Student.class的实例 System.out.println(stu.getStudentName()); // 此时输出结果为:卡特琳娜 stu.setStudentName("卡特"); stu = (Student)session.createQuery("from Student s where s.studentNo=12").uniqueResult(); System.out.println(stu.getStudentName()); // 此时输出结果为:卡特 tx.commit(); HibernateUtil.closeSession();
控制台显示结果为:
Hibernate: select student0_.[StudentNo] as StudentN1_1_0_, student0_.[LoginPwd] as LoginPwd2_1_0_, student0_.[StudentName] as StudentN3_1_0_, student0_.[Gender] as Gender4_1_0_, student0_.[Phone] as Phone5_1_0_, student0_.[Address] as Address6_1_0_, student0_.[BornDate] as BornDate7_1_0_, student0_.[Email] as Email8_1_0_, student0_.[IdentityCard] as Identity9_1_0_, student0_.[GradeId] as GradeId10_1_0_ from [Student] student0_ where student0_.[StudentNo]=? 卡特琳娜 Hibernate: update [Student] set [LoginPwd]=?, [StudentName]=?, [Gender]=?, [Phone]=?, [Address]=?, [BornDate]=?, [Email]=?, [IdentityCard]=?, [GradeId]=? where [StudentNo]=? Hibernate: 卡特
在使用session.createQuery()得到学生对象时,Hibernate将清理缓存,发现stu已经被修改,于是发送一条update语句更新数据。
然后session.createQuery()的select语句才得到被更改过的学生数据,于是,程序可以打印出修改过的姓名“卡特”
Hibernate 系列 05 - Session 类的更多相关文章
- Hibernate 系列 学习笔记 目录 (持续更新...)
前言: 最近也在学习Hibernate,遇到的问题差不多都解决了,顺便把学习过程遇到的问题和查找的资料文档都整理了一下分享出来,也算是能帮助更多的朋友们了. 最开始使用的是经典的MyEclipse,后 ...
- Hibernate 系列 04 - Hibernate 配置相关的类
引导目录: Hibernate 系列教程 目录 前言: 通过上一篇的增删改查小练习之后,咱们大概已经掌握了Hibernate的基本用法. 我们发现,在调用Hibernate API的过程中,虽然Hib ...
- Hibernate中对象的三种状态以及Session类中saveOrUpdate方法与merge方法的区别
首先,用一张图说明一个对象,在Hibernate中,在调用了不同方法之后对象所处的不同状态 在Hibernate中,一个对象的状态可以被分为如图所示的三种 Transient:瞬时对象,该对象在数据库 ...
- Hibernate 系列 02 - Hibernate介绍及其环境搭建
引导目录: Hibernate 系列教程 目录 昨晚喝多了,下午刚清醒,继续搞Hibernate.走起. 觉得还行的话,记得点赞哈,给我这个渣渣点学习的动力.有错误的话也请指出,省的我在错误上走了不归 ...
- Hibernate 系列 03 - 使用Hibernate完成持久化操作
引导目录: Hibernate 系列教程 目录 康姆昂,北鼻,来此狗.动次打次,Hibernate继续走起. 目录: 使用Hibernate实现按主键查询 使用Hibernate实现数据库的增.删.改 ...
- Hibernate 系列 08 - 对象识别机制
目录导读: Hibernate 系列 学习笔记 目录 本篇目录: 为了区别不同的对象,有两种识别方法: 1. 内存地址识别(“==”号识别) 2. equals()和hashCode()识别 1. 以 ...
- 【SSH框架】之Hibernate系列一
微信公众号:compassblog 欢迎关注.转发,互相学习,共同进步! 有任何问题,请后台留言联系! 1.Hibernate框架概述 (1).什么是HibernateHibernate是一个开放源代 ...
- Hibernate系列4-----之删除
1.和它的增改查兄弟不同,多了个until包定义了HibernateUntil类,让我们来一起看看吧 public class HibernateUntil { private static Conf ...
- hibernate系列之四
数据库中表之间的关系: 一对一.一对多.多对多 一对多的建表原则:在多的一方创建外键指向一的一方的主键: 多对多的建表原则:创建一个中间表,中间表中至少有两个字段作为外键分别指向多对多双方的主键: 一 ...
随机推荐
- 自定义Sublime Text的图标
sublime text很赞,windows上最接近mac逼格的轻量编辑器,对于我这样比较喜欢格调的人来说,简直不二之选啊. 美中不足的是,看久了觉得它的图标似乎不是很上心.现在都流行扁平化了而它还停 ...
- 【MSP是什么】MSP认证之成功的项目群管理
同项目管理相比,项目群管理是为了实现项目群的战略目标与利益,而对一组项目进行的统一协调管理. 项目群管理 项目群管理是以项目管理为核心.单个项目上进行日常性的项目管理,项目群管理是对多个项目进行的总体 ...
- 2016年8月ios面试问题总结
1.app分发方式 所谓分发方式简单点讲就是你的app都可以通过哪些途径给用户使用. a:个人或者公司的开发者账号 可以上传appStore,用户通过appStore下载. b:企业账号:打包分发. ...
- ILMerge合并多个DLL
序言 如果你的项目要提供多个dll给别人用,那么不妨让你的dll合并为一个,让别人看起来简洁,引用起来不会过于繁琐. 本篇比较少,但也算是比较实用吧. 下载微软的辅助工具ILMerge Imerge下 ...
- 【Win 10 应用开发】共享目标(UWP)
在开始吹牛之前,先给大伙伴们拜个年,祝各位身体健康.生活愉快.[码]到功成. ------------------------------------------------------------- ...
- 【原】SDWebImage源码阅读(二)
[原]SDWebImage源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 解决上一篇遗留的坑 上一篇中对sd_setImageWithURL函数简单分析了一下,还 ...
- Cesium原理篇:Material
Shader 首先,在本文开始前,我们先普及一下材质的概念,这里推荐材质,普及材质的内容都是截取自该网站,我觉得他写的已经够好了.在开始普及概念前,推荐一首我此刻想到的歌<光---陈粒>. ...
- GIS部分理论知识备忘随笔
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.高斯克吕格投影带换算 某坐标的经度为112度,其投影的6度带和3度带 ...
- ASP.NET MVC 登录验证
好久没写随笔了,这段时间没 什么事情,领导 一直没安排任务,索性 一直在研究代码,说实在的,这个登录都 搞得我云里雾里的,所以这次我可能也讲得不是 特别清楚,但是 我尽力把我知道的讲出来,顺便也对自 ...
- windows环境tomcat8配置Solr5.5.1
前言 前前后后接触Solr有一个多月了,想趁着学习Solr顺便把java拾起来.我分别用4.X和5.X版本在windows环境下用jetty的方式.tomcat部署的方式自己搭建了一把.其中从4.x到 ...