java同一个类中,一个无事务方法调用一个有事务方法时,事务失效问题

 更新时间:2024年12月10日 15:22:57   作者:linab112  
本文详细介绍了Spring框架中事务管理的实现原理,包括@Transactional注解的使用、事务的开启、提交和回滚机制,以及代理对象的两种实现方式(JDK动态代理和CGLIB代理),文章还探讨了在同一个类中调用有事务方法时事务失效的原因,并提供了解决方法

事务的使用

在spring项目的开发中,通过在方法上添加Transactional注解,实现事务的管理,在方法开始开启事务,出现异常进行事务的回滚,方法结束前提交事务。

事务的实现原理

Transactional 注解是 Spring 框架用来实现声明式事务管理的重要工具。其原理主要基于 AOP(面向切面编程),通过动态代理在方法执行前、后以及异常情况下进行事务的处理。

当你在一个方法上使用 @Transactional 注解时,Spring 会在运行时生成一个代理对象,该对象会拦截对该方法的调用。

在调用之前,代理会开始一个新的事务;在方法执行完成后,代理会提交或回滚事务,具体取决于方法是否抛出了未处理的异常。

具体流程如下:

  • spring使用jdk动态代理技术或者cglib代理来创建目标类的代理对象
  • 当方法被调用时,实际上调用的是代理类中的增强方法,而不是直接调用目标类中的方法
  • 在调用方法之前,代理会根据注解的属性(传播行为和隔离级别)从PlatformTransactionManager中获取一个事务,在调用目标方法前开启事务
  • 执行目标方法
  • 目标方法执行成功,则提交事务,执行失败,则回滚事务

代理的两种方式:

  • JDK 动态代理:当目标类实现至少一个接口时,Spring会使用JDK动态代理。这种代理仅适用于基于接口的代理。原理是通过Java反射机制,在运行时生成一个实现了目标类接口的代理类。
  • CGLIB 代理:如果目标类没有实现任何接口,或者您强制配置为使用CGLIB,那么Spring会使用CGLIB库生成一个目标类的子类作为代理。原理是使用CGLIB库,通过继承目标类并重写其方法来实现代理。

原因

在同一个类中,一个无事务方法直接调用有事务的方法时,是通过this.方法名的方式调用。

this方式:它直接访问的是当前对象的实现,如果当前类被Spring AOP代理,那么使用this调用的方法将不会触发AOP切面。也就是说,切面(如事务管理、日志记录等)不会生效,因为你直接调用了目标对象的方法,而不是代理对象的方法。

通过Autowired注解注入的bean进行调用的方式:是通过spring容器管理的代理对象进行调用,这种情况下AOP特性可以正常工作,例如事务、日志等会生效。

Spring容器管理的代理对象的生成条件和时机

在Spring中,代理对象的生成通常与AOP(面向切面编程)相关,当类上使用@Component@Service@Repository@Controller等注解,并且方法上使用AOP相关的注解时,Spring会创建代理对象,以便能够在调用这些方法时执行切面逻辑。

常见的AOP注解包括:

  • @Transactional
  • @Cacheable
  • @Async
  • @Scheduled

事务失效代码

package com.ruoyi.system.service.impl;

import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.service.TestTransactionalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @Author linaibo
 * @Date 2024/8/3 15:36
 * @Version 1.0
 */
@Service
public class TestTransactionalServiceImpl implements TestTransactionalService {

    @Autowired
    private SysDeptMapper deptMapper;

    @Autowired
    private SysConfigMapper configMapper;


    @Override
    public int insertDept(SysDept dept) {
        dept.setAncestors("123123");
        deptMapper.insertDept(dept);
        insertConfig();
        return 1;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertConfig() {
        SysConfig config;
        for (int i = 0; i < 5; i++) {
            config = new SysConfig();
            config.setConfigName("配置" + i);
            configMapper.insertConfig(config);
            if (i == 2) {
                throw new RuntimeException();
            }
        }
        return 1;
    }
}

解决方法

①自己autowire自己(也可以将方法放到另外一个service中,然后注入该service进行调用)

package com.ruoyi.system.service.impl;

import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.service.TestTransactionalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @Author linaibo
 * @Date 2024/8/3 15:36
 * @Version 1.0
 */
@Service
public class TestTransactionalServiceImpl implements TestTransactionalService {

    @Autowired
    private SysDeptMapper deptMapper;
    @Autowired
    private SysConfigMapper configMapper;
    @Autowired
    private TestTransactionalService testTransactionalService;


    @Override
    public int insertDept(SysDept dept) {
        dept.setAncestors("123123");
        deptMapper.insertDept(dept);
        testTransactionalService.insertConfig();
        return 1;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertConfig() {
        SysConfig config;
        for (int i = 0; i < 5; i++) {
            config = new SysConfig();
            config.setConfigName("配置" + i);
            configMapper.insertConfig(config);
            if (i == 2) {
                throw new RuntimeException();
            }
        }
        return 1;
    }
}

②通过spring上下文获取到当前代理类

package com.ruoyi.system.service.impl;

import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.service.TestTransactionalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @Author linaibo
 * @Date 2024/8/3 15:36
 * @Version 1.0
 */
@Service
public class TestTransactionalServiceImpl implements TestTransactionalService {

    @Autowired
    private SysDeptMapper deptMapper;
    @Autowired
    private SysConfigMapper configMapper;
    @Autowired
    private TestTransactionalService testTransactionalService;
    @Autowired
    private ApplicationContext applicationContext;


    @Override
    public int insertDept(SysDept dept) {
        dept.setAncestors("123123");
        deptMapper.insertDept(dept);
        TestTransactionalService service = applicationContext.getBean(TestTransactionalService.class);
        service.insertConfig();
        return 1;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertConfig() {
        SysConfig config;
        for (int i = 0; i < 5; i++) {
            config = new SysConfig();
            config.setConfigName("配置" + i);
            configMapper.insertConfig(config);
            if (i == 2) {
                throw new RuntimeException();
            }
        }
        return 1;
    }
}

③使用AopContext获取到当前代理类,需要在启动类加上EnableAspectJAutoProxy(exposeProxy = true),exposeProxy = true用于控制AOP框架公开代理,公开后才可以通过AopContext获取到当前代理类。

package com.ruoyi.system.service.impl;

import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.system.service.TestTransactionalService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Objects;

/**
 * @Author linaibo
 * @Date 2024/8/3 15:36
 * @Version 1.0
 */
@Service
public class TestTransactionalServiceImpl implements TestTransactionalService {

    @Autowired
    private SysDeptMapper deptMapper;
    @Autowired
    private SysConfigMapper configMapper;


    @Override
    public int insertDept(SysDept dept) {
        dept.setAncestors("123123");
        deptMapper.insertDept(dept);
        TestTransactionalService service = Objects.nonNull(AopContext.currentProxy()) ? (TestTransactionalService)AopContext.currentProxy() : this;
        service.insertConfig();
        return 1;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertConfig() {
        SysConfig config;
        for (int i = 0; i < 5; i++) {
            config = new SysConfig();
            config.setConfigName("配置" + i);
            configMapper.insertConfig(config);
            if (i == 2) {
                throw new RuntimeException();
            }
        }
        return 1;
    }
}

总结

使用 AOP 注解

  • 如果 TestTransactionalService 类上使用了 AOP 相关的注解(如 @Transactional, @Aspect, @Around 等)
  • 通过 applicationContext.getBean(TestTransactionalService.class) 获取的对象将是一个代理对象

代理对象的创建是为了在方法调用前后加入额外的行为,比如事务管理、日志记录等。

未使用 AOP 注解

  • 如果该类没有任何 AOP 相关的注解
  • 获取的对象就是普通的 Bean
  • 没有经过 AOP 的增强

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 了解java Struts拦截器的相关操作

    了解java Struts拦截器的相关操作

    Struts为我们实现了很多的功能,比如数据自动封装,文件上传功能阿。Struts为我们提供的这些功能都是通过拦截器完成的。下面我们来详细了解一下吧
    2019-06-06
  • springboot实现在工具类(util)中调用注入service层方法

    springboot实现在工具类(util)中调用注入service层方法

    这篇文章主要介绍了springboot实现在工具类(util)中调用注入service层方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • SpringBoot 2.x整合Log4j2日志的详细步骤

    SpringBoot 2.x整合Log4j2日志的详细步骤

    log4j2优越的性能其原因在于log4j2使用了LMAX,一个无锁的线程间通信库代替了,logback和log4j之前的队列,并发性能大大提升,下面这篇文章主要给大家介绍了关于SpringBoot 2.x整合Log4j2日志的相关资料,需要的朋友可以参考下
    2022-10-10
  • java IO流之转换流的具体使用

    java IO流之转换流的具体使用

    转换流可以将一个字节流包装成字符流,或者将一个字符流包装成字节流,本文主要介绍了java IO流之转换流的具体使用,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • mybatis调用mysql存储过程并获取返回值方式

    mybatis调用mysql存储过程并获取返回值方式

    这篇文章主要介绍了mybatis调用mysql存储过程并获取返回值方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • BaseDao封装JavaWeb的增删改查的实现代码

    BaseDao封装JavaWeb的增删改查的实现代码

    Basedao 是一种基于数据访问对象(Data Access Object)模式的设计方法,它是一个用于处理数据库操作的基础类,负责封装数据库访问的底层操作,提供通用的数据库访问方法,本文给大家介绍了BaseDao封装JavaWeb的增删改查的实现代码,需要的朋友可以参考下
    2024-03-03
  • springboot集成redis启动报错问题的解决方式

    springboot集成redis启动报错问题的解决方式

    这篇文章主要介绍了springboot集成redis启动报错问题的解决方式,从错误信息上看缺少pool2相关包,查询资料发现当redis客户端选择Lettuce时候需要增加:commons-pool2
    添加引用,重启服务,需要的朋友可以参考下
    2023-11-11
  • 详解SpringBoot定制@ResponseBody注解返回的Json格式

    详解SpringBoot定制@ResponseBody注解返回的Json格式

    这篇文章主要介绍了详解SpringBoot定制@ResponseBody注解返回的Json格式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • 基于log4j2.properties踩坑与填坑

    基于log4j2.properties踩坑与填坑

    这篇文章主要介绍了log4j2.properties踩坑与填坑方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • SpringBoot实现RAS+AES自动接口解密

    SpringBoot实现RAS+AES自动接口解密

    本文主要介绍了SpringBoot实现RAS+AES自动接口解密,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03

最新评论