在事物过程中出现异常终止时,后续的内容不再执行,但之前的插入操作依然没有回滚。
声明式注解事务失效原因,主要由以下几点:
- 方法可见性:
@Transactional
仅在public
方法上生效。 - 自调用:当类中的方法调用同一个类中的另一个
@Transactional
方法时,事务可能不会生效。这是因为事务注解是通过 AOP 实现的,而 Spring 的 AOP 代理机制在这种情况下不会被触发。 - 异常处理:只有
RuntimeException
和Error
类型的异常会触发事务回滚。如果你抛出的是checked exception
,事务不会回滚,除非你明确指定rollbackFor
属性。 - 代理对象:确保你是在 Spring 管理的代理对象上调用方法。如果你直接使用
new
关键字实例化对象,Spring 的 AOP 代理机制将不会被应用。
很显然,自调用这种情况很容易出现,可以替换为编程式事务:
public Long userRegisterByPhone(String phone) {
return transactionTemplate.execute(status -> {
try {
// 获得全局自增的用户编号
Long ojId = redissonClient.getAtomicLong(RedisKeyConstants.ONLINEJUDGE_ID_GENERATOR).incrementAndGet();
// 新建用户实例
User user = User.builder()
.userAccount(phone)
.userPassword(DigestUtils.md5DigestAsHex((SALT + phone).getBytes()))
.userName("用户" + ojId)
.phone(phone)
.userState(UserStateEnum.LOGOUT.getValue())
.userRole("common_user")
.ojId(ojId)
.build();
// 插入到数据库
this.save(user);
// 获取刚刚插入的用户的数据库id
Long userId = this.baseMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getOjId, ojId)).getId();
// 分配角色
TUserRoleRel userRoleRel = TUserRoleRel.builder()
.userId(userId)
.roleId(RoleConstants.COMMON_USER_ROLE_ID)
.createTime(LocalDateTime.now())
.updateTime(LocalDateTime.now())
.isDeleted(DeletedEnum.NOT_DELETED.getValue())
.build();
userRoleRelService.save(userRoleRel);
// 角色信息存入 redis
List<Long> roles = Lists.newArrayList(RoleConstants.COMMON_USER_ROLE_ID);
String redisRolesKey = RedisKeyConstants.buildUserRoleKey(ojId);
redissonClient.getBucket(redisRolesKey).set(JsonUtil.toJsonString(roles));
return userId;
} catch (Exception e) {
status.setRollbackOnly(); // 标记事务为回滚
log.error("==> 系统注册用户异常: ", e);
return null;
}
});
}