一、以下情况spring的事务管理会失效
private方法、final方法、static方法、绕过代理对象直接调用添加了事务管理的注解的方法时(new UserService.save())事务管理将无法生效。spring的声明式事务是基于动态代理实现的,代理类需要继承目标类(cglib)或目标类实现了接口(jdk)。由于java继承机制中不能重写private、final、static修饰的方法,接口抽象方法只能是pulic方法,因此所有的private方法、final方法、static方法都不能直接添加spring的事务管理功能。
现在解析一下spring事物管理的基本流程:
如上图所示 TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource 的 computeTransactionAttribute 方法,获取 Transactional 注解的事务配置信息。
// 此方法会检查目标方法的修饰符是否为 public,不是 public 则不会获取 @Transactional 的属性配置信息。
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
如果是以xml的形式配置spring环境,请在配置文件上添加配置<tx:annotation-driven transaction-manager="txManager"/>,
接着在service类或方法上添加@Transactional注解,我就曾经忘记添加,然后排查了一下午事物管理为什么会失效,然后查找资料产出了这篇文章。
二、多线程下使用事物管理产生的问题
@Service
public class UserService{
@Transactional
public User add(User u) {
userDao.insert(u);
return u;
}
@Transactional
public int delete(int id) {
int i = userDao.deleteByPrimaryKey(id);
return i;
}
@Transactional(rollbackFor = Exception.class)
public void test_thread2() throws ExecutionException, InterruptedException {
//同一类中的添加方法
add(new User("5","姓名","密码"));
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
//同一类中的添加方法
add(new User("6","姓名","密码"));
//同一类中的删除方法
delete(6);
//抛异常
throw new Exception("模拟异常操作");
}
};
ExecutorService pool = Executors.newCachedThreadPool();
Future<String> submit = service.submit(callable);
pool.shutdown();
System.out.println(submit.get());
}
}
spring会启用了三个事务,线程外添加、线程内添加、线程内删除,但是执行完线程内添加和线程内删除后线程内抛错了,被主线程catch到了,导致线程外事务被回滚,线程内的单独俩个添加和删除都开启了事务并且事务已经被提交。类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。
三、多线程下如何进行事物管理
将线程内的业务代码抽离出来并使用@Transactional注解修饰
@Service
@Transactional(rollbackFor = Exception.class)
public class TestService {
@Autowired
private UserMapper userMapper;
@Transactional(rollbackFor = Exception.class)
public void add(User user) {
userMapper.insert(user);
}
@Transactional(rollbackFor = Exception.class)
public void delete(int user) {
userMapper.deleteByPrimaryKey(user);
}
@Transactional(rollbackFor = Exception.class)
public void addDeleteOneTransaction(User user,int id) {
add(user);
delete(id);
throw new Exception("模拟事务内异常");
}
}
@Service
@Transactional(rollbackFor = Exception.class)
public class UserService{
@Autowired
private UserMapper userMapper;
@Autowired
private TestService testService;
@Transactional(rollbackFor = Exception.class)
public void add(User user) {
userMapper.insert(user);
}
public void test_thread2() throws ExecutionException, InterruptedException {
//同一类中的添加方法
add(new User("5","姓名","密码"));
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
//抽离后的添加删除方法
testService.addDeleteOneTransaction(new User("6","姓名","密码"),6);
throw new Exception("模拟线程内异常");
}
};
ExecutorService pool = Executors.newCachedThreadPool();
Future<String> submit = service.submit(callable);
pool.shutdown();
System.out.println(submit.get());
}
}
如果只在test_thread2()中模拟抛出异常,因为线程内的事务未出现异常,线程内事务已提交,线程内抛出异常,被线程外catch,线程外事务被回滚,因此需要在TestService的addDeleteOneTransaction()方法中也抛出异常,线程外事物会回滚,线程内事物也会回滚。
四、总结
1、@Transactional事物最好用在接口实现类或接口实现方法( public 方法)上,而不是接口类上,spring建议不要在接口或者接口方法上使用@Transactional注解,因为只有在使用基于接口的代理时它才会生效。如果正在使用基于类的代理时,因为注解是 不能继承的,那么事务的设置将不能被基于类的代理所识别。
2、默认情况下只有来自外部的方法调用(该方法被事物修饰)才会被AOP代理捕获,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法被事物修饰。如果非要实现调用类内部方法(该方法被@Transactional修饰)事物生效,可以从beanFactory中获取代理实例来调用类内部方法。
3、如果在一个被@Transactional修饰的方法内启用多线程,那么该方法的事务与线程内的事务是2个完全不相关的事务,也就是说事务是不会从调用者线程传播到新建线程。