Spring事务管理总结
事务定义
事务管理对于企业应用来说是至关重要的,主要作用是用来保证数据的一致性,比如转账问题。如下伪代码所示:
transaction begin
A账户扣100
B账户加100
transaction commit
要么这两部操作都完成,要么都不做(原子性),否则数据就不完整。因为在一个事务中的操作可以看成是一个统一整体,所以事务可以定义为一个不可分割的工作单元。
一个事务可以以两种方式结束:提交或者回滚。当事务提交时,所有对数据的修改都会保存。如果事务中的某一部操作失败了,则事务回滚,并且在事务中的所有对数据的改动全部取消。所以即便是事务失败了,数据的完整性依然能够保证(一致性)。
事务四大特性(ACID)
- 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。
- 一致性(Consistency):事务在完成时,必须使所有的数据都保持一致状态(事务前后状态是一致的)。还是用上面的例子,A账户有1000元,B账户有500元,加起来是1500,A向B转账100元,事务结束后,A是900元,B是600元,加起来还是1500元,即使中间出问题了,事务回退,A和B的总余额还是1500元,这就是一致性。
- 隔离性(Isolation):由并发事务所做的修改必须与任何其它并发事务所作的修改隔离。
- 持久性(Durability):事务完成后,对系统的影响是永久性的。
Spring事务管理
对事务的支持是Spring的一大特性,Spring对其事务管理提供了一致地抽象,具有如下优点:
- 为不同的事务管理API如JTA(Java Transaction API)、JDBC、Hibernate、JPA(Java Persistence API)、JDO(Java Data Objects)提供了一致地编程模型;
- 支持声明式事务管理;
- 提供比JTA更简单易用地编程式事务管理API;
- 和Spring数据访问抽象的完美集成;
什么是Spring提供的一致编程模型(consistent programming model)?
在Spring之前Java EE开发人员可以通过两种方式来进行事务管理:全局事务和局部事务。全局事务允许处理多个事务源,典型关系数据库和消息队列。应用服务器通过JTA(Java Transaction API)来管理全局事务,而JTA又需要用到JNDI,通常JTA只能在应用服务器环境下使用,因此全局事务会限制代码的复用性。而局部事务比如JDBC事务,虽然比较简单,可以实现最基本的事务操作,但是只能处理同一个数据源的操作,如果涉及到多数据的操作或者分布式场景,JDBC事务就无能为力了。
Spring通过提供一个一致的编程模型来解决全局事务和局部事务的缺点,所谓的一致的编程模型其实可以理解为Spring对事务的抽象,即Spring只提供了事务管理器的接口:org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,各个平台自己负责实现具体的事务管理,Spring官方的说法叫事务策略(transaction strategy)。PlatformTransactionManager接口如下:
public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
getTransaction(..)方法根据传入的TransactionDefinition参数返回了一个TransactionStatus对象,TransactionStatus代表一个新的事务或者一个已存在的事务。TransactionDefinition是一个接口,主要设置事务隔离级别、传播特性、超时间隔和是否只读。
不管是使用声明式还是编程式事务管理,都需要设置具体的事务管理器(PlatformTransactionManager接口的实现),在Spring中通过依赖注入的方式来指定,springboot会根据具体的依赖完成自动注入。
Spring提供了许多内置事务管理器实现,常用的有HibernateTransactionManager和DataSourceTransactionManager:
- DataSourceTransactionManager:数据源事务管理器,提供对单个javax.sql.DataSource事务管理,用于Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理;
- HibernateTransactionManager:提供对单个org.hibernate.SessionFactory事务支持,用于集成Hibernate框架时的事务管理;
- JtaTransactionManager:提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器事务管理器;
- JpaTransactionManager:提供对单个javax.persistence.EntityManagerFactory事务支持,用于集成JPA实现框架时的事务管理;
- JdoTransactionManager:提供对单个javax.jdo.PersistenceManagerFactory事务管理,用于集成JDO框架时的事务管理;
声明式事务管理和编程式事务管理
spring支持编程式事务管理和声明式事务管理两种方式。
编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。
Spring声明式事务的配置
Spring Boot 使用事务非常简单,首先在启动类上添加@EnableTransactionManagement 注解,启用注解事务管理,这等同于xml配置方式的 <tx:annotation-driven />。然后在访问数据库的Service方法上添加注解@Transactional即可。
在Spring Boot中,关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager。当我们使用了spring-boot-starter-jdbc的时候,Spring Boot会自动注入DataSourceTransactionManager启用帮助配置数据库事务相关的类;如果你添加的是 spring-boot-starter-data-jpa 依赖,框架会默认注入 JpaTransactionManager 实例。
@Transactional 注解可以作用于接口、接口方法、类以及类方法上,作用于单个方法上时该方法将支持事务;作用在类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到public方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
在@Transactional 后可以设置属性,来设置事务的具体语义,有如下属性:
value:类型是字符串,可以设置具体的事务管理器,如@Transactional(value="txManager1")
大多数应用只需单个事务管理器,但是有些场合需要多个事务管理器,则需要人为的指定使用哪个事务管理器。可以在启动类中添加如下方法,Debug测试,就能知道自动注入的是 PlatformTransactionManager 接口的哪个实现类。
@Bean public Object testBean(PlatformTransactionManager platformTransactionManager) { System.out.println("------>>" + platformTransactionManager.getClass().getName()); return new Object(); }
propagation:类型为枚举,用于设置事务的传播行为,例如@Transactional(propagation = Propagation .REQUIRED )
- REQUIRED :如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- MANDATORY :如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。
- NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED
isolation:类型为枚举,用于设置事务的隔离级别,例如:@Transactional(isolation = Isolation.DEFAULT)
- DEFAULT :这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是: READ_COMMITTED 。
- READ_UNCOMMITTED :该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
- READ_COMMITTED :该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
- REPEATABLE_READ :该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
- SERIALIZABLE :所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
readOnly:类型为boolean,用于设置事务为只读或读写
timeout:类型为int,用于设置事务的超时时间
rollbackFor:一系列类,用于设置出现某些异常(继承自Throwable)时一定会触发事务回退,如@Transactional(rollbackFor=Exception.class)
rollbackForClassName:一系列类名,用于设置出现某些异常(继承自Throwable)时一定会触发事务回退
noRollbackFor:一系列类,用于设置出现某些异常(继承自Throwable)时一定不会触发事务回退
noRollbackForClassName:一系列类名,用于设置出现某些异常(继承自Throwable)时一定不会触发事务回退
如果未设置事务属性,则spring会为事务指定默认属性,默认传播行为:PROPAGATION_REQUIRED,默认隔离级别:ISOLATION_DEFAULT,事务为读写,默认RuntimeException会触发回退,而检查异常则不会触发回退。
参考文献: