Spring事务管理
1.Spring事务管理概述
事务特性
原子性:一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性
一致性:事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。
隔离性:事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。
持久性:一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。–即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态
spring-tx包三个核心接口
PlatformTransactionManager平台事务管理器
TransactionStatus getTransaction(TransactionDefinition definition);//用于获取事务以及状态信息
void commit(TransactionStatus status);//用于提交事务
void rollback(TransactionStatus status);//用于回滚事务
常用实现类: 1.org.springframework.jdbc.datasource.DataSourceTransactionManager用于配置JDBC数据源的事务管理器
2.org.springframework.orm.hibernate4.HibernateTransactionManager用于配置Hibernate的事务管理器
3.org.springframework.transaction.jta.JtaTransactionManager用于配置全局事务管理器
TransactionDefinition事务定义对象
String getName( ); 获取事务对象名称
int getIsolationLevel( ); 获取事务的隔离级别
int getPropagationBehavior( ); 获取事务的传播行为
int getTimeout( ); 获取事务的超时时间
boolean isReadOnly( ); 获取事务是否只读
TransactionStatus事务的状态
传播行为(propagation behavior):当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。Spring定义了七种传播行为
- REQUIRED:如果当前存在事务,则加入该事务,如果没有事务,则创建一个新的事务。这是默认的传播行为。
- SUPPORTS:如果当前存在事务,则加入该事务,如果没有事务,则以非事务方式执行。
- MANDATORY:如果当前存在事务,则加入该事务,如果没有事务,则抛出异常。
- REQUIRES_NEW:无论当前是否存在事务,都创建一个新的事务。如果当前存在事务,则将其挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将其挂起。
- NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则在嵌套事务中执行。如果没有事务,则创建一个新的事务。嵌套事务可以独立提交或回滚,但是最终的提交或回滚会影响到外部事务的状态。
隔离级别(isolation level):定义了一个事务可能受其他并发事务的影响程度。多个事务并发运行,经常会操作相同的数据来完成各自的任务,可能会出现脏读,不可重复读和幻读的问题。隔离级别有四种
-
DEFAULT(默认):使用数据库默认的事务隔离级别。在大多数情况下,这等同于使用READ_COMMITTED级别。
-
READ_UNCOMMITTED(读取未提交数据):最低的隔离级别,允许一个事务读取另一个事务未提交的数据。这种隔离级别可能会导致脏读、不可重复读和幻读问题。
-
READ_COMMITTED(读取已提交数据):保证一个事务只能读取到已经提交的数据,避免脏读问题。但是在并发情况下,可能会导致不可重复读和幻读问题。
-
REPEATABLE_READ(可重复读取):保证一个事务在多次读取同一数据时,能够得到一致的结果。在该隔离级别下,其他事务不能修改当前事务已经读取的数据,避免了不可重复读问题。但是仍然可能出现幻读问题。
-
SERIALIZABLE(可串行化):最高的隔离级别,确保并发事务之间不会发生任何并发问题。它通过强制事务串行执行来避免脏读、不可重复读和幻读问题。但是这种隔离级别的性能较低,一般情况下不建议使用。
是否只读(isReadOnly):如果一个方法内都是对数据库的select操作,那么可以设置方法事务为只读,数据库也会对该事务进行特定的优化。只读事务内不能有insert、update、delete的操作
事务超时(timeout):事务可能设计对后端数据库的锁定,所以长时间的事务运行会不必要的占用数据库资源,设置事务超时时间可以及时释放资源
DefaultTransactionDefinition是Spring提供的TransactionDefinition接口的默认实现类
public class DefaultTransactionDefinition implements TransactionDefinition, Serializable {
private int propagationBehavior = PROPAGATION_REQUIRED;
private int isolationLevel = ISOLATION_DEFAULT;
private int timeout = TIMEOUT_DEFAULT;
private boolean readOnly = false;
//略
}
2.编程式事务管理
步骤1:
添加事务管理器组件
<!--定义事务管理器 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
步骤2:
在业务逻辑类中使用事务管理器
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private PlatformTransactionManager txManager;//通过依赖注入使用事务管理器
//转账操作方法
}
步骤3:
在方法中编程实现事务管理
public String moneyTransfer(String accountA, String accountB, double money) {
//定义事务规则(隔离级别、传播行为)
DefaultTransactionDefinition definition=new DefaultTransactionDefinition();
//开启事务管理,并返回事务状态
TransactionStatus status = txManager.getTransaction(definition);
try {
//转账业务逻辑(省略)
//提交事务
txManager.commit(status);
return "转账成功";
} catch (Exception e) {
//如果回滚事务
txManager.rollback(status);
return "转账失败";
}
}
编程式事务管理缺点
编程式事务管理必须要在业务逻辑中包含额外的事务管理代码。和业务逻辑代码产生了耦合,产生了代码冗余,不方便代码的维护和扩展。
声明式事务管理最大的优点在于开发者无需通过编程的方式来管理事务,只需在配置文件中进行相关的事务规则声明,就可以将事务应用到业务逻辑中。这使得开发人员可以更加专注于核心业务逻辑代码的编写,在一定程度上减少了工作量,提高了开发效率,所以在实际开发中,通常都推荐使用声明式事务管理。
3. 声明式事务管理
声明式事务管理的原理
通过AOP技术实现的事务管理,主要思想是将事务作为一个“切面”代码单独编写,然后通过AOP技术将事务管理的“切面”植入到业务目标类中
3.1基于XML方式的声明式事务
基于XML方式的声明式事务是在配置文件中通过tx:advice元素配置事务规则来实现的,然后通过使用aop:config编写的AOP配置,让Spring自动对目标生成代理
配置tx:advice元素的重点是配置tx:method子元素,上图中使用灰色标注的几个属性是tx:method元素中的常用属性。
因为要用到tx标签配置事务,需要修改applicationContext.xml文件头
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
步骤1:
添加事务管理器组件
<!--定义事务管理器 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
步骤2:
使用tx:advice标签配置事务规则
<!-- 配置事务规则 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 定义哪些方法需要进行事务处理,*表示任意字符 -->
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="moneyTransfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
步骤3:
使用aop:config配置事务切面
<!-- 定义切面 -->
<aop:config>
<!-- 定义切点范围 -->
<aop:pointcut expression="execution(* service.*.*(..))" id="pointcut"/>
<!-- 应用事务规则 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
3.2基于注解方式的声明式事务
步骤1:
在Spring容器中注册事务注解驱动:
<tx:annotation-driven transaction-manager=“txManager"/>
步骤2:
在需要事务管理的类或方法上使用@Transactional注解
如果将注解添加在Bean类上,则表示事务的设置对整个Bean类的所有方法都起作用;
如果将注解添加在Bean类中的某个方法上,则表示事务的设置只对该方法有效。
一般选择添加在方法上面
使用@Transactional注解时,可以通过参数配置事务详情:
3.3声明式事务失效场景
场景一
**原因:**不能自己捕获 需要抛出异常
**解决方法:**抛出异常
场景二
原因:声明式事务只对runtimeException及其子类异常回滚
**解决方法:**添加@Transactional(rollbackFor = Exception.class) rollbackFor指定除了runtimeException以外需要回滚的异常
场景三
原因:代理原因,类的内部调用
解决方法:1.对这个方法添加注解@Transactional
2.容器中注入一个本身类的bean,在上面的return 中修改为
**解决方法:**添加@Transactional(rollbackFor = Exception.class) rollbackFor指定除了runtimeException以外需要回滚的异常
场景三
[外链图片转存中…(img-4XHQKVW1-1700399671234)]
原因:代理原因,类的内部调用
解决方法:1.对这个方法添加注解@Transactional
[外链图片转存中…(img-Qoyel6Ej-1700399671235)]
2.容器中注入一个本身类的bean,在上面的return 中修改为
return bean.moneyTransfer(accountA,accountB,1000);