[Spring] Spring 探秘 之 事务配置
[capsule-spring-boot-transaction] Spring 探秘 之 事务配置
准备工作
>>> [Spring Boot] Spring boot 整合mybatis、postgresql [Gradle构建项目]
数据库事务简介
何为数据库事务?
是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。事务具有四个特性:原子性、一致性、隔离性、持久性。
- 原子性:表示组成一个事务的多个数据库操作是一个不可分割的原子单元。事务中的操作要么都发生,要么都不发生。
- 一致性:事务前后数据的完整性必须保持一致
- 隔离性:指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离,
- 持久性:指一个事务一旦被提交,它对数据库的数据的改变是永久的,即使数据库发生故障。
Spring声明式事务管理
概述
1、Spring 的声明式事务管理在底层是建立在 AOP 的基础上。其本质是在方法前后进行拦截,然后在目标方法开始之前创建一个事务,在执行这目标方法结束后,根据执行情况提交或进行回滚事务。
2、声明式事务最大的优点就是不需通过编程的方式而进行管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明,便可将事务规则应用到业务逻辑中。
3、声明式事务不足的地方在于,与编程式事务相比,只能作用到方法级别,无法像编程式事务那样可以作用到代码块级别
编程模板
Spring为事务管理提供了一致的编程模板,把事务处理的主要过程抽象出来,不管底层选择的是mybatis、hibernate操作数据库,都可以使用统一的事务编程模型。Spring通过AOP切面增强实现声明式事务处理的功能。
- 事务运行状态:org.springframework.transaction.TransactionStatus
- 事务定义信息(隔离级别、传播等):org.springframework.transaction.TransactionDefinition
- 事务同步管理器:org.springframework.transaction.PlatformTransactionManager
事务管理器的配置
Spring 将事务管理委托给底层具体的持久化实现框架来完成。因此,Spring为不同的持久化框架提供了PlatformTransactionManager 接口的实现类。
实现类 | 描述 |
---|---|
org.springframework.transaction.jta.JtaTransactionManager | 使用JPA进行持久化时,使用该事务管理器 |
org.springframework.orm.hibernate3.HibernateTransactionManager | 使用Hibernate X.0(X 可以为3/4/5)版本进行持久化时,使用该事务管理器 |
org.springframework.jdbc.datasource.DataSourceTransactionManager | 使用Spring JDBC 或 Mybatis 等基于DataSource数据源的持久化技术时,使用 该事务管理器 |
org.springframework.orm.jdo.JdoTransactionManager | 使用JDO进行持久化时 ,使用该事务管理器 |
org.springframework.transaction.jta.JtaTransactionManager | 具有多个数据源的全局事务使用该事务管理器(不管采用何种持久化技术) |
Spring将JDBC的Connection
、Hibernate的Session
等访问数据库的连接或会话对象统称为资源。这些资源在同一时刻是不能多线程共享的。为了让DAO
、Service
类能租到singleton
。
Spring的事务同步管理类org.springframework.transaction.support.TransactionSynchronization
使用ThreadLcoal
为不同事务线程提供了独立的资源副本,同时维护事务配置的属性和运行状态信息。事务同步管理器是Spring事务管理的基石,不管用户使用的是编程式事务管理,还是声明式事务管理,都离不开同步管理器。
Spring框架为不同的持久化技术提供了一套从TransactionSynchronization
中获取对应线程绑定资源的工具类,如下:
持久化技术 | 线程绑定资源获取工具 |
---|---|
Spring JDBC 或 Mybatis | org.springframework.jdbc.datasource.DataSourceUtils |
Hibernate X .0 | org.springframework.orm.hibernate3.SessionFactoryUtils |
JPA | org.springframework.orm.jpa.EntityManagerFactoryUtils |
JDO | org.springframework.orm.jdo.PersistenceManagerFactoryUtils |
Spring配置事务管理器和策略
xml形式
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务策略
1、<tx:advice>定义事务通知,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过<tx:attributes>指定具体需要拦截的方法
2、<tx:method>拦截方法,其中参数有:
name:方法名称,将匹配的方法注入事务管理,可用通配符(*)可以用来指定一批关联到相同的事务属性的方法。 如:'find*'、'handle*'、'on*Event'等等。
propagation:事务传播行为,
isolation:事务隔离级别定义;默认为“DEFAULT”
timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
read-only:事务只读设置,默认为false,表示不是只读;
rollback-for:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚;
no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception"/>
<tx:method name="insert*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception"/>
<tx:method name="del*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="modify*" propagation="REQUIRED" read-only="false"
rollback-for="java.lang.Exception" />
<tx:method name="handle*" propagation="NOT_SUPPORTED" read-only="false" />
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 配置事务切点
1、其中 and,or,需要注意大小写,可以写成 &&,||.
2、execution是Spring AOP中最主要的切入点指示符,该切入点的用法相对复杂,execution表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
上面格式中的execution是不变的,用于作为execution表达式的开头,整个表达式中的解释如下:
modifiers-pattern:指定方法的修饰符,支持通配符,该部分可以省略
ret-type-pattern:指定方法的返回值类型,支持通配符,可以使用"*"通配符来匹配所有的返回值类型。
declaring-type-pattern:指定方法所属的类,支持通配符,在包名之间的..代表当前包和其子包,该部分可以省略。
name-pattern:指定匹配指定的方法名,支持通配符,可以使用"*"通配符来匹配所有方法。
param-pattern:指定方法声明中的形参列表,支持两个通配符,即"*"和"..",其中“*”代表一个任意类型的参数,而“..”代表零个或多个任意类型的参数。例如,()匹配一个不接受任何参数的方法,而(..)匹配一个接受任意数量参数的方法,(*)匹配了一个接受一个任何类型的参数的方法,(*,String)匹配了一个接受两个参数的方法,其中第一个参数是任意类型,第二个参数必须是String类型。
throws-pattern:指定方法声明抛出的异常,支持通配符,该部分可以省略.
-->
<aop:config>
<aop:pointcut id="pc"
expression="(execution(* com.x.xx.xxx..*.service.*.*(..)))
or (execution(* com.x.xx.xxx..*.thread.*.*(..)))
or (execution(* com.x.xx.xxx..*.mq.*.*(..)))" />
<aop:advisor pointcut-ref="pc" advice-ref="txAdvice" />
</aop:config>
说明:
-
异常的继承结构
:Throwable
为基类,Error
和Exception
继承Throwable
。Error
和RuntimeException
及其子类称为未检查异常(unchecked),其它异常成为已检查异常(checked)。 -
事务配置
:告知数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。
Spring 事务传播规则
当我们调用一个基于Spring的Service接口方法(如UserService#addUser())时,它将运行于Spring管理的事务环境中,Service接口方法可能会在内部调用其它的Service接口方法以共同完成一个完整的业务操作,因此就会产生服务接口方法嵌套调用的情况, Spring通过事务传播行为控制当前的事务如何传播到被嵌套调用的目标服务接口方法中。
在这里插入图片描述Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播:
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 指定当前方法必需在事务环境中运行,如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 指定当前方法加入当前事务环境,支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 指定当前方法必须加入当前事务环境,使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 指定当前方法总是会为自己发起一个新的事务,如果发现当前方法已运行在一个事务中,则原有事务被挂起,我自己创建一个属于自己的事务,直我自己这个方法commit结束,原先的事务才会恢复执行。 |
PROPAGATION_NOT_SUPPORTED | 指定当前方法以非事务方式执行操作,如果当前存在事务,就把当前事务挂起,等我以非事务的状态运行完,再继续原来的事务。 |
PROPAGATION_NEVER | 指定当前方法绝对不能在事务范围内执行,如果方法在某个事务范围内执行,容器就抛异常,只有没关联到事务,才正常执行。(以非事务方式执行,如果当前存在事务,则抛出异常) |
PROPAGATION_NESTED | 指定当前方法执行时,如果已经有一个事务存在,则运行在这个嵌套的事务中.如果当前环境没有运行的事务,就新建一个事务,并与父事务相互独立,这个事务拥有多个可以回滚的保证点。就是指我自己内部事务回滚不会对外部事务造成影响,只对DataSourceTransactionManager事务管理器起效。 |
说明:
当使用PROPAGATION_NESTED
时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制.
事务隔离的必要性
-
不考虑隔离性的话会出现以下问题:
-
脏读
:一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的. -
不可重复读
:一个事务重新读取之前读取过的数据,发现该数据已经被另一个事务(在初始读之后提交)修改. -
幻读
:一个事务重新执行一个返回符合一个搜索条件的行集合的查询, 发现满足条件的行集合因为另一个最近提交的事务而发生了改变.
-
而隔离级别就是解决以上问题的。
- 隔离级别
隔离级别 | 说明 |
---|---|
ISOLA+TION_DEFAULT | Spring默认选项,使用数据库的默认隔离级别(不同的数据库会有所区别) |
ISOLATION_READ_UNCOMMITTED | 读未提交:允许读取还未提交的改变了的数据,可能导致脏读、幻读、不可重复读 |
ISOLATION_READ_COMMITTED | 读已提交:允许在并发事务已经提交后读取,可防止脏读(幻读和不可重复读可能会发生) |
ISOLATION_REPEATABLE_READ | 可重复读:对相同字段的读取是一致的(除了数据库事务改变),防止脏读、不可重复读。 |
ISOLATION_SERIALIZABLE | 可序列化:串行化,完全遵从ACID的隔离级别,效率也是最低的。 |
Pg、Oracle默认的隔离级别为读已提交。
事务嵌套
REQUIRED(REQUIRES_NEW) 内层会新建一个事务,代码执行完成后,直接提交事务
/*UserInfoServiceImpl.propagationRequired*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void propagationRequired() {
log.debug("---------------propagationRequired");
transationSevice.propagationRequiresNew();
log.debug("---------------propagationRequired");
}
/*TransationSeviceImpl.propagationRequiresNew*/
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void propagationRequiresNew() {
log.debug("---------------propagationRequiresNew");
}
日志
:
REQUIRED(REQUIRES_NEW[Exception]) 内层事务异常不会影响外层事务
/*UserInfoServiceImpl.propagationRequiredRollback*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void propagationRequiredRollback() {
try {
log.debug("---------------propagationRequiredRollback");
transationSevice.propagationRequiresNewRollBack();
log.debug("---------------propagationRequiredRollback");
} catch (Exception e) {
log.error("---------------excaption");
}
}
/*TransationSeviceImpl.propagationRequiresNewRollBack*/
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void propagationRequiresNewRollBack() {
log.debug("-------------------propagationRequiresNewRollBack");
throw new RuntimeException();
}
在这里插入图片描述
REQUIRED(REQUIRES_NEW)[Exception] 外层事务异常不会影响内层事务
/*UserInfoServiceImpl.propagationRequiredCurrentRollback*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void propagationRequiredCurrentRollback() {
log.debug("---------------propagationRequiredCurrentRollback");
transationSevice.propagationRequiresNew();
log.debug("---------------propagationRequiredCurrentRollback");
throw new RuntimeException();
}
/*TransationSeviceImpl.propagationRequiresNew*/
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void propagationRequiresNew() {
log.debug("---------------propagationRequiresNew");
}
在这里插入图片描述
REQUIRED(NESTED)内部嵌套事务结束后和外部事务一起提交
/*UserInfoServiceImpl.propagationRequiredAndNested*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void propagationRequiredAndNested() {
log.debug("---------------propagationRequiredAndNested");
UserInfo userInfo = new UserInfo("xx1",1,"xx1","xx","xx",1,new Date());
insertSelective(userInfo);
transationSevice.propagationNested();
log.debug("---------------propagationRequiredAndNested");
userInfo = new UserInfo("xx2",1,"xx2","xx","xx",1,new Date());
insertSelective(userInfo);
}
/*TransationSeviceImpl.propagationNested*/
@Override
@Transactional(propagation = Propagation.NESTED)
public void propagationNested() {
log.debug("-------------------propagationNested");
UserInfo userInfo = new UserInfo("xx3",1,"xx1","xx","xx",1,new Date());
userInfoService.insertSelective(userInfo);
}
在这里插入图片描述
在这里插入图片描述
REQUIRED(NESTED[Exception]) 虽然内部事务嵌套在外部事务中,但内部回滚时只回滚内部的操作不影响外部
/*UserInfoServiceImpl.propagationRequiredAndNested*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void propagationRequiredAndNested() {
try {
log.debug("---------------propagationRequiredAndNested");
UserInfo userInfo = new UserInfo("xx1", 1, "xx1", "xx", "xx", 1, new Date());
insertSelective(userInfo);
transationSevice.propagationNested();
log.debug("---------------propagationRequiredAndNested");
userInfo = new UserInfo("xx2", 1, "xx2", "xx", "xx", 1, new Date());
insertSelective(userInfo);
} catch (Exception e) {
log.error("---------------excaption");
}
}
/*TransationSeviceImpl.propagationNested*/
@Override
@Transactional(propagation = Propagation.NESTED)
public void propagationNested() {
log.debug("-------------------propagationNested");
UserInfo userInfo = new UserInfo("xx3",1,"xx1","xx","xx",1,new Date());
userInfoService.insertSelective(userInfo);
throw new RuntimeException();
}
在这里插入图片描述
在这里插入图片描述
REQUIRED(NESTED)[Exception] 外部事务回滚导致内部也不会提交
/*UserInfoServiceImpl.propagationRequiredAndNested*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void propagationRequiredAndNested() {
log.debug("---------------propagationRequiredAndNested");
UserInfo userInfo = new UserInfo("xx1", 1, "xx1", "xx", "xx", 1, new Date());
insertSelective(userInfo);
transationSevice.propagationNested();
log.debug("---------------propagationRequiredAndNested");
userInfo = new UserInfo("xx2", 1, "xx2", "xx", "xx", 1, new Date());
insertSelective(userInfo);
throw new RuntimeException();
}
/*TransationSeviceImpl.propagationNested*/
@Override
@Transactional(propagation = Propagation.NESTED)
public void propagationNested() {
log.debug("-------------------propagationNested");
UserInfo userInfo = new UserInfo("xx3",1,"xx1","xx","xx",1,new Date());
userInfoService.insertSelective(userInfo);
}
REQUIRED(NESTED)[Exception]事务日志
REQUIRED(NESTED)[Exception]数据表
REQUIRED(REQUIRED[Exception]) 内部回滚,外部进行异常捕获,试图使当前事务不回滚的情况下报错
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
错误信息提示为已将当前事务标记为回滚
状态
/*UserInfoServiceImpl.propagationRequired*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void propagationRequired() {
try {
log.debug("---------------propagationRequired");
transationSevice.propagationRequired();
log.debug("---------------propagationRequired");
} catch (Exception e) {
log.error("---------------excaption");
}
}
/*TransationSeviceImpl.propagationRequired*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void propagationRequired() {
log.debug("----------------propagationRequired");
throw new RuntimeException();
}
REQUIRED(REQUIRED[Exception])事务日志
REQUIRED(REQUIRED[Exception])数据表为空
尝试在外层进行事务提交
:
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void propagationRequired() {
UserInfo userInfo = new UserInfo("xx1", 1, "xx1", "xx", "xx", 1, new Date());
insertSelective(userInfo);
transationSevice.propagationNested();
try {
log.debug("---------------propagationRequired");
transationSevice.propagationRequired();
log.debug("---------------propagationRequired");
} catch (Exception e) {
log.error("---------------excaption");
}
UserInfo userInfo2 = new UserInfo("xx2", 1, "xx1", "xx", "xx", 1, new Date());
insertSelective(userInfo2);
transationSevice.propagationNested();
}
REQUIRED(REQUIRED[Exception])事务日志
REQUIRED(REQUIRED[Exception])数据表为空
相同传播规则的事务,内部异常会导致整体事务无法提交,直接回滚。
事务注解 和 AOP切面的环绕通知
- 内部调用无法触发事务
/*TransationSeviceImpl.propagationRequired*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void propagationRequired() {
log.debug("---------------propagationRequired");
propagationRequiresNew();//或this.propagationRequiresNew();
log.debug("---------------propagationRequired");
}
/*TransationSeviceImpl.propagationRequiresNew*/
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void propagationRequiresNew() {
log.debug("---------------propagationRequiresNew");
}
内部调用无法触发事务
- 非接口定义的内部方法无法触发事务
/*TransationSeviceImpl.propagationRequired*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void propagationRequired() {
log.debug("---------------propagationRequired");
propagationRequiresNew();
log.debug("---------------propagationRequired");
}
/*TransationSeviceImpl.propagationRequiresNew2*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void propagationRequiresNew2() {
log.debug("---------------propagationRequiresNew2");
}
非接口定义的内部方法无法触发事务
- 接口实例注入后触发AOP环绕通知
@Resource
private UserInfoService userInfoService;
/*TransationSeviceImpl.propagationRequired*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void propagationRequired() {
log.debug("---------------propagationRequired");
userInfoService.propagationRequiresNew();
log.debug("---------------propagationRequired");
}
/*TransationSeviceImpl.propagationRequiresNew2*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void propagationRequiresNew2() {
log.debug("---------------propagationRequiresNew2");
}
接口实例注入后触发AOP环绕通知
备注
Spring Boot 只开启事务日志
logging:
level:
root: WARN
com:
example: DEBUG
org:
springframework:
jdbc: DEBUG
REFRENCES
更多
架构探险之道扫码关注或搜索架构探险之道
获取最新文章,不积跬步无以至千里,坚持每周一更,坚持技术分享^_^
!
网友评论