事务是逻辑上的一组操作,要么都执行,要么都不执行
1. 什么是事务
事务是逻辑上的一组操作,要么都执行,要么都不执行。
假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是:
- 小明的余额减少 1000 元
- 小红的余额增加 1000 元。
public void accountMoney() {
//小红账户多1000
accountDao.addMoney(1000, xiaohong);
//小明账户少1000
accountDao.reduceMoney(1000, xiaoming);
}
如果这两个操作之间出现系统崩溃或者网络故障,导致小红余额增加而小明余额没有减少,这样会出现问题。事务就是保证这两个关键操作要么都成功,要么都要失败。
事务能否生效数据库引擎是否支持事务是关键:
数据库 | 引擎 | 支持事务 |
---|---|---|
Oracle | - | <font color=#3CB371>是</font> |
MySQL | InnoDB | <font color=#3CB371>是</font> |
MySQL | MyISAM | <font color=#FF3333>否</font> |
2. 事务的特性
-
原子性(Atomicity)
一个事务中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
-
一致性(Consistency)
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
-
隔离性(Isolation)
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read Uncommitted)、提交读(Read committed)、可重复读(Repeatable read)和串行化(Serializable)。
-
持久性(Durability)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
3. Spring 对事务的支持
3.1 Spring 支持的两种事务管理方式
1) 编程式事务管理
通过 TransactionTemplate
或者TransactionManager
手动管理事务,实际应用较少使用,但是对理解 Spring 事务管理原理有帮助。
使用TransactionTemplate
进行编程式事务管理的示例代码:
@Autowired
private TransactionTemplate template;
public void testTransaction() {
template.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
//业务代码...
} catch (Exception e){
//回滚
status.setRollbackOnly();
}
}
});
}
使用 TransactionManager
进行编程式事务管理的示例代码如下:
@Autowired
private PlatformTransactionManager manager;
public void testTransaction() {
TransactionStatus status = manager.getTransaction(new DefaultTransactionDefinition());
try {
//业务代码...
manager.commit(status);
} catch (Exception e) {
//回滚
manager.rollback(status);
}
}
2) 声明式事务管理
代码侵入性最小,原理是通过 AOP 实现,核心源码位于TransactionInterceptor
中。
使用 @Transactional
注解进行事务管理的示例代码如下:
@Transactional(rollbackFor = Exception.class)
public void testTransaction {
//业务代码...
}
3.2 Spring 事务管理接口介绍
Spring 框架中,事务管理相关最重要的 3 个接口如下:
PlatformTransactionManager
: 平台事务管理器,Spring 事务策略的核心。TransactionDefinition
: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。TransactionStatus
: 事务运行状态。
PlatformTransactionManager
接口可以被看作是事务上层的管理者,而 TransactionDefinition
和 TransactionStatus
这两个接口可以看作是事务的描述。
PlatformTransactionManager
会根据 TransactionDefinition
的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus
接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。
3.2.1 PlatformTransactionManager:事务管理接口
Spring 不直接管理事务,而是提供多种事务管理器,事务管理器的接口是: PlatformTransactionManager
。
通过这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager
)、Hibernate(HibernateTransactionManager
)、JPA(JpaTransactionManager
)等都提供了对应的事务管理器,具体的实现由各个平台处理。
PlatformTransactionManager
接口的具体实现如下:
PlatformTransactionManager
接口中定义了三个方法:
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager {
//获得事务
TransactionStatus getTransaction(@Nullable TransactionDefinition definition);
//提交事务
void commit(TransactionStatus status);
//回滚事务
void rollback(TransactionStatus status);
}
3.2.2 TransactionDefinition:事务属性
事务属性可以理解为事务的一些基本配置,描述了事务策略如何应用到方法上,事务属性包含了 5 个方面:
TransactionDefinition
接口中定义了 5 个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等。
package org.springframework.transaction;
import org.springframework.lang.Nullable;
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;
// 返回事务的传播行为,默认值为 REQUIRED。
int getPropagationBehavior();
//返回事务的隔离级别,默认值是 DEFAULT
int getIsolationLevel();
// 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
int getTimeout();
// 返回是否为只读事务,默认值为 false
boolean isReadOnly();
@Nullable
String getName();
}
3.2.3 TransactionStatus:事务状态
TransactionStatus
接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息。
PlatformTransactionManager.getTransaction(…)
方法返回一个 TransactionStatus
对象。
TransactionStatus 接口接口内容如下:
public interface TransactionStatus {
//是否是新的事务
boolean isNewTransaction();
//是否有恢复点
boolean hasSavepoint();
//设置为只回滚
void setRollbackOnly();
//是否为只回滚
boolean isRollbackOnly();
//是否已完成
boolean isCompleted;
}
3.3 事务属性详解
3.3.1 事务传播行为
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播,方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
在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;
......
}
Spring 相应地定义了一个枚举类:Propagation
@Getter
@RequiredArgsConstructor
public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
}
事务传播行为需要注意的重点在于:
-
<font color=#FF3333>外层事务异常时是否影响内层事务</font>
@Service @RequiredArgsConstructor public class A { private final B b; private final Dao dao; //外层事务 @Transactional(propagation = Propagation.REQUIRED) public void aMethod { dao.insert("张三"); b.bMethod(); //模拟外层事务异常 throw new RuntimeException(); } } @Service @RequiredArgsConstructor public class B { private final Dao dao; //内层事务 @Transactional(propagation = Propagation.XXX) public void bMethod { dao.insert("李四"); } }
-
<font color=#FF3333>内层事务异常时是否影响外层事务</font>
@Service @RequiredArgsConstructor public class A { private final B b; private final ADao dao; //外层事务 @Transactional(propagation = Propagation.REQUIRED) public void aMethod { dao.insert("张三"); b.bMethod(); } } @Service @RequiredArgsConstructor public class B { private final BDao dao; //内层事务 @Transactional(propagation = Propagation.XXX) public void bMethod { dao.insert("李四"); //模拟内层事务异常 throw new RuntimeException(); } }
-
<font color=#FF3333>内层事务异常时被外层 try-catch 后是否影响外层事务</font>
@Service @RequiredArgsConstructor public class A { private final B b; private final ADao dao; //外层事务 @Transactional(propagation = Propagation.REQUIRED) public void aMethod { dao.insert("张三"); //外层try-catch内层事务异常 try { b.bMethod(); } catch (Exception ignored) { } } } @Service @RequiredArgsConstructor public class B { private final BDao dao; //内层事务 @Transactional(propagation = Propagation.XXX) public void bMethod { dao.insert("李四"); //模拟内层事务异常 throw new RuntimeException(); } }
正确的事务传播行为可能的值如下 :
1.TransactionDefinition.PROPAGATION_REQUIRED
默认使用的事务传播行为:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
-
如果外部方法没有开启事务的话,
Propagation.REQUIRED
修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。 -
如果外部方法开启事务并且被
Propagation.REQUIRED
的话,所有Propagation.REQUIRED
修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。
2.TransactionDefinition.PROPAGATION_REQUIRES_NEW
创建一个新的事务,如果当前存在事务,则把当前事务挂起。Propagation.REQUIRES_NEW
修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
3.TransactionDefinition.PROPAGATION_NESTED
:
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED
。
- 在外部方法未开启事务的情况下
Propagation.NESTED
和Propagation.REQUIRED
作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。 - 如果外部方法开启事务的话,
Propagation.NESTED
修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务。
4.TransactionDefinition.PROPAGATION_MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
内层传播行为 | 外层异常 | 内层异常 | 外层 try-catch 内层异常 |
---|---|---|---|
REQUIRED | 张三、李四均未入库 | 张三、李四均未入库 | 张三、李四均未入库 |
REQUIRES_NEW | 张三未入库,李四入库 | 张三、李四均未入库 | 张三入库,李四未入库 |
NESTED | 张三、李四均未入库 | 张三、李四均未入库 | 张三入库,李四未入库 |
若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚,使用的很少。
-
TransactionDefinition.PROPAGATION_SUPPORTS
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
-
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
以非事务方式运行,如果当前存在事务,则把当前事务挂起。
-
TransactionDefinition.PROPAGATION_NEVER
以非事务方式运行,如果当前存在事务,则抛出异常。
3.3.2 事务隔离级别
TransactionDefinition
接口中定义了五个表示隔离级别的常量:
public interface TransactionDefinition {
......
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
......
}
为了方便使用,Spring 也相应地定义了一个枚举类:Isolation
@Getter
@RequiredArgsConstructor
public enum Isolation {
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
private final int value;
}
隔离级别介绍如下:
-
TransactionDefinition.ISOLATION_DEFAULT
Oracle 默认采用的
READ_COMMITTED
MySQL 默认采用的
REPEATABLE_READ
-
TransactionDefinition.ISOLATION_READ_UNCOMMITTED
最低的隔离级别,允许读取尚未提交的数据变更,可能会导致<font color=#FF3333>脏读</font>、<font color=#FF3333>幻读</font>或<font color=#FF3333>不可重复读</font>。
-
TransactionDefinition.ISOLATION_READ_COMMITTED
允许读取并发事务已经提交的数据,可以防止脏读,可能会导致<font color=#FF3333>幻读</font>或<font color=#FF3333>不可重复读</font>。
-
TransactionDefinition.ISOLATION_REPEATABLE_READ
对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以防止脏读和不可重复读,可能会导致<font color=#FF3333>幻读</font>。
-
TransactionDefinition.ISOLATION_SERIALIZABLE
完全服从 ACID 的最高隔离级别,所有的事务依次逐个执行,事务之间就完全不可能产生干扰,该级别可以防止脏读、不可重复读以及幻读,但会严重影响性能,可能应用到的场景为分布式事务。
脏读、幻度、不可重复度的介绍如下:
-
脏读(读取未提交的数据) 在数据库访问中,事务 A 对一个值做修改,事务 B 读取这个值,但是由于某种原因事务 A 回滚撤销了对这个值得修改,这就导致事务 B 读取到的值是无效数据。
-
不可重复读(前后数据多次读取,结果集内容不一致) 事务 A 按照查询条件得到了一个结果集,此时事务 B 对事务 A 查询的结果集数据做了修改操作,之后事务 A 为了数据校验继续按照之前的查询条件得到的结果集与前一次查询不同,导致不可重复读取原始数据。
-
幻读(前后数据多次读取,结果集数量不一致) 事务 A 按照查询条件得到了一个结果集,这时事务 B 对事务 A 查询的结果集数据做新增操作,之后事务 A 继续按照之前的查询条件得到的结果集平白无故多了几条数据,好像出现了幻觉一样。
3.3.3 事务超时属性
事务超时是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。
3.3.4 事务只读属性
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {
......
// 返回是否为只读事务,默认值为 false
boolean isReadOnly();
}
对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。
-
如果一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL 执行期间的读一致性;
-
如果一次执行多条查询语句,例如统计查询,报表查询等,这种场景下多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时应该启用事务支持。
3.3.5 事务回滚规则
默认情况下,事务只有遇到RuntimeException
、Error
或其子类时才会回滚,其余Exception
不会回滚。
常用的写法如下,回滚所有Exception
:
@Transactional(rollbackFor= MyException.class)
可能有人会问,只声明了 Exception,那 Error 不管了吗?其实 Spring 判断是否回滚有两段逻辑:
- 首先判断抛出的异常是不是声明的回滚异常,是的话进行回滚
- 如果不满足上面的判断,再进行默认判断,是不是 RuntimeException 或 Error,是的话进行回滚
3.4 @Transactional 注解使用详解
1) @Transactional
的作用范围
-
方法 :推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。
源码位于
AbstractFallbackTransactionAttributeSource
类中,allowPublicMethodsOnly
方法会被其子类AnnotationTransactionAttributeSource
重写为返回true
。protected TransactionAttribute computeTransactionAttribute( Method method, @Nullable Class<?> targetClass) { // Don't allow no-public methods as required. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } ...... }
个人猜想:可能是为了保证 JDK 动态代理和 CGLib 动态代理在实现上的一致性。
-
类 :如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。
2) @Transactional
的常用配置参数
@Transactional
注解源码如下,里面包含了基本事务属性的配置:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
@Transactional
的常用配置参数总结:
属性名 | 说明 |
---|---|
propagation | 事务的传播行为,默认值为 REQUIRED,可选的值在上面介绍过 |
isolation | 事务的隔离级别,默认值采用 DEFAULT,可选的值在上面介绍过 |
timeout | 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
readOnly | 指定事务是否为只读事务,默认值为 false。 |
rollbackFor | 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。 |
3) @Transactional
事务注解原理
@Transactional
的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。
创建代理的代码如下:
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
.......
}
Spring 容器就会在启动的时候为其创建一个代理类,在调用被@Transactional
注解的 public 方法的时候,实际调用的是TransactionInterceptor
类中的 invoke()
方法。
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
......
}
TransactionInterceptor
类中的 invoke()
方法内部实际调用的是 TransactionAspectSupport
类的 invokeWithinTransaction()
方法。
这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。
核心代码如下:
public class TransactionAspectSupport {
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
InvocationCallback invocation) throws Throwable {
......
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, identification);
Object retVal;
try {
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
......
}
}
3.5 @Transactional 注解事务异常场景
-
访问修饰符问题
@Transactional(rollbackFor = Exception.class) private void insert(){ dao.insert("张三"); dao.insert("李四"); }
Spring 要求被代理方法必须是被
public
修饰,具体逻辑代码位于 3.4 章节。 -
方法或类用 final 修饰
@Transactional(rollbackFor = Exception.class) public final void insert(){ dao.insert("张三"); dao.insert("李四"); }
Spring 事务底层使用了 AOP 通过 JDK 或 CGLib 动态代理生成了代理类,在代理类通过重写标记方法实现事务功能。如果方法或类用
final
修饰,那么在它的代理类中就无法重写该方法,从而使事务失效。注意:如果某个方法是 static 的,同样无法通过动态代理变成事务方法。
-
方法内部调用
@Service public class TestService { public void add() { dao.insert("张三"); //调用同一类的内部方法 update(); } @Transactional(rollbackFor = Exception.class) public void update() { dao.update("李四", 1); } }
在同一个类中的方法直接内部调用,会导致事务失效,
update
方法拥有事务的能力是因为 Spring AOP 生成代理了对象(TestService$$Enhancer
),但是这种方法直接调用了 <font color=#FF3333>this</font> (TestService)对象的方法,所以update
方法不会生成事务。如果有些场景确实想在同一个类的某个方法中调用它自己的另外一个 AOP 方法,可以考虑:
-
新增一个 Service 方法,将逻辑抽离,使用时注入新 Service 使用<font color=#3CB371>(推荐)</font>
-
在该 Service 类中注入自己或从容器中获取该 Service 的 Bean,如
applicationContext.getBean()
,由于 Spring 属性注入的三级缓存存在,不会出现循环依赖。<font color=#FF8C00>(可选)</font> -
在该 Service 类中使用
AopContext.currentProxy()
获取代理对象<font color=#FF3333>(不推荐)</font>public void add() { dao.insert("张三"); ((TestService) AopContext.currentProxy()).update(); }
使用 AopContext 需要设置 expose-proxy 属性为true,这样做会降低性能,在类注释中作者也有写:
「此类中的功能可能由需要在调用时访问资源的目标对象使用。 但是,如果有合理的替代方案,则不应使用此方法。」
-
-
未被 Spring 管理
//@Service public class TestService { @Transactional(rollbackFor = Exception.class) public void update() { dao.update("李四", 1); } }
Spring 事务的前提是对象要被 Spring 容器管理,否则无法生成代理类。
-
多线程调用
@Service @RequiredArgsConstructor public class TestService { private final TestService2 testService2; @Transactional(rollbackFor = Exception.class) public void insert() { dao.insert("张三"); new Thread(() -> testService2.update()).start(); } } @Service public class TestService2 { @Transactional(rollbackFor = Exception.class) public void update() { dao.update("李四", 1); } }
这样场景会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务,回滚时会出现问题。
Spring 的事务是通过数据库连接来实现的,当前线程中保存了一个 Map,key 是数据源,value 是数据库连接,源码位于
TransactionSynchronizationManager
类中。public abstract class TransactionSynchronizationManager { private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources"); ...... }
同一个事务其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚,如果在不同的线程,拿到的数据库连接是不一样的,也就是是不同的事务,到导致事务回滚失效。
-
数据库不支持事务
如:MySQL 的 MyISAM 引擎不支持事务。
-
错误的传播行为
正确的传播行为位于 3.3.1 章节。
-
异常被吞
@Transactional(rollbackFor = Exception.class) public void aMethod { dao.insert("张三"); try { b.bMethod(); } catch (Exception ignored) { } }
这种情况下 Spring 事务不会回滚,因为代码捕获了异常,又没有手动抛出,生成的代理无法触发回滚。
如果想要 Spring 事务能够正常回滚,必须抛出异常,如果没有抛异常,则 Spring 认为程序是”正常”的。
-
未指定回滚异常
@Transactional public void insert() { dao.insert("张三"); //模拟其它异常 throw new Exception(); }
Spring事务默认情况下只会回滚
RuntimeException
(运行时异常)和Error
(错误),对于普通的Exception(非运行时异常),不会回滚。回滚规则位于 3.3.5 章节。
-
自定义了回滚异常
@Transactional(rollbackFor = MyException.class) public void insert() { dao.insert("张三"); //模拟其它异常 throw new Exception(); }
同理。
3.6 常见问题
-
@Transactional
注解写在 Resource 层还是 Service 层?优先考虑 Service 层,可以覆盖到 Resource 调用和同服务其它 Service 调用。
-
大事务问题
@Transactional(rollbackFor = Exception.class) public void doBusiness() { query1(); query2(); remoteClient.query(); insert(); update(); }
@Transactional
的粒度为方法级,粒度太粗,会导致整个业务方法都在同一个事务中,不好精确控制事务范围,容易出现大事务问题。可以考虑的优化方案:
- 使用编程式事务在方法内精确控制需要保证事务的代码段
- 将 SELECT 语句放到方法外,需注意业务逻辑是否必须满足读一致性;
- 事务中避免使用远程调用;
- 避免事务中一次性处理过多数据,可能占用大量数据库回滚段表空间。