文章目录
- Spring事务管理
- Spring事务API
- 事务管理器接口
- 常用的两个实现类
- Spring的回滚方式
- 事务定义接口
- 事务的四种隔离级别
- 几种读问题的区别
- 事务的七种传播行为
- 事务管理示例
- 用AspectJ的AOP管理事务
- 依赖
- 配置spring-context.xml
- 使用注解管理事务
- 使用Spring注解管理事务
- @Transactional 注解简介
- 测试事务
Spring事务管理
事务用于数据库的访问,但是一般情况下,需要将事务提升到业务层,即Service层。这样做是为了能够使用事务的特性来管理具体的业务。
在Spring中通常可以通过以下三种方式来实现对事务的管理:
- 使用Spring的事务代理工厂管理事务(已废弃)
- 使用Spring的事务注解管理事务(目前最常用)
- 使用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[]
, 默认值为空数组
注意
@Transactional
如果用在方法上, 则只能用于public
方法, 对于其他非public
的方法, 即时加了该注解也不会生效, 因为Spring会忽略所有非public
方法上的@Transactional
注解- 如果该注解用在类上, 就是对类内所有
public
方法生效
测试事务
返回目录 如果直接运行单元测试的事务测试方法, 会把测试事务的数据直接插入到数据库中, 造成数据库的脏数据
解决 在单元测试类上加注解@Transaction
和@Rollback
, 则测试的数据不会实际插入数据库, 从而不会对实际数据库造成影响