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系列之四
数据库中表之间的关系: 一对一.一对多.多对多 一对多的建表原则:在多的一方创建外键指向一的一方的主键: 多对多的建表原则:创建一个中间表,中间表中至少有两个字段作为外键分别指向多对多双方的主键: 一 ...
随机推荐
- 浅谈WebService的版本兼容性设计
在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...
- JS模块化开发:使用SeaJs高效构建页面
一.扯淡部分 很久很久以前,也就是刚开始接触前端的那会儿,脑袋里压根没有什么架构.重构.性能这些概念,天真地以为前端===好看的页面,甚至把js都划分到除了用来写一些美美的特效别无它用的阴暗角落里,就 ...
- Bulk Insert:将文本数据(csv和txt)导入到数据库中
将文本数据导入到数据库中的方法有很多,将文本格式(csv和txt)导入到SQL Server中,bulk insert是最简单的实现方法 1,bulk insert命令,经过简化如下 BULK INS ...
- SQLSERVER聚集索引与非聚集索引的再次研究(下)
SQLSERVER聚集索引与非聚集索引的再次研究(下) 上篇主要说了聚集索引和简单介绍了一下非聚集索引,相信大家一定对聚集索引和非聚集索引开始有一点了解了. 这篇文章只是作为参考,里面的观点不一定正确 ...
- ASP.NET Core管道深度剖析(4):管道是如何建立起来的?
在<管道是如何处理HTTP请求的?>中,我们对ASP.NET Core的请求处理管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.这样一 ...
- 【CSS进阶】伪元素的妙用2 - 多列均匀布局及title属性效果
最近无论是工作还是自我学习提升都很忙,面对长篇大论的博文总是心有余而力不足,但又不断的接触学习到零碎的但是很有意义的知识点,很想分享给大家,所以本篇可能会很短. 本篇接我另一篇讲述 CSS 伪元素的文 ...
- angular2系列教程(三)components
今天,我们要讲的是angualr2的components. 例子
- TokuDB存储引擎
TokuDB是Tokutek公司开发的基于ft-index(Fractal Tree Index)键值对的存储引擎. 它使用索引加快查询速度,具有高扩展性,并支持hot scheme modifica ...
- Hadoop入门学习笔记---part3
2015年元旦,好好学习,天天向上.良好的开端是成功的一半,任何学习都不能中断,只有坚持才会出结果.继续学习Hadoop.冰冻三尺,非一日之寒! 经过Hadoop的伪分布集群环境的搭建,基本对Hado ...
- android使用ImageLoader实现图片缓存(安卓开发必备)
相信大家在学习以及实际开发中基本都会与网络数据打交道,而这其中一个非常影响用户体验的就是图片的缓存了,若是没有弄好图片缓存,用户体验会大大下降,总会出现卡顿情况,而这个问题尤其容易出现在ListVie ...