Java传家宝:微信公众号(Java传家宝)、Java传家宝-B站、Java传家宝-知乎、Java传家宝-CSND
Spring 事务
首先,确认一点,Spring中的事务都是依赖于数据库实现的。Spring 并不直接管理事务,而是提供了多种事务管理器 。PlatformTransactionManager就是Spring事务的管理接口,另外TransactionDefinition 定义了事务的隔离级别、传播行为、超时、回滚规则等等,TransactionStatus定义事务运行状态。先看一下实现:
- PlatformTransactionManager
public interface PlatformTransactionManager extends TransactionManager {
// 获取事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
// 提交事务
void commit(TransactionStatus var1) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
- TransactionDefinition
public interface TransactionDefinition {
// 传播级别
int PROPAGATION_REQUIRED = 0; // 默认
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
// 隔离级别
int ISOLATION_DEFAULT = -1; // 默认
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
// 超时时间
int TIMEOUT_DEFAULT = -1;
//....
}
- TransactionStatus
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
// TransactionExecution
boolean isNewTransaction(); // 是否是新事物
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted();
//....
boolean hasSavepoint(); // 是否有恢复点
}
编程式事务
这个大概了解一下,实际不怎么使用。可以通过transactionTemplate或者transactionManager手动管理事务。大概就是新建事务,然后通过try…catch实现提交和回滚逻辑。示例如下:
- transactionTemplate示例
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
// .... 业务代码
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}
}
});
}
- transactionManager示例
@Autowired
private PlatformTransactionManager transactionManager;
public void testTransaction() {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// .... 业务代码
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}
声明式事务
实际中,我们常用的是这个,通过注解@Transactional,先看一下实现:
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
String[] label() default {};
Propagation propagation() default Propagation.REQUIRED; // 传播行为 默认REQUIRED
Isolation isolation() default Isolation.DEFAULT; // 隔离级别 默认DEFAULT
int timeout() default -1; // 超时时间默认-1,即没有
String timeoutString() default "";
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
Propagation
事务传播级别,可分为以下几种,默认为Propagation.REQUIRED:
- Propagation.REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。
- Propagation.REQUIRES_NEW:当前存在事务,挂起。新建一个事务。
- Propagation.NESTED:当前存在事务,则嵌套一个新得小事务,小事务回滚不影响外部事务,而外部事务回滚会导致小事务回滚。
- Propagation.MANDATORY:支持当前事务,当前不存在事务抛出异常
- Propagation.SUPPORTS:支持当前事务,当前不存在事务不做任何操作
- Propagation.NOT_SUPPORTED:当前存在事务,挂起。反之不做任何操作
- Propagation.NEVER:当前存在事务,抛出异常。反之不做任何操作
支持当前事务:是指当前存在事务得话,就加入该事务,随着事务提交或者回滚
反之不做任何操作:是指不创建新得事务,以非事务状态运行
Isolation
事务隔离级别,可分为以下几种,默认为DEFAULT:
- Isolation.DEFAULT:采用数据库默认得隔离级别,比如Innodb引擎得Mysql就使用的可重复读隔离级别Isolation.REPEATABLE_READ
- Isolation.READ_UNCOMMITTED:读未提交,表示当前事务可以读取其他事务还没有提交的数据,无法避免并发事务问题
- Isolation.READ_COMMITTED:读已提交,表示当前事务可以读取已经提交过的数据,可以避免脏读问题
- Isolation.REPEATABLE_READ:可重复读,表示当前事务重复读取数据,读取到的数据不变,可以避免脏读和不可重复读问题
- Isolation.SERIALIZABLE:可串行化,表示当前事务按照串行执行,即同一时间只执行一条事务,可避免所以并发事务问题
并发事务问题:为便于理解,假定事务A和事务B正在并发执行,然后假定数据id=2
- 脏读:A读取了B修改了但还未提交的id=3,但是此时B回滚了,id仍然=2,A读取到的id=3就相当于是错误的,造成脏读
- 不可重复读:A读取了id=2,此时B修改了id=3并提交,A此刻再次读取id=3,两次读取不一致,造成不可重复读
- 幻读:A读取某个字段不存在,此时B插入了该字段并提交,A再次读取该字段,发现存在了,那么此时就造成幻读
其他属性
- timeout:表示超时时间,默认为-1即没有超时时间或者取决后端数据库的设置。如果设置了那么当事务超过该时间还没有完成,那么自动回滚。
- readOnly:表示只读,默认为false。如果设置为true,那么该事务只读不可写,可用于只做查询的事务。
- rollbackFor:回滚策略,默认只回滚RuntimeException和Error。也可以设置回滚自定义异常,传入class对象数组即可,还有对应的可以设置类名数组rollbackForClassName属性
- noRollbackFor:不回滚的异常,同理还有noRollbackForClassName属性
事务注解使用
@Transactional注解可作用于public修饰的类、方法、接口中,具体效果如下:
- 作用于类:当前类的所以public方法都会赋予相同的事务
- 作用于方法:当类配置了注解,方法也配置了注解,方法的事务会覆盖类的事务配置信息
- 作用于接口:不推荐,在接口使用事务注解并且配置了 AOP 使用 CGLib 动态代理将会导致其失效。
事务失效
实际应用中可能会出现添加了@Transactional注解,但数据没有回滚的问题,即事务失效,看下面的总结。
非public方法
由于Spring事务是通过Spring AOP产生代理对象实现的,在 Spring AOP 代理时,TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 Intercept 方法或 JDKDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransationAttributeSource 的 computeTransactionAttribute 方法,获取 @Transactional 注解的事务配置信息。
1 protected TransactionAttribute computeTransactionAttribute(Method method,
2 Class<?> targetClass) {
3 // Don't allow no-public methods as required.
4 if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
5 return null;
6 }
此方法会检查目标方法的修饰符是否为 public,非 public 作用域则不会获取 @transactional 的属性配置信息。其中 protected、private 修饰的方法上使用 @Transactional 注解,事务会失效但不会有任何报错。
属性配置错误
- Propagation配置为SUPPORTS、NOT_SUPPORTS和NEVER时,自身不会产生事务,SUPPORTS依赖当前事务,NOT_SUPPORTS会挂起当前事务,NEVER存在当前事务抛出异常。
- rollbackFor回滚策略可以配置不回滚的异常,配置错误导致应该回滚的异常没有回滚,事务失效
同类方法调用,事务不生效
在同一个类中,该类A方法调用了该类的某个事务方法B,那么在调用A方法时,就会间接导致B方法的事务失效
解决办法:
1、通过IOC容器拿到对应的代理对象即可。可以使用AopContext().currentProxy拿到;或者直接将本类对象直接注入即可。
2、将事务方法拿到该类之外
多线程任务
在事务方法内,如果采用了多线程,多线程内的方法不被事务控制。
事务方法内异常被捕获
在事务方法内,如果出现需要回滚的异常在方法内被try/catch捕获处理了,那么事务不会回滚。
事务方法未被 Spring 管理
Spring的事务是通过AOP实现的,而Spring AOP是基于Spring的IOC容器的,只会对IOC内部的Bean生效。因此如果事务方法对应的类不在IOC容器中,即不被Spring管理,那么事务失效。
未配置事务管理器
开启事务不仅需要添加@Transactional注解,还需要为IOC容器添加事务管理器,如下:
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
没有添加,则事务失效。或者在主类添加**@EnableTransactionManagement**实现一样的效果。