Spring事務失效的各種場景總結及源碼分析
環境:Spring5.3.23
1. 簡介
在Spring框架中,事務管理是保障數據一致性和系統可靠性的重要手段。但在實際開發中,Spring事務失效的問題卻時有發生。本文將總結并分析Spring事務失效的各種場景,幫助你全面了解事務失效的原因和解決方案,讓你不再被事務問題困擾。。讓我們一起揭開Spring事務失效的神秘面紗,迎接更穩健、高效的系統開發之旅!
2. 事務失效場景
2.1 非public方法
@Transactional
protected void save() {
Person person = new Person();
person.setAge(36);
person.setName("張三");
int result = jdbcTemplate.update("insert into t_person (age, name) values (?, ?)", person.getAge(),
person.getName());
System.out.println("save Db Update " + result + " 次");
System.out.println(1 / 0) ;
}
以上方法是protected修飾的,事務將失效,默認Spring支持支public修飾的方法。如何讓Spring支持非public方法呢?可以通過如下方法修改
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
// 設置為false,這樣protected及默認修飾的方法都將支持事務功能
return new AnnotationTransactionAttributeSource(false) ;
}
該要想上面bean生效,你還需要開啟如下功能
GenericApplicationContext context = new GenericApplicationContext();
// 允許Bean覆蓋,后面的BeanDefintion能覆蓋前面的
// 我們定義的transactionAttributeSource bena能夠覆蓋系統默認的
context.setAllowBeanDefinitionOverriding(true) ;
2.2 異常被吞
@Transactional
protected void save() {
try {
// ...
int result = jdbcTemplate.update("insert into t_person (age, name) values (?, ?)", person.getAge(),
person.getName());
System.out.println(1 / 0) ;
} catch (Exception e) {
e.printStackTrace() ;
}
}
上面代碼將異常信息捕獲了后并沒有再進行拋出。Spring 事務的原理就是根據你代碼執行時是否發生了異常來控制事務是否回滾。源碼如下:
Spring事務的核心攔截器TransactionInterceptor
public abstract class TransactionAspectSupport {
protected Object invokeWithinTransaction(...) throws Throwable {
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// 執行實際的業務代碼調用
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 執行事務回滾
completeTransactionAfterThrowing(txInfo, ex);
// 繼續拋出,終止向下執行
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
// 沒有異常則進行事務的提交
commitTransactionAfterReturning(txInfo);
}
}
2.3 回滾異常類設置錯誤
Spring事務回滾策略是只會回滾RuntimeException與Error類型的異常和錯誤。
@Transactional
protected void save() throws Exception {
try {
Person person = new Person();
person.setAge(36);
person.setName("張三");
int result = jdbcTemplate.update("insert into t_person (age, name) values (?, ?)", person.getAge(),
person.getName());
System.out.println("save Db Update " + result + " 次");
System.out.println(1 / 0) ;
} catch (Exception e) {
e.printStackTrace() ;
throw new Exception(e) ;
}
}
這里并沒有設置rollbackFor屬性,所以這里事務不會被回滾。回滾邏輯處理如下:
public abstract class TransactionAspectSupport {
protected Object invokeWithinTransaction() {
try {
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 回滾處理
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
}
protected void completeTransactionAfterThrowing() {
// 檢查異常
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
}
}
}
public abstract class DelegatingTransactionAttribute {
// 實現類是下面的RuleBasedTransactionAttribute
private final TransactionAttribute targetAttribute;
public boolean rollbackOn(Throwable ex) {
return this.targetAttribute.rollbackOn(ex);
}
}
public class RuleBasedTransactionAttribute {
public boolean rollbackOn(Throwable ex) {
RollbackRuleAttribute winner = null;
int deepest = Integer.MAX_VALUE;
// 遍歷處理你配置的rollbackFor屬性配置
if (this.rollbackRules != null) {
for (RollbackRuleAttribute rule : this.rollbackRules) {
int depth = rule.getDepth(ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule;
}
}
}
// 如果上沒有找到異常,則進行默認行為的處理,檢查異常類型
if (winner == null) {
return super.rollbackOn(ex);
}
return !(winner instanceof NoRollbackRuleAttribute);
}
public boolean rollbackOn(Throwable ex) {
// 回滾是運行時及Error類型的異常或錯誤
return (ex instanceof RuntimeException || ex instanceof Error);
}
}
2.4 同一類中方法互相調用
protected void save() {
// ...
this.updatePerson()
}
@Transactional
public void updatePerson() {
// ...
}
上面的事務將會失效,因為在save中通過this調用updatePerson,而這時的this是原始對象,并不是當前容器中生成的那個代理對象,通過如下方式解決:
方式1:
protected void save() {
// 通過AopContext獲取當前代理對象
PersonService proxy = (PersonService)AopContext.currentProxy() ;
proxy.save() ;
}
這種方式,不推薦;這將你的代碼與Spring AOP完全耦合,并使類本身意識到它正在AOP上下文中使用,這與AOP背道而馳。
方式2:
自己注入自己
@Resource
private PersonService personService ;
public void save() {
personService.save() ;
}
2.5 方法被final修飾
@Transactional
protected final void save() {
// ...
}
方法被final修飾,cglib是通過繼承的方式實現代理,final修飾后將不能重寫save方法。程序拋出NPE異常
Exception in thread "main" java.lang.NullPointerException
at com.pack.main.transaction.TransactionNoPublicMethodMain2$PersonService.save(TransactionNoPublicMethodMain2.java:98)
因為無法重寫save方法,首先是沒法對方法進行增強處理,其次只能調用父類的save方法,而父類中的所有屬性(需要注入的)都將是null。
2.6 傳播類型設置錯誤
@Transactional(propagation = Propagation.NOT_SUPPORTED)
protected void save() {
// ...
}
或者是設置為Propagation.NEVER,這都將使得事務失效。部分源碼:
public abstract class TransactionAspectSupport {
protected Object invokeWithinTransaction() {
// 使用getTransaction和commit/rollback調用進行標準事務劃分。
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
}
protected TransactionInfo createTransactionIfNecessary() {
// 調用事務管理器獲取事務對象
status = tm.getTransaction(txAttr);
}
}
public abstract class AbstractPlatformTransactionManager {
public final TransactionStatus getTransaction() {
// 根據配置的事務傳播屬性進行相應的處理
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
} else {
// 創建“空”事務:沒有實際的事務,但可能是同步。
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}
}
2.7 異步線程執行
在一個事務方法中開啟新的線程執行事務方法
@Transactional()
protected void save() {
new Thread(() -> {
Person person = new Person();
person.setAge(36);
person.setName("張三");
int result = jdbcTemplate.update("insert into t_person (age, name) values (?, ?)", person.getAge(),
person.getName());
System.out.println("save Db Update " + result + " 次");
System.out.println(1 / 0) ;
}).start() ;
try {
TimeUnit.SECONDS.sleep(3) ;
} catch (InterruptedException e) {}
}
上面的事務將不會生效,這是因為主線程與子線程使用的不是同一個Connection對象,Spring事務執行會為每一個執行線程綁定一個Connection對象。源碼如下:
public abstract class AbstractPlatformTransactionManager {
// 開始新的事務
private TransactionStatus startTransaction() {
doBegin(transaction, definition);
}
}
public class DataSourceTransactionManager {
protected void doBegin(...) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
// 獲取連接對象
Connection newCon = obtainDataSource().getConnection();
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
// 將連接對象綁定到當前線程上
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
}
}
你新啟動的線程是拿不到主線程中的Connection。
2.8 數據庫不支持
在MySQL建表時指定了錯誤的引擎,比如使用了MyISAM。mysql支持哪些引擎及事務支持情況如下:
支持事務的只有InnoDB。在建表時明確指定引擎。
通過上面的方式制定ENGINE=InnoDB。
2.9 關于@Transactional注解使用錯誤的情況
有些人說使用了錯誤的@javax.transaction.Transactional注解。通過源碼分析
Spring在定義事務的切面時,會使用TransactionAttributeSource來判斷當前的類上或者是方法上是否有@Transactional注解
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
public class AnnotationTransactionAttributeSource {
private static final boolean jta12Present;
private static final boolean ejb3Present;
static {
// 判斷是否存在該注解類
jta12Present = ClassUtils.isPresent("javax.transaction.Transactional", classLoader);
}
public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
this.publicMethodsOnly = publicMethodsOnly;
if (jta12Present || ejb3Present) {
this.annotationParsers = new LinkedHashSet<>(4);
this.annotationParsers.add(new SpringTransactionAnnotationParser());
if (jta12Present) {
// 如果存在會加入專門解析@javax.transaction.Transactional注解的解析器類
this.annotationParsers.add(new JtaTransactionAnnotationParser());
}
if (ejb3Present) {
this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
}
}
else {
this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
}
}
}
所以如果你類路徑下只要存在,那么你的事務還是可以生效的。
總結:在本文中,深入探討了Spring事務失效的各種情況。通過了解這些情況,我們可以更好地理解事務管理在Spring框架中的重要性,以及如何避免和解決事務失效的問題。
完畢!!!