记录一次事务失效场景

场景重现

@Override
    @Transactional(rollbackFor = ServiceException.class)
    public boolean initializeTenantRoles(InitializeTenantParam initializeParam) {
        // 手动进行事务回滚(reg:因为以下逻辑中有子方法进行了事务的处理)
            try {


                //获取租户需要初始化超管的登录账号
                String account = initializeParam.getTenantAccount();
                //获取租户信息
                Tenant tenant = null;
                try {
                    tenant = this.lambdaQuery().eq(Tenant::getAccount, initializeParam.getTenantName()).one();
                } catch (Exception e) {
                    log.warn("不存在的租户");
                }
                if (ObjectUtils.isNotEmpty(tenant)) {
                    throw new ServiceException("租户账号重复,租户:".concat(initializeParam.getTenantName()));
                }
                Tenant saveDo = new Tenant();
                Long tenantId = lambdaQuery().orderByDesc(Tenant::getTenantId).last("limit 1").one().getTenantId()+1;
                String tenantIdStr = tenantId.toString();
                saveDo.setTenantName(initializeParam.getTenantName());
                saveDo.setTenantId(tenantId)
                        .setAccount(initializeParam.getTenantAccount())
                        .setEmail(initializeParam.getEmail())
                        .setExpiresTime(initializeParam.getExpiresTime())
                        .setStatus(initializeParam.getStatus())
                        .setRechargeImgUrl(initializeParam.getRechargeImgUrl());
                this.save(saveDo);

                //-----------清除旧模板-----------
                SysUser admUser = userService.selectUserByUserName(account);
                if (admUser != null) {
                    throw new ServiceException("请勿重复初始化模板,当前租户超管账户租户已存在,账户名:".concat(account));
                }
                //-----------开始创建模板-----------
                //获取租户是否已经创建了顶级部门
                SysDept newTopDept = new SysDept();
                SysDept topDept = sysDeptMapper.checkIsHaveTopDeptByTenantId(tenantIdStr);
                if (ObjectUtils.isEmpty(topDept)) {
                    newTopDept.setParentId((long) 0);
                    newTopDept.setDeptName(initializeParam.getTenantName());
                    newTopDept.setStatus(Constants.SUCCESS);
                    newTopDept.setCreateBy("admin");
                    newTopDept.setTenantId(tenantIdStr);
                    newTopDept.setAncestors("0");
                    try {
                        deptService.saveNoTenant(newTopDept);
                    } catch (Exception e) {
                        throw new ServiceException("初始化部门失败");
                    }
                }

                //        创建租户菜单
                List<SysTenantMenu> sysMenuTemps = addTenantMenuTemp(tenantIdStr,true);

                //新的租户角色
                SysRole newTopRole = new SysRole();
                //获取租户1的角色
                SysRole topRole = roleService.selectTopRolesByTenantId("1");
                if (topRole != null) {
                    //获取租户1的角色已分配的角色信息
                    List<Long> topMenuIds = menuService.selectMenuListByRoleId(topRole.getRoleId());
                    //创建新的租户角色
                    newTopRole.setRoleName(topRole.getRoleName());
                    newTopRole.setRoleKey(topRole.getRoleKey());
                    newTopRole.setRoleSort(topRole.getRoleSort());
                    newTopRole.setStatus(topRole.getStatus());
                    newTopRole.setMenuCheckStrictly(true);
                    newTopRole.setDeptCheckStrictly(true);
                    newTopRole.setRemark(topRole.getRemark());
//            Long[] newTopMenuIds = topMenuIds.toArray(new Long[0]);,
                    Long[] newTopMenuIds = topMenuIds.stream().filter(Objects::nonNull).toArray(Long[]::new);
                    newTopRole.setMenuIds(newTopMenuIds);

                    newTopRole.setTenantId(tenantIdStr);
                    try {
                        roleService.insertRole(newTopRole);
                    } catch (Exception e) {
                        log.error("初始化角色失败,{}",e);
                        throw new ServiceException("初始化角色失败");
                    }
                }
                try {
                    saveSysRoleMenu(newTopRole, sysMenuTemps);
                } catch (Exception e) {
                    log.error("初始化角色菜单失败,{}",e);
                    throw new ServiceException("初始化角色菜单失败");
                }

                //创建超级管理员账户
                SysUser newUser = new SysUser();
                newUser.setDeptId(newTopDept.getDeptId());
                newUser.setUserName(account);
                newUser.setNickName(account);
                newUser.setSex("1");
                newUser.setPassword(SecurityUtils.encryptPassword("123456"));
                newUser.setStatus("0");
                newUser.setDelFlag("0");
                newUser.setAdminFlag(Constants.NO);
                newUser.setTenantId(tenantIdStr);
                Long[] newUserRoles = new Long[]{newTopRole.getRoleId()};
                newUser.setRoleIds(newUserRoles);
                SysPost sysPost = SysPost.builder().postName("管理员").postCode(account.concat("adm")).tenantId(tenantIdStr).build();
                try {
                    iSysPostService.saveNoTenant(sysPost);
                } catch (Exception e) {
                    log.error("初始化岗位失败,{}",e);
                    throw new ServiceException("初始化岗位失败");
                }
                newUser.setPostIds(new Long[]{sysPost.getPostId()});
                try {
                    userService.insertUser(newUser);
                } catch (Exception e) {
                    log.error("初始化用户失败,{}",e);
                    throw new ServiceException("初始化用户失败");
                }
                return true;
        });

场景中,由于insertrole子方法内已经被处理了事务,所以,异常的时候回滚不了数据

解决方案:

方案1:例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException()语句,以便让aop捕获异常再去回滚,并且在service上层(webservice客户端,view层action)要继续捕获这个异常并处理

手动处理异常,把异常回滚。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;

@Service
public class TenantService {

    @Autowired
    private PlatformTransactionManager transactionManager;

    public boolean initializeTenantRoles(InitializeTenantParam initializeParam) {
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        return transactionTemplate.execute(status -> {
            try {
                // 业务逻辑
                String account = initializeParam.getTenantAccount();
                Tenant tenant = this.lambdaQuery().eq(Tenant::getAccount, initializeParam.getTenantName()).one();
                if (tenant != null) {
                    throw new ServiceException("租户账号重复,租户:" + initializeParam.getTenantName());
                }
                // 更多业务逻辑...
                return true;
            } catch (Exception e) {
                // 手动回滚事务
                status.setRollbackOnly();
                throw e;
            }
        });
    }
}

方案2:在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常

也可以,在方法抛出异常后的处理。

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

原理 解释:

为什么会这样呢?

      此错在没有Transaction无法回滚事务。自调用导致@Transactional 失效。

      spring里事务是用注解配置的,当一个方法没有接口,单单只是一个方法不是服务时,事务的注解是不起作用的,需要回滚时就会报错。

      出现这个问题的根本原因在于AOP的实现原理。由于@Transactional 的实现原理是AOP,AOP的实现原理是动态代理,换句话说,自调用时不存在代理对象的调用,这时不会产生我们注解@Transactional 配置的参数,自然无效了。

      虽然可以直接从容器中获取代理对象,但这样有侵入之嫌,不推荐。

也是在此记录一笔:

      事务必须用在服务上,且一个服务一个事务,不得嵌套。
 

      sping的事务是通过注解配置上去的,而下面的那个方法并没有接口,在实现类里面只是一个简单的方法而已,对于事务的注解来说没有任何作用,所以在这个方法里面调用回滚的方法自然就报错了。

      所以在以后的项目中如果你要使用事务,那么请记住,一个服务一个事务,一次请求一个事务,千万不要想着用调用方法,然后再一个方法上面加事务。你只能调用另外一个服务,在另外一个服务上面加事务。

事务异常的场景:

Spring事务在以下场景可能会失效:

  1. 异常未被捕获或处理:如果在事务中发生了异常,但是异常没有被捕获或处理,那么事务将会失效。这种情况下,事务中已经执行的操作无法回滚。

  2. 事务方法内部调用其他事务方法:如果在一个事务方法内部调用另一个事务方法,但是被调用的方法没有使用Required或RequiresNew事务传播级别,那么事务将会失效。被调用的方法将独立于调用方法,无法参与到调用方法的事务中。

  3. 非Spring管理的事务:如果在使用Spring进行事务管理的应用中,使用了其他框架或手动管理事务,那么Spring事务将会失效。由于Spring无法感知到非Spring管理的事务,无法进行事务的隔离和回滚操作。

  4. 不同数据源之间的事务:如果在一个事务中同时操作多个数据源(数据库),但是数据源的事务管理器不同,那么事务将会失效。由于Spring无法跨多个不同的事务管理器进行事务管理,无法实现跨数据源的事务一致性。

  5. 线程内事务:如果在一个线程内执行多个事务操作,但是事务操作之间没有进行合适的隔离或提交,那么事务将会失效。在一个线程内,每个事务操作应该使用单独的事务,避免事务间的干扰。

总的来说,Spring事务会在一些特定的情景下失效,主要是由于事务的传播机制、异常处理、多数据源等因素导致的。为了确保事务的有效性,应该注意事务的传播级别、异常处理和事务的隔离性等方面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java斌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值