java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > 同一个类中,一个无事务方法调用一个有事务方法时,事务失效

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

作者:linab112

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

事务的使用

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

事务的实现原理

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

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

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

具体流程如下:

代理的两种方式:

原因

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

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

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

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

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

常见的AOP注解包括:

事务失效代码

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 注解

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

未使用 AOP 注解

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

您可能感兴趣的文章:
阅读全文