当前位置: 首页 > article >正文

【BUG】声明式事务失效导致日志记录失败

发生背景

公司里有一段代码,刚看到我是十分震惊的,我们知道在这种场景下事务会失效

  1. @Transactional 注解修饰的事务方法权限修饰符只能是 public,否则事务会失效
  2. 当前类中的方法不能调用事务方法,否则事务失效

然而公司内有一段向数据库中添加日志的代码,都存在导致事务失效的问题;带着事务是否会失效的疑问,自己亲自在本地实现了类似的代码。

问题复现

@Transactional
@Service
public class UserServiceImpl {

    @Autowired
    private UserDao userDao;

    public void register(User user) {
	    //...
        save(user);
        //...
    }
    
	// 模拟添加日志到数据库
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    private void save(User user) {
        userDao.save(user);
    }
}

/**
 * <p>
 *  测试事务失效
 * @author zhl
 */
public class TransactionTest {
    @Test
    public void testTransaction(){
        ApplicationContext ctx = new AnnotationConfigApplicationContext(MyBatisAutoConfiguration.class);
        User user = new User();
        user.setName("zhangsan");
        user.setPassword("123456");
        UserServiceImpl userServiceImpl = (UserServiceImpl) ctx.getBean("userServiceImpl");
        userServiceImpl.register(user);
    }
}

在这里插入图片描述
被 @Transactional 修饰的save方法的事务失效,传播属性失效,由于当前类被 @Transactional 修饰,register 方法变成事务方法。并且通过控制台输出的日志,只有 register 方法开启了事务,save 方法事务确实失效了。
说明:权限修饰符不是 public 或 当前类中直接调用事务方法 都会导致事务失效。

验证一:事务方法权限修饰符不是 public,导致事务失效

  • 当前类实现 ApplicationContextAware ,从 Spring 容器中获取代理后的 UserServiceImpl.save 方法,让执行事务方法的途径正确(排除当前类直接调用事务方法导致的事务方法失效)
    1. 权限修饰符非 public,控制台只输出了一个 register 事务,原save方法事务失效;
      在这里插入图片描述

    2. 权限修饰符为 public,控制台输出了两个事务,且在最外层通过除零运行时异常,发现只有最外层 register 方法事务异常回滚,内部 save 方法事务正常提交,说明 save 方法的传播属性正常生效。
      在这里插入图片描述
      在这里插入图片描述

验证二:本类方法直接调用本类的事务方法,导致事务失效

  • 将 save 方法的权限修饰符改为 public(排除权限修饰符非 public 导致的事务失效)
    1. 控制台只输出一个事务,说明原 save方法事务失效
      在这里插入图片描述
    2. 当前类实现 ApplicationContextAware,从 Spring 容器中获取代理后的 UserServiceImpl.save 方法;发现控制台输出两个事务,且原 save 方法事务正常挂起,证明本类直接调用本类的事务方法,导致原事务方法失效。
      在这里插入图片描述
      在这里插入图片描述

结论

经过验证证明 save 方法事务失效。由于类上的 @Transactional 注解,为所有方法开启了事务,包括register方法,传播属性和隔离属性等都是默认值,如果在 save 方法外,register 方法中遇到异常,整个方法都会回滚,save 事务失效传播属性失效,同样随着外部方法回滚而回滚,save 方法插入数据库失败。

由于直接调用了 save 方法,save 方法没有被 Spring 加工代理,导致事务失效;
由于权限修饰符不是 public,导致在动态代理时,无法访问到事务方法,无法为该方法进行加工添加事务。

解决方法

从 Spring 容器获取 Bean,调用事务方法,并且修改事务方法的权限修饰符为 public

@Transactional
@Service
public class UserServiceImpl implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Autowired
    private UserDao userDao;

    public void register(User user) {
    	//...
        UserServiceImpl userServiceImpl = (UserServiceImpl) applicationContext.getBean("userServiceImpl");
        userServiceImpl.save(user);
        //...
    }
    
	//模拟日志记录
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save(User user) {
        userDao.save(user);
    }

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

建议在方法上使用 @Transactional 注解


http://www.kler.cn/news/355558.html

相关文章:

  • 【命令操作】信创终端系统上timedatectl命令详解 _ 统信 _ 麒麟 _ 方德
  • Ruby CGI Cookie
  • 利用 Direct3D 绘制几何体—6.常量缓冲区
  • PHP 正则验证A-Z且排除某字母
  • 浅谈华为 HarmonyOS Next
  • C语言_指针_进阶
  • 基于HEC-Ras及ArcGIS的泥石流数值模拟与灾害风险评估典型案例
  • 项目实战:构建 effet.js 人脸识别交互系统的实战之路
  • 万户ezEIP企业管理系统 productlist.aspx SQL注入漏洞复现
  • C++ —— 类和对象
  • 目标检测——Cascade R-CNN算法解读
  • 一波基于winform和wpf的桌面端界面,历久弥新。
  • 数据结构(JAVA)包装类泛型
  • 如何测试IP速度?
  • 5G NR:UE初始接入信令流程浅介
  • 从头开始的可视化数据 matplotlib:初学者努力绘制数据图
  • Flink CDC同步mysql数据到doris
  • 如何用pyhton修改1000+图片的名字?
  • 【深入解析】ChatGPT各版本在论文写作中的五大表现差异
  • Vscode 如何设置自定义快捷键