文章目录

  • Spring事务管理
  • Spring事务API
  • 事务管理器接口
  • 常用的两个实现类
  • Spring的回滚方式
  • 事务定义接口
  • 事务的四种隔离级别
  • 几种读问题的区别
  • 事务的七种传播行为
  • 事务管理示例
  • 用AspectJ的AOP管理事务
  • 依赖
  • 配置spring-context.xml
  • 使用注解管理事务
  • 使用Spring注解管理事务
  • @Transactional 注解简介
  • 测试事务


Spring事务管理

事务用于数据库的访问,但是一般情况下,需要将事务提升到业务层,即Service层。这样做是为了能够使用事务的特性来管理具体的业务。
在Spring中通常可以通过以下三种方式来实现对事务的管理:

  1. 使用Spring的事务代理工厂管理事务(已废弃)
  2. 使用Spring的事务注解管理事务(目前最常用)
  3. 使用AspectJ的AOP配置管理事务

Spring事务API

Spring的事务管理,主要用到两个事务相关的接口。

事务管理器接口

返回目录 事务管理器是PlatformTransactionManager接口对象。其主要用于完成事务的提交、回滚,以及获取事务的状态信息。
该接口定义了3个事务方法:

  • void commit(TransactionStatus status) : 事务的提交
  • TransactionStatus getTransaction(TransactionDefinition definition) : 获取事务的状态
  • void roolback(TransactionStatus status) : 事务的回滚

常用的两个实现类

PlatformTransactionManager接口有两个常用的事务实现类:

  • DataSourceTransactionManager : 使用JDBC或Mybatis进行持久化数据时使用
  • HibernateTransactionManager : 使用Hibernate进行持久化数据时使用

Spring的回滚方式

Spring事务的默认回滚方式是 : 发生运行时异常回滚

事务定义接口

返回目录 事务定义接口TransactionDefinition中定义了事务描述相关的三类常量:事务隔离级别事务传播行为事务默认超时时限,以及对他们的操作。

事务的四种隔离级别

  • default : 采用DB默认的事务隔离级别,MySQL默认为REPEATABLE_READ;Oracle默认为READ_COMMITTED
  • READ_UNCOMMITTED:读未提交。未解决任何并发问题
  • READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读
  • ERPEATABLE_READ:可重复读。解决脏读,不可重复读,存在幻读
  • SERIALIZABLE:串行化。不存在并行问题

几种读问题的区别

返回目录

  • 脏读:修改数据的事务提交失败发生回滚

务A 执行A.a()

事务B 执行B.b()

TbUser

TbUser

读取到TbUser.id=1

-

-

修改了id=2

再次读取id=2

-

-

修改提交失败,回滚为id=1

  • 不可重复读:修改数据的事务提交成功后,另一个事务无法重复读取相同数据

务A 执行A.a()

事务B 执行A.a()

TbUser

TbUser

读取TbUser.id=1

修改TbUser.id=2

-

提交修改

再次读取TbUser.id=1的数据

-

  • 幻读:数据的条目数发生变化

务A 执行A.a()

事务B 执行B.b()

TbUser

TbUser

读取n条数据

-

-

add一条数据,数据量n+1

事务的七种传播行为

返回目录 事务的传播是指处于不同事务中的方法互相调用时, 方法执行期间事务的维护情况。比如,事务A中的方法a()调用事务B中的方法b(),在调用期间事务的维护情况,就称为事务传播行为。即事务传播行为是加在方法上的
假如事务A的执行过程是:A.a(){ B.b()},事务B的执行过程是:B.b(),那么传播行为执行事务A时,方法B.b()是在事务A中执行还是在事务B中执行。以下举例都以此事务A的执行过程为假设前提。

  • REQUIRED : 指定的方法必须在事务内执行.若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个事务.这种事务最常见,也是Spring的默认传播行为
    过程 : 假如B.b()方法是REQUIRED,如果A.a()有事务,则执行B.b(),如果没有事务,则为B.b()创建一个事务执行
  • SUPPORTS : 指定的方法支持当前事务,但若当前没有事务,则也可以以非事务执行
    过程: 无论A.a()是否有事务,B.b()都可以继续执行
  • MANDATORY:指定的方法必须在当前事务内执行,若当前没有事务,则抛出异常
    过程: 如果A.a()有事务,则B.b()正常执行,否则B.b()抛出异常
  • REQUIRES_NEW:总是新建一个事务,若当前事务存在,就将当前事务挂起,直到新事务执行完成
    过程: 如果A.a()有事务,则挂起,并为B.b()创建一个新事务C,直到新事务C执行完成,才继续执行A.a()事务。如果A.a()没有事务,也为B.b()创建一个新事务
  • NOT_SUPPORTED:指定的方法不能在事务环境中执行,如果当前存在事务,就将事务挂起
    过程: 如果A.a()有事务,则挂起,直到B.b()执行完成后,继续执行A.a()的事务
  • NEVER:指定的方法不能在事务环境下执行,若当前存在事务,直接抛出异常
    过程: 如果A.a()有事务,则B.b()抛出异常
  • NESTED:指定的方法必须在事务内执行,无论当前是否存在事务,都创建一个事务执行B.b(),但是A.a()的事务不挂起
    **过程:**无论A.a()是否有事务,都为B.b()创建一个事务C,事务C在事务A中执行

事务管理示例

用AspectJ的AOP管理事务

依赖

返回目录

<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.1.9.RELEASE</version>
</dependency>

配置spring-context.xml

返回目录 在Spring的配置文件中添加如下设置:

<beans xmlns:tx="http://www.springframework.org/schema/tx">
	<!-- 配置事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 数据源:在spring-context-druid.xml的配置中定义,这里引用 -->
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<!-- 配置事务通知 -->
	<tx:advice id="myAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- transactionManager事务管理器中以save开头的方法传播行为都是REQUIRED -->
			<tx:method name="save*" propagation="REQUIRED"/>
			<!-- 设置以select和get开头的方法都不需要事务管理 -->
			<tx:method name="select*" propagation="NEVER" />
			<tx:method name="get*" propagation="NEVER" />
		</tx:attributes>
	</tx:advice>
	
	<!-- 配置顾问和切入点 -->
	<aop:config>
		<!-- 切入点:表达式表示要在哪些方法上加入该切入点, 这里是com.hello.spring.aspectj.service下的所有方法 -->
		<aop:pointcut id="myPointcut" expression="exection(* com.hello.spring.aspectj.service.*.*(..))" />
		<!-- 配置事务顾问:把哪个事务加到哪个切入点 -->
		<aop:advisor advice-ref="myAdvice" pointcut-ref="myPointcut" />
	</aop:config>
</beans>

使用注解管理事务

使用Spring注解管理事务

返回目录 通过@Transactional注解方式, 也可将事务植入到相应方法中, 使用注解的方式, 只需要在spring-context.xml配置文件中加入一个tx标签, 告诉Spring使用注解来完成事务的植入. 该标签只需指定一个属性, 即事务管理器

<beans>
	<!-- 配置事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 数据源:在spring-context-druid.xml的配置中定义,这里引用 -->
		<property name="dataSource" ref="dataSource"/>
	</bean>
	<!-- 开启事务注解驱动 -->
	<tx:annotation-driven transaction-manager="transactionmanager"/>
</beans>

@Transactional 注解简介

返回目录 所有可选属性:

  • propagation : 用于设置事务传播属性. 该属性类型为Propagation枚举, 默认值是Propagation.REQUIRED
  • isolation : 用于设置事务的隔离级别. 该属性类型为Isolation枚举, 默认值是Isolation.DEFAULT
  • readOnly : 用于设置该方法对数据库的操作是否是只读的. 该属性为boolean, 默认值为false
  • timeout : 用于设置本操作与数据库连接的超时时限. 单位为秒, 类型为int, 默认值为-1, 即没有时限
  • rollbackFor : 指定需要回滚的异常类. 类型为Class[], 默认值为空数组. 如果只有一个异常类, 可以不用数组
  • rollbackForClassName : 指定需要回滚的异常类类名. 类型为String[], 默认值为空数组.
  • noRollbackFor : 指定不需要回滚的异常类. 类型为Class[],默认值为空数组
  • noRollbackForClassName : 指定不需要回滚的异常类的类名. 类型为String[], 默认值为空数组

注意

  1. @Transactional如果用在方法上, 则只能用于public方法, 对于其他非public的方法, 即时加了该注解也不会生效, 因为Spring会忽略所有非public方法上的@Transactional注解
  2. 如果该注解用在类上, 就是对类内所有public方法生效

测试事务

返回目录 如果直接运行单元测试的事务测试方法, 会把测试事务的数据直接插入到数据库中, 造成数据库的脏数据
解决 在单元测试类上加注解@Transaction@Rollback, 则测试的数据不会实际插入数据库, 从而不会对实际数据库造成影响