可以先了解JDBC事务,参考: 【Java】JDBC事务
一、Spring事务的理解
1、事务的定义
事务(Transaction):是并发控制的单元,是用户定义的一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。通过事务,数据库能将逻辑相关的一组操作绑定在一起,以便服务器保持数据的完整性。
事务通常是以begin transaction开始,以commit或rollback结束:
Commint表示提交,即提交事务的所有操作。具体地说就是将事务中所有对数据的更新写回到磁盘上的物理数据库中去,事务正常结束。
Rollback表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态。
2、事务的特性
1) 原子性(atomicity):事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。
2) 一致性(consistency):事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。
3) 隔离性(isolation):一个事务的执行不能被其他事务所影响。
4) 持久性(durability):一个事务一旦提交,事物的操作便永久性的保存在DB中。即使此时再执行回滚操作也不能撤消所做的更改。
3、事务的类型
3.1 数据库分为本地事务跟全局事务
-
本地事务:普通事务,独立一个数据库,能保证在该数据库上操作的ACID。
-
分布式事务:涉及两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成的),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库;
3.2 Java事务类型分为JDBC事务跟JTA事务
-
JDBC事务:即为上面说的数据库事务中的本地事务,通过connection对象控制管理。
-
JTA事务:JTA指Java事务API(Java Transaction API),是Java EE数据库事务规范, JTA只提供了事务管理接口,由应用程序服务器厂商(如WebSphere Application Server)提供实现,JTA事务比JDBC更强大,支持分布式事务。
3.3 按是否通过编程分为声明式事务和编程式事务
-
声明式事务:通过AOP(面向切面)方式在方法前使用编程式事务的方法开启事务,在方法后提交或回滚。用配置文件的方法或注解方法(如:@Transactional)控制事务。
-
编程式事务:手动开启、提交、回滚事务。
通俗地去理解两者的区别,即声明式事务只需要“声明”就可以达到事务的效果;编程式事务需要“编程”才可以达到事务效果。
二、Spring事务隔离级别
Spring有五大隔离级别,其在TransactionDefinition接口中定义。看源码可知,其默ISOLATION_DEFAULT(底层数据库默认级别),其他四个隔离级别跟数据库隔离级别一致。
- ISOLATION_DEFAULT(默认)
用底层数据库的默认隔离级别,数据库管理员设置什么就是什么
-
ISOLATION_READ_UNCOMMITTED(读未提交)
即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用
-
ISOLATION_READ_COMMITTED(读已提交)
即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读
-
ISOLATION_REPEATABLE_READ(重复读取)
即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决
-
ISOLATION_SERIALIZABLE(串行化)
最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
读已提交(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
设置隔离级别,解决事务并发问题
举个例子,事务A和事务B操纵的是同一个资源,事务A有若干个子事务,事务B也有若干个子事务,事务A和事务B在高并发的情况下,会出现各种各样的问题。"各种各样的问题",总结一下主要就是五种:第一类丢失更新、第二类丢失更新、脏读、不可重复读、幻读。五种之中,第一类丢失更新、第二类丢失更新不重要,不讲了,讲一下脏读、不可重复读和幻读。
1、脏读
所谓脏读,就是指事务A读到了事务B还没有提交的数据,比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务-->取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。
2、不可重复读
所谓不可重复读,就是指在一个事务里面读取了两次某个数据,读出来的数据不一致。还是以银行取钱为例,事务A开启事务-->查出银行卡余额为1000元,此时切换到事务B事务B开启事务-->事务B取走100元-->提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。
3、幻读
所谓幻读,就是指在一个事务里面的操作中发现了未被操作的数据。比如学生信息,事务A开启事务-->修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务-->事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。
三、传播行为
有七大传播行为,也是在TransactionDefinition接口中定义
- PROPAGATION_REQUIRED(默认)
(需要)支持当前事务(即融合到当前事务中),如当前没有事务,则新建一个。
- PROPAGATION_SUPPORTS
(支持)支持当前事务(即融合到当前事务中),如当前没有事务,则以非事务方式执行
- PROPAGATION_MANDATORY
(强制)支持当前事务(即融合到当前事务中),如当前没有事务,则抛出异常(强制一定要在一个已经存在的事务中执行,业务方法不可独自发起自己的事务)。
- PROPAGATION_REQUIRES_NEW
(需要新的)始终新建一个事务,如当前原来有事务,则把原事务挂起。
- PROPAGATION_NOT_SUPPORTED
(不支持)不支持当前事务,始终以非事务方式执行,如当前事务存在,挂起该事务。
- PROPAGATION_NEVER
(绝不)不支持当前事务,以非事务方式执行,如果当前事务存在,则引发异常。
- PROPAGATION_NESTED
(嵌套)如果当前事务存在,则在嵌套事务中执行,如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作(注意:当应用到JDBC时,只适用JDBC 3.0以上驱动)。
事务传播行为验证,参考:https://www.cnblogs.com/haha12/p/11855001.html
四、Spring事务属性
1、事务超时
指定事务的最大运行时间。使用int指定,单位是秒。
2、事务的只读属性
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。
3、事务的回滚规则
默认出现RuntimeException就会回滚。如果没有抛出任何异常,或者抛出了已检查异常,则仍然提交事务。这通常也是大多数开发者希望的处理方式,也是 EJB 中的默认处理方式。(这里个人理解的是已检查异常,是我们定义的checkedException,包括我们自定义的异常和调用方法捕获的异常)
五、Spring事务支持
1、spring提供了很多内置事务管理器,支持不同数据源
常见的有三大类
- DataSourceTransactionManager:
org.springframework.jdbc.datasource包下,数据源事务管理类,提供对单个javax.sql.DataSource数据源的事务管理,只要用于JDBC,Mybatis框架事务管理。
- HibernateTransactionManager:
org.springframework.orm.hibernate3包下,数据源事务管理类,提供对单个org.hibernate.SessionFactory事务支持,用于集成Hibernate框架时的事务管理;注意:该事务管理器只支持Hibernate3+版本,且Spring3.0+版本只支持Hibernate 3.2+版本;
- JtaTransactionManager:
位于org.springframework.transaction.jta包中,提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器,或者自定义一个本地JTA事务管理器,嵌套到应用程序中。
内置事务管理器
内置事务管理器都继承了抽象类AbstractPlatformTransactionManager,而AbstractPlatformTransactionManager又继承了接口PlatformTransactionManager
Spring框架支持事务管理的核心是事务管理器抽象,对于不同的数据访问框架通过实现策略接口PlatformTransactionManager,从而能支持多钟数据访问框架的事务管理。
PlatformTransactionManager接口定义如下
1 public interface PlatformTransactionManager { 2 3 // 返回一个已经激活的事务或创建一个新的事务(具体由TransactionDefinition参数定义的事务属性决定), 4 // 返回的TransactionStatus对象代表了当前事务的状态,其中该方法抛出TransactionException(未检查异常)表示事务由于某种原因失败。 5 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 6 7 // 用于提交TransactionStatus参数代表的事务。 8 void commit(TransactionStatus status) throws TransactionException; 9 10 // 用于回滚TransactionStatus参数代表的事务。 11 void rollback(TransactionStatus status) throws TransactionException; 12 }
TransactionDefinition接口定义如下:
1 public interface TransactionDefinition { 2 // 返回定义的事务传播行为 3 int getPropagationBehavior(); 4 // 返回事务隔离级别 5 int getIsolationLevel(); 6 // 返回定义的事务超时时间 7 int getTimeout(); 8 // 返回定义的事务是否是只读的 9 boolean isReadOnly(); 10 // 返回事务名称 11 String getName(); 12 }
TransactionStatus接口定义如下:
1 public interface TransactionStatus extends SavepointManager { 2 // 返回当前事务是否是新的事务 3 boolean isNewTransaction(); 4 // 返回当前事务是否有保存点 5 boolean hasSavepoint(); 6 // 设置事务回滚 7 void setRollbackOnly(); 8 // 设置当前事务是否应该回滚 9 boolean isRollbackOnly(); 10 // 用于刷新底层会话中的修改到数据库,一般用于刷新如Hibernate/JPA的会话,可能对如JDBC类型的事务无任何影响; 11 void flush(); 12 // 返回事务是否完成 13 boolean isCompleted(); 14 }
2、Spring分布式事务配置
-
引用应用服务器(如Tomcat)的JNDI数据源,间接实现JTA事务管理,依赖于应用服务器
-
直接集成JOTM(官网:http://jotm.objectweb.org/)、Atomikos(官网:https://www.atomikos.com/)提供JTA事务管理(无应用服务器支持,常用于单元测试)
-
使用特定于应用服务器的事务管理器,使用JTA事务的高级功能(Weblogic,Websphere)
参考:https://blog.csdn.net/liaohaojian/article/details/68488150