Spring声明式数据库事务使用-@Transactional自调用失效的问题

@Transactional在某些场景下会失效,这是值得注意的问题,上一节中,我们测试传播行为,是使用一个RoleBatchServiceImpl类去调用RoleServiceImpl类的方法,那么如果我们不创建RoleBatchServiceImpl类,而是只是使用RoleServiceImpl类进行批量插入用户会怎样,下面我们改造RoleServiceImpl

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
            propagation = Propagation.REQUIRED
    )
    public int saveRoles(List<SysRole> records) {
        int count = 0;
        for(SysRole sysRole: records){
		//调用自己类自身的方法,产生自调用问题
            count += save(sysRole);
        }
        return count;
    }
//修改传播行为REQUIRES_NEW,每次调用产生新事务
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1,
    propagation = Propagation.REQUIRES_NEW)
    public int save(SysRole record) {
        return sysRoleMapper.insert(record);
    }

代码中新增了saveRoles,对应的接口也需要改造,这部比较简单就不再演示了。对于saveRole方法,我们把传播行为修改为REQUIRES_NEW,也就是每次调用产生新的事务,而saveRoles就调用这个方法。这是一个类自身方法之间的调用,我们称之为自调用。那么它能够成功的每次调用都产生新的事务码?下面是我们的测试日志

在这里插入图片描述
通过日志可以看到,Spring在运行过程中并没有创建任何新的事务独立的运行save方法。换句话说,我们的注解@Transactional失效了,为什么会这样呢?
上一节中,我们谈到过Spring数据库事务的约定,其实现原理是AOP,而AOP的原理是动态代理,在自调用的过程中,是类自身的调用,而不是代理对象去调用,那么就不会产生AOP。这样Spring就不会把你的代码织入到约定的流程中,于是就产生了现在看到的失败场景。为了克服这个问题,我们可以像之前那言,用一个service去调用另一个service,这就是代理对象的调用,Spring才会把你的代码织入事务流程,当然也可以从Spring IoC容器获取代理对象去启用AOP,例如,我们再次对SysRoleServiceImpl进行改造,代码啊如下:

package cn.hctech2006.boot.bootmybatis.service.impl;

import cn.hctech2006.boot.bootmybatis.bean.SysRole;
import cn.hctech2006.boot.bootmybatis.mapper.SysRoleMapper;
import cn.hctech2006.boot.bootmybatis.service.SysRoleService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * 服务实现类
 */
@Service
public class SysRoleServiceImpl implements SysRoleService , ApplicationContextAware {
    @Autowired
    private SysRoleMapper sysRoleMapper=null;
    @Autowired
    private ApplicationContext applicationContext = null;
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1,
    propagation = Propagation.REQUIRES_NEW)
    public int save(SysRole record) {
        return sysRoleMapper.insert(record);
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
            propagation = Propagation.REQUIRED
    )
    public int saveRoles(List<SysRole> records) {
        int count = 0;
        for(SysRole sysRole: records){
            //调用子方法,将使用@Tranactional定义的传播行为
            //从IOC容器中取出代理对象
            SysRoleService sysRoleService = applicationContext.getBean(SysRoleService.class);
            //使用代理对象调用方法插入用户,此时会织入Spring数据库事务流程中
            count += sysRoleService.save(sysRole);
        }
        return count;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

从这个代码中我们可以实现ApplciationContextAware接口的setApplcicationContext方法,这样遍能够把IoC容器设置到这个类中来。于是在saveRoles方法中,我们通过IoC容器获取了SysRoleService这个接口对象。但是请注意,这是一个代理对象,并且使用它调用了传播行为为REQUIRES_NEW的save()方法,这样才可以运行成功。我还监控了获取UserService对象,如图

在这里插入图片描述
从代码中我们可以看出,从IOC容器中取出的对象是一个代理对象,通过它可以克服自调用的问题。下面是运行这段嗲码的日志:
在这里插入图片描述
可以看出,Spring已经为我们的方法创建了新的事务,这样自调用的问题就克服了,只是菏泽个代码需要依赖于Spring的API,这样就会造成代码的侵入。使用一个类调用另一个类则不会有依赖,只是相对麻烦一点

发布了180 篇原创文章 · 获赞 114 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43404791/article/details/105540069
今日推荐