spring在多线程下如何进行事物管理

一、以下情况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个完全不相关的事务,也就是说事务是不会从调用者线程传播到新建线程。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值