场景重现
@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事务在以下场景可能会失效:
-
异常未被捕获或处理:如果在事务中发生了异常,但是异常没有被捕获或处理,那么事务将会失效。这种情况下,事务中已经执行的操作无法回滚。
-
事务方法内部调用其他事务方法:如果在一个事务方法内部调用另一个事务方法,但是被调用的方法没有使用Required或RequiresNew事务传播级别,那么事务将会失效。被调用的方法将独立于调用方法,无法参与到调用方法的事务中。
-
非Spring管理的事务:如果在使用Spring进行事务管理的应用中,使用了其他框架或手动管理事务,那么Spring事务将会失效。由于Spring无法感知到非Spring管理的事务,无法进行事务的隔离和回滚操作。
-
不同数据源之间的事务:如果在一个事务中同时操作多个数据源(数据库),但是数据源的事务管理器不同,那么事务将会失效。由于Spring无法跨多个不同的事务管理器进行事务管理,无法实现跨数据源的事务一致性。
-
线程内事务:如果在一个线程内执行多个事务操作,但是事务操作之间没有进行合适的隔离或提交,那么事务将会失效。在一个线程内,每个事务操作应该使用单独的事务,避免事务间的干扰。
总的来说,Spring事务会在一些特定的情景下失效,主要是由于事务的传播机制、异常处理、多数据源等因素导致的。为了确保事务的有效性,应该注意事务的传播级别、异常处理和事务的隔离性等方面。