Hibernate的事务管理 ---- Hibernate入门学习
叙:本章中本人开始并不算熟悉,因此,我细致的梳理了一遍,可能会显得东西有些多,但是却是事务这个知识点比较全面、深入检出的笔记;
Hibernate事务管理
学习hibernate的事务管理前首先学习一下什么是事务?事务中存在哪些问题?如何解决的?这些都是事务的基础知识,其后才是hibernate的事务管理设置;
事务基础
什么是事务
事务在Java中就是指一个逻辑上的一组操作,组成这个逻辑的逻辑单元要么全部完成,要么全部失败;
事务的特性
1) 原子性:代表事务是不可分割的,要么同时完成要么同时失败,存在例外;
2) 一致性:代表数据执行前后数据保持一致,例如:甲乙买卖,最终两个人的钱和货物相加还是原始量,不会有不明下落的数据;
3) 隔离性:意思是指一个事物的执行过程中不应该和其他的事务有交集;
4) 持久性:代表事务执行完后,数据就会被持久存放到数据库中,不会被执行过的事务所调用;
在事务的特性中重点需要了解的是事物的隔离性,下面进行详细了解;
如果不考虑事务的隔离,会导致的问题:
隔离的目的是为了解决不同的事务同时并发的情况所会存在的问题,防止事务间的相互影响;如果不考虑事务的隔离,则会引发多种问题,总结为两大类,分别是读的问题和写的问题;
读的问题:
1)脏读:是指事物读B到事务A未提交的数据 — 既是:事务B读到的是事务A可能要提交也可能要回滚的数据;
2)不可重复度:是指事务A在进行操作数据并且读取到了数据,而此时事务B突然插队进来也操作数据,并且在A事务提交前进行了事务提交,则会导致事务A读取前后的数据不一致和操作不成功;
3)虚读(幻读):事务A开始操作数据库,把表中数据全部修改成某种样式的,但与此同时,事务B也在修改数据表,数据B的是添加一条原始样式的数据,在两个人都提交后,表中只有一条数据是原始样式的,当事务A回头查看时会发现又一条数据没有修改成新样式;
注意:这个幻读和不可重复度很容易搞混;
辨析:幻读是指之前查询不存在的,中途被其他事务添加了,再次查询却出现了,而不可重复度是指之前查询存在的,中途被其他事务修改了,再次查询却不同了;区别点就在于第一个事务第一次查询是否存在这个数据;
写的问题(了解即可)
1) 引发丢失更新
A事务在回滚时把B事务已经提交的更新数据也给回滚了(当然也有可能是A事务提交后覆盖了B事务的更新操作,导致需要的事务B操作的丢失了更新的结果,不一定非要是回滚,只是回滚更方便理解)!
事务的解决 — 事务的隔离级别
设置事务隔离级别(4种):
隔离级别名称 | 隔离读取级别 | 解决问题级别 |
---|---|---|
Read uncommitted | 读取未提交的数据 | 以上的读问题都会发生 |
Read committed | 读取已提交的数据 | 除脏读其他均可能发生 |
Repeatable read | 读取到已经修改过的 | 只有幻读可能会发生 |
Serializable | 读问题都可以解决 | 串行化序列执行(例:排队上厕所,一个出来后另一个才能用)效率太低 |
隔离级别处理问题能力表:
Oracle使用的是 read committed 的隔离级别来配置事务管理;
MySQL使用的是 repeatable read 的隔离级别来配置事务管理;
事务进阶 — hibernate中设置隔离级别
上面回顾了不隔离导致的问题以及各个隔离级别的作用范围等,现在学习在hibernate中如何设置隔离级别;
hibernate中设置事务的隔离级别
首先,要知道hibernate中设置事务的隔离级别是在核心配置文件中进行的,其次,所使用的标签以及标签所对应的属性值,具体介绍如下:
在核心文件配置中的<session-factory></session-factory>
标签内进行定义的,所使用的方法举例为<property name=”hibernate.connection.isolation”>4</property>
全部代码如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <!-- 核心配置 --> <session-factory> <!--连接数据库的基本参数 --> <!-- 方言 。。。 dialect --> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect </property> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql:///hibernate_day02</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">root</property> <!-- 可选配置 --> <property name="hibernate.show_sql">true</property> <property name="hibernate.format_sql">true</property> <property name="hibernate.hbm2ddl.auto">update</property> <!-- 配置隔离级别 --> <property name="hibernate.connection.isloation">4</property> <!-- 引入orm数据源路径 --> <mapping resource="com/java/demo/Customer.hbm.xml" /> </session-factory> </hibernate-configuration>
配置标签、属性什么的都好说,只要记住就好,主要注意的是标签中间的数字,例中是4,还有其他的1,2,8;这些数字代表的是其对应的隔离级别,具体介绍如下:
隔离级别对应数值 | 隔离级别名称 | 备注 |
---|---|---|
1 | read uncommitted | |
2 | read committed | Oracle使用的隔离级别 |
4 | repeatable read | MySQL使用的隔离级别 |
8 | serializable |
以上就是核心配置文件中的配置方法,配置完核心配置中的后需要使用,具体的是service层在使用;
Service层事务
事务管理等是在Service层的,事务层 = Service层;
为什么要把事务加在Service层?
答:三层架构,Web层、Service层、Dao层;Dao层里封装的是一个个对数据源单一操作的方法,每个对数据的增删改查的业务要分为四个方法,每个方法都要重新创建一个单独的连接(Hibernate中的是Session,JDBC中的是Connection),这样不便于业务逻辑的管理;而在service层中封装的是一个业务逻辑操作(最少两个的对数据源操作的Dao层方法,就比如转账的业务逻辑,由转出方扣钱和转入方加钱的业务统一成一个业务逻辑),service层便于此业务逻辑的事物管理;
回顾事务管理在service层中:
Service层事务管理管理的是一个业务逻辑,其中连接Dao层的多个对数据源单一操作的方法,这样的话就需要使用同一个连接,这个连接在Hibernate中就是Session。
在JDBC中使用的有两种方法,一种是在Service层创建完后传递给Dao层,Dao层的进行调用创建好的同一个连接,第二种是使用ThreadLocal(线程本地变量/线程本地储存)对象,这个对象的原理是将连接保存到线程中,通过线程来传递连接到Dao层;
Hibernate中的事务管理:
在Hibernate框架中提供的有已绑定好的ThreadLocal,存在于SessionFactory中,方法名叫做getCurrentSession(),此方法默认是关闭的,需要手动设置打开,操作方法:
1) 工具类:向生成一般的Session对象所在的工具类中添加可以生成getCurrentSession()方法;添加部分如下所示:
package com.java.hibernate.Utils; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; /* * hibernate生成session的工具类 */ public class HibernateUtils { public static final Configuration conf; public static final SessionFactory sf; static{ //加载读取核心配置文件对象 conf = new Configuration().configure(); //使用核心配置文件对象创建Session工厂,生成链接session连接对象 sf = conf.buildSessionFactory(); } public static Session openSession(){ return sf.openSession(); } //添加 --- 生成线程session代码 public static Session getCurrentSession(){ return sf.getCurrentSession(); } }
2) 配置允许使用getCurrentSession()方法的操作;在核心配置文件中进行配置,配置如下所示:
<property name="hibernate.current_session_context_class">thread</property>
其属性有三个,分别是:
(1) Thread:Session对象生命周期与本地线程绑定!!!
(2) jta:Session对象生命周期与JTA事务绑定(跨数据库事务);
(3) managed: Hibernate委托程序来管理session生命周期;
3) 测试代码:
@Test public void demo1(){ Session session = HibernateUtils.getCurrentSession(); Transaction bt = session.beginTransaction(); Customer cust = session.load(Customer.class, 1l); System.out.println(cust); bt.commit(); }
能通过就算成功
注意:在使用此方法进行操作时后不需要有释放资源的操作(即:session.close()),因为线程会自动关闭一次,如果自己再手动关闭Session就会报错;