记一次spring事务问题排查

spring事务@Transactional失效问题排查。

问题现象

  • 简述下问题
    • 实际业务编码中遇到的问题,代码敏感,替换为类似结构的栗子
    • 通过工厂类AnimalFactory获取动物实例,调用Animal.action()方法,该方法中存在对数据库的CURD
    • 针对方法action()打上Transactional注解,然而在抛出异常时却没有事务回滚作用
1
2
3
public abstract class Animal implements InitializingBean{
public abstract void action();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
public class Dog extends Animal{
@Autowired
AnimalFactory animalFactory;

@Override
@Transactional(rollbackFor = Exception.class)
public String action(){
// insert
throw new RuntimeExection("");
// update
return "wangwang";
}
@Override
public void afterPropertiesSet() {
animalFactory.register(Animal.TypeEnum.Dog.getValue(), this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class AnimalFactory{
private Map<String, Animal> cachedAnimals = new ConcurrentHashMap<>();

public void register(String animalType, Animal animal){
cachedAnimals.put(animalType, animal);
}

public Animal loadAnimal(String animalType) {
if (!cachedAnimals.containsKey(animalType)) {
throw new IllegalArgumentException("未找到类型为【" + animalType + "】的动物");
}
return cachedAnimals.get(msgType.toLowerCase());
}
}
1
2
3
4
5
6
7
@Component
public class Zoo {
public Animal showDog(){
Animal animal = AnimalFactory.
animal.action();
}
}

问题环境

  • 事务配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.*;

import javax.sql.DataSource;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@Aspect
@Configuration
public class TxConfig {

/**
* 切面 Service
*/
private static final String AOP_POINTCUT_EXPRESSION = "execution(* cn.tongdun.bond..service..*.*(..))";

@Autowired
private PlatformTransactionManager transactionManager;

@Bean
public PlatformTransactionManager txManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}

/**
* 事务拦截器
*/
@Bean
public TransactionInterceptor txAdvice() {
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();

/*只读事务,不做更新操作*/
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setReadOnly(true);
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);

/*当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务*/
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

Map<String, TransactionAttribute> txMap = new HashMap<>(16);

/*配置事务方法的前缀*/
txMap.put("add*", requiredTx);
txMap.put("save*", requiredTx);
txMap.put("insert*", requiredTx);
txMap.put("create*", requiredTx);
txMap.put("batch*", requiredTx);
txMap.put("update*", requiredTx);
txMap.put("modify*", requiredTx);
txMap.put("delete*", requiredTx);
/*配置只读事务方法的前缀*/
txMap.put("get*", readOnlyTx);
txMap.put("query*", readOnlyTx);
txMap.put("count*", readOnlyTx);
txMap.put("select*", readOnlyTx);
source.setNameMap(txMap);

return new TransactionInterceptor(transactionManager, source);
}

/**
* 注册事务
*/
@Bean
public Advisor txAdviceAdvisor(TransactionInterceptor txAdvice) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice);
}

}
  • action命令未AOP事务的规则,故次配置不会在action()中生效,正因如此才加上了
  • Spring默认propagation属性为Propagation.REQUIRED

排查思路

  • @Transactional失效场景
    1. @Transactional 应用在非 public 修饰的方法上
    2. @Transactional 注解属性 propagation 设置错误
    3. @Transactional 注解属性 rollbackFor 设置错误
    4. 同一个类中方法调用,导致 @Transactional 失效
    5. 异常被 catch“吃了”导致 @Transactional 失效
    6. 数据库引擎不支持事务
  • 然而网上常见的几种@Transactional失效场景均无法匹配当前问题场景
  • 即使把问题函数名aciton改为addaction也仍然没有事务回滚

  • 通过在代码中加入工具类TransactionTestUtils.transactionRequired,直观看出当前函数内是否存在事务

1
TransactionTestUtils.transactionRequired("action");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.lang.reflect.InvocationTargetException;

public class TransactionTestUtils {
private static final boolean transactionDebugging = true;
private static final boolean verboseTransactionDebugging = true;

public static void showTransactionStatus(String message) {
System.out.println(((transactionActive()) ? "[+] " : "[-] ") + message);
}

public static boolean transactionActive() {
try {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
Class tsmClass = contextClassLoader.loadClass("org.springframework.transaction.support.TransactionSynchronizationManager");
Boolean isActive = (Boolean) tsmClass.getMethod("isActualTransactionActive", null).invoke(null, null);

return isActive;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}

throw new IllegalStateException("ServerUtils.transactionActive was unable to complete properly");
}

public static void transactionRequired(String message) {
if (!transactionDebugging) {
return;
}

if (verboseTransactionDebugging) {
showTransactionStatus(message);
}
}
}
  • 通过观察发现,问题函数内的事务确实没有生效
  • 仔细思考,@Transactional事务,依赖SpringAOP实现,那么不如在AOP事务源码处加上断点,看看到底进入什么逻辑导致事物没生效
  • 结果发现,连AOP外层逻辑都没进去
  • 再想想,AOP实际通过动态代理实现,而动态代理实际通过spring容器实现,也就是函数对应的类实例可能未被容器所管理
  • 想到这里,问题点很容器就找到了,AnimalFactory工厂中register()加注册逻辑反转给具体的工厂中的类,而Dog实际通过put的方式将自己存入了cachedHandlers中,脱离了容器的管控
  • 对于上面一点,有必要解释下,spring实现动态代理是通过代理类+被代理类 两个实例的方式实现的,而不是只有一个代理类,故注册进工厂的是原始Dog实例,而不是被代理类

解决方案

  • 既然没有通过spring容器注册实例导致了当前问题,那么咱通过spring容器拿就是了
  • register不注册实例,改为注册beanName
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component("Dog")
public class Dog extends Animal{
@Autowired
AnimalFactory animalFactory;

@Override
@Transactional(rollbackFor = Exception.class)
public String action(){
// insert
throw new RuntimeExection("");
// update
return "wangwang";
}
@Override
public void afterPropertiesSet() {
animalFactory.register(Animal.TypeEnum.Dog.getValue(), "Dog");
}
}
  • 工厂类load时通过容器获取对应的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class AnimalFactory{
private Map<String, String> cachedAnimals = new ConcurrentHashMap<>();

public void register(String animalType, String beanName){
cachedAnimals.put(animalType, beanName);
}

public Animal loadAnimal(String animalType) {
if (!cachedAnimals.containsKey(animalType)) {
throw new IllegalArgumentException("未找到类型为【" + animalType + "】的动物");
}
String beanName = cachedAnimals.get(msgType.toLowerCase());
return (Animal)applicationContext.getBean(beanName);
}
}

总结

  • 问题在复盘时,顺着捋总是会显得easy and stupid,希望能通过不断的总结优化既有的问题思考方法论,以至于以后能少走点弯路
-------------The End-------------
坚持原创技术分享,您的支持将鼓励我继续创作!