Spring事务使用中的一些“坑”点

背景
笔者最初使用spring事务时,主要通过@Transactional注解来管理事务,由于对spring事务原理了解不够清楚,一次程序出现异常时发现事务没有生效,场景是同一个类中被@Transactional修饰的方法A调用同样被@Transactional修饰的方法B时发现B中事务未生效,这才回过头学习了下spring事务管理的原理,找到了事务失效的原因:Spring的事务本质上是个代理类,而本类方法直接调用时其对象本身并不是织入事务的代理,所以事务切面并未生效

  使用spring声明式事务时,除了同一类调用本身会使事务失效外,还有一些其他场景也容易掉入事务失效或者事务传播失效的坑里,这里简单做下总结:

1、数据库引擎不支持事务
常用的 oracle 直接就是支持事务的,而 mysql 的 innodb 支持事务,myIsam 的话,是不支持事务的。在 mysql5.1 版本之前,默认引擎是 myIsam ,而之后的版本则默认就是innodb 了~

建议检查项:mysql的数据库引擎

执行命令

show variables like ‘%storage_engine%’;

2、当前实体没有被spring管理
//@Service

public class UserService {

​ @Autowired

private UserDao userDao;

@Transactional

public void add(User user) {

    userDao.save(user);

}    

}

如果我们的类没有使用service注解,或者说没有交给spring管理bean实例,那么它的add方法也不会生成事务。

3、spring事务传播特性使用不对
@Service

public class UserService {

@Autowired

private UserDao userDao;

@Transactional(propagation = Propagation.NEVER)

public void add(User user) {

    userDao.save(user);

}    

}

   如果add方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。只有这三种传播特性才会创建新事务:PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED,Spring默认使用的PROPAGATION_REQUIRED是天然支持事务的。

4、自己对异常做了捕捉且没有对外抛出
@Log4j2

@Service

public class UserService {

@Autowired

private UserDao userDao;

@Transactional

public void add(User user) {

    userDao.save(user);

    try {

        userDao.save(user);

    } catch (Exception e) {

        log.error(e.getMessage(), e);

    }

}

}

   如果我们在事务方法中手动try,catch住了异常,并且没有往外抛出,那么方法发生异常时事务不会回滚,只有把异常抛出,Spring事务的AOP捕获到异常,才能使事务得到回滚。

5、自己对异常进行捕捉并抛出,但是抛出的异常类型不对
@Log4j2

@Service

public class UserService {

@Autowired

private UserDao userDao;

@Transactional

public void add(User user) throws Exception {

    try {

        userDao.save(user);;

    } catch (Exception e) {

        log.error(e.getMessage(), e);

        throw new Exception(e);

    }

}

}

   相对上一种情况,咱们把异常往外抛出了,是不是Spring事务管理器就能处理异常并回滚呢?不一定!Spring的事务管理默认是针对unchecked exception回滚,也就是默认对Error异常和RuntimeException异常以及其子类进行事务回滚,上面代码抛出了Exception,Srping事务管理器并不会对该异常进行事务回滚,当然,抛出自定义异常也不会回滚了。

   通常自己捕捉异常需要回滚时y有两种方式:

   1、可以在catch中显式的调用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 语句      进行手动回滚,

   2、在事务注解中添加参数,eg: @Transactional(rollbackFor=MyException.class)

6、多线程调用
@Log4j2

@Service

public class UserService {

@Autowired

  private UserDao userDao;

@Autowired

private RoleService roleService;

@Transactional

public void add(User user) throws Exception {

    userDao.save(user);

    new Thread(() -> {

        roleService.save();

    }).start();

}

}

@Service

public class RoleService {

@Transactional

public void save() {

    System.out.println("插入一条角色信息");

}

}

   在事务方法add中,调用了RoleService的事务方法save,但是save是在另外一个线程中调用的,这样会导致两个事务方法不在同一个线程中,获取到的数据库连接不一样,那么一定是在两个不同的事务中,那么为什么不在同一个线程中,获取到的数据库连接不一样?

   因为spring的事务是通过数据库连接来实现的,每个线程中保存了一个map,key是数据源,value是数据库连接

private static final ThreadLocal<Map<Object, Object>> resources =

  new NamedThreadLocal<>("Transactional resources");

   通常我们说的事务,都是限定在同一个数据库连接下的,只有拥有同一个数据库连接才能同时提交和回滚,如果是不同的线程使用事务,拿到的数据库连接肯定是不一样的,所以可以说是不同的事务。

7、使用多层事务时结果不符合预期
public class UserService {

@Autowired

private UserDao userDao;

@Autowired

private RoleService roleService;

@Transactional

public void add(User user) throws Exception {

    userDao.save(user);

    roleService.save();

}

}

@Service

public class RoleService {

@Transactional(propagation = Propagation.NESTED)

public void save() {

    System.out.println("插入一条角色信息");

}

}

   以上代码使用了多层嵌套的内部事务,内层事务传播属性设置为NESTED,目的是调用roleService.save方法时,如果出现了异常,只回滚roleService.save方法里的内容,不回滚 userDao.save里的内容,但实际结果是userDao.save也回滚了。

   原因是roleService.save方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚内层事务。

那应该怎么实现预期效果?

@Log4j2

@Service

public class UserService {

@Autowired

private UserDao userDao;

@Autowired

private RoleService roleService;

@Transactional

public void add(User user) throws Exception {

    userDao.save(user);

    try {

        roleService.save();

    } catch (Exception e) {

        log.error(e.getMessage(), e);

    }

}

}

   这里我们把内层事务包在了try,catch中,对内部事务的异常进行捕捉,这样就不影响外部事务了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值