一、场景引入
在实际开发中,我们经常遇到这样的需求:
- 需要异步更新数据库某个字段
- 必须保证数据一致性
- 异常时要回滚事务
- 但又不想让异常影响主流程
比如本文要讲解的更新用户状态场景:
- 根据用户ID查询用户信息
- 更新用户的某个状态字段(如 status、lastLoginTime 等)
- 记录操作日志
- 整个过程需要保证原子性
二、@Async + @Transactional 的问题
很多同学第一反应是这样写:
java
@Async
@Transactional(rollbackFor = Exception.class)
public void updateUserStatus(Long userId) {
// 更新用户状态
}
但这是错误的! 因为:
- @Async 让方法在代理对象中执行
- @Transactional 需要代理对象创建事务
- 两者同时使用时,事务会失效
三、解决方案:TransactionTemplate
1、什么是 TransactionTemplate
TransactionTemplate 是 Spring 提供的编程式事务模板,它让我们可以手动控制事务的提交和回滚,同时保持代码简洁。
2、完整代码实现
第一步:配置异步支持
java
@SpringBootApplication
@EnableAsync // 启用异步
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
第二步:实体类定义
java
// 用户实体
@Data
@TableName("user")
public class User {
@TableId
private Long id;
private String username;
private String email;
private Integer status; // 用户状态:0-禁用,1-正常
private Date lastLoginTime; // 最后登录时间
private Integer loginCount; // 登录次数
private Date updateTime; // 更新时间
}
第三步:Mapper接口
java
@Mapper
public interface UserMapper extends BaseMapper<User> {
/**
* 根据用户ID查询用户
*/
@Select("SELECT * FROM user WHERE id = #{userId}")
User selectByUserId(@Param("userId") Long userId);
/**
* 更新用户状态
*/
@Update("UPDATE user SET status = #{status}, update_time = NOW() WHERE id = #{userId}")
int updateUserStatus(@Param("userId") Long userId, @Param("status") Integer status);
}
第四步:Service实现(核心代码)
java
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private TransactionTemplate transactionTemplate; // 注入事务模板
/**
* 异步更新用户状态 - 使用 TransactionTemplate
* 异常自行处理,不回抛
*/
@Async
public void updateUserStatusAsync(Long userId, Integer newStatus) {
try {
log.info("========== 开始异步更新用户状态 ==========");
log.info("用户ID: {}, 新状态: {}", userId, newStatus);
// 参数校验
if (userId == null || newStatus == null) {
log.warn("参数不能为空");
return;
}
// 在事务中执行业务逻辑
transactionTemplate.execute(status -> {
try {
// 1. 查询用户是否存在
User user = userMapper.selectByUserId(userId);
if (user == null) {
log.warn("用户不存在,ID: {}", userId);
return null;
}
log.info("查询到用户: {}, 当前状态: {}", user.getUsername(), user.getStatus());
// 2. 如果状态相同,不需要更新
if (newStatus.equals(user.getStatus())) {
log.info("用户状态已经是 {},无需更新", newStatus);
return null;
}
// 3. 更新用户状态
int result = userMapper.updateUserStatus(userId, newStatus);
if (result == 0) {
throw new RuntimeException("更新用户状态失败,用户ID: " + userId);
}
// 4. 模拟其他业务操作(可选)
// updateOtherData(userId);
log.info("用户 {} 状态更新成功: {} -> {}",
user.getUsername(), user.getStatus(), newStatus);
return null;
} catch (Exception e) {
// 设置事务回滚
status.setRollbackOnly();
log.error("处理失败,事务已回滚: {}", e.getMessage());
throw new RuntimeException(e); // 抛出异常触发回滚
}
});
log.info("用户ID: {} 状态更新处理完成", userId);
} catch (Exception e) {
// 外部捕获异常,只记录不抛出
log.error("异步更新用户状态失败,用户ID: {}, 错误: {}", userId, e.getMessage());
}
}
/**
* 异步更新用户最后登录时间
*/
@Async
public void updateLastLoginTimeAsync(Long userId) {
try {
transactionTemplate.execute(status -> {
try {
// 1. 查询用户
User user = userMapper.selectByUserId(userId);
if (user == null) {
log.warn("用户不存在,ID: {}", userId);
return null;
}
// 2. 更新最后登录时间和登录次数
user.setLastLoginTime(new Date());
user.setLoginCount(user.getLoginCount() == null ? 1 : user.getLoginCount() + 1);
user.setUpdateTime(new Date());
// 3. 执行更新
int result = userMapper.updateById(user);
if (result == 0) {
throw new RuntimeException("更新最后登录时间失败");
}
log.info("用户 {} 最后登录时间更新成功", user.getUsername());
} catch (Exception e) {
status.setRollbackOnly();
throw new RuntimeException(e);
}
return null;
});
} catch (Exception e) {
log.error("更新最后登录时间失败: {}", e.getMessage());
}
}
/**
* 批量异步更新用户状态
*/
@Async
public void batchUpdateUserStatusAsync(List<Long> userIds, Integer newStatus) {
try {
log.info("开始批量更新用户状态,共 {} 个用户", userIds.size());
transactionTemplate.execute(status -> {
try {
int successCount = 0;
int failCount = 0;
for (Long userId : userIds) {
try {
// 更新单个用户状态
User user = userMapper.selectByUserId(userId);
if (user != null) {
int result = userMapper.updateUserStatus(userId, newStatus);
if (result > 0) {
successCount++;
log.debug("用户 {} 状态更新成功", userId);
} else {
failCount++;
log.warn("用户 {} 状态更新失败", userId);
}
} else {
failCount++;
log.warn("用户 {} 不存在", userId);
}
} catch (Exception e) {
failCount++;
log.error("处理用户 {} 时发生错误: {}", userId, e.getMessage());
}
}
log.info("批量更新完成,成功: {},失败: {}", successCount, failCount);
} catch (Exception e) {
status.setRollbackOnly();
throw new RuntimeException("批量更新失败", e);
}
return null;
});
} catch (Exception e) {
log.error("批量更新用户状态失败: {}", e.getMessage());
}
}
}
第五步:Controller接口
java
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
/**
* 异步更新用户状态
*/
@PostMapping("/status/update")
public Result<String> updateUserStatus(@RequestParam Long userId, @RequestParam Integer status) {
log.info("收到更新用户状态请求,用户ID: {}, 新状态: {}", userId, status);
// 异步执行,立即返回
userService.updateUserStatusAsync(userId, status);
return Result.success("任务已提交,正在异步处理中");
}
/**
* 异步更新用户最后登录时间
*/
@PostMapping("/login-time/update")
public Result<String> updateLastLoginTime(@RequestParam Long userId) {
userService.updateLastLoginTimeAsync(userId);
return Result.success("更新任务已提交");
}
/**
* 批量更新用户状态
*/
@PostMapping("/status/batch-update")
public Result<String> batchUpdateUserStatus(@RequestBody BatchUpdateRequest request) {
log.info("收到批量更新请求,共 {} 个用户", request.getUserIds().size());
userService.batchUpdateUserStatusAsync(request.getUserIds(), request.getNewStatus());
return Result.success("批量任务已提交,共 " + request.getUserIds().size() + " 个");
}
}
// 批量更新请求体
@Data
class BatchUpdateRequest {
private List<Long> userIds;
private Integer newStatus;
}
四、工作原理详解
1、执行流程

2、为什么这样设计?
- @Async:让方法在独立线程中执行,不阻塞主线程
- TransactionTemplate:手动控制事务,避免注解失效
- 双层try-catch:
- 内层捕获业务异常,触发事务回滚
- 外层捕获所有异常,只记录不抛出
五、总结
1、方案优势
✅ 异步不阻塞:主线程快速返回
✅ 事务保证:出错自动回滚
✅ 异常隔离:异常不影响主流程
✅ 代码简洁:TransactionTemplate 比编程式事务更优雅
✅ 可扩展:容易添加更多业务逻辑
2、适用场景
- 更新用户状态
- 记录操作日志
- 修改配置项
- 任何需要异步更新表字段的场景
六、注意事项
- 事务超时设置:可在配置文件中设置默认事务超时时间
- 线程池配置:生产环境建议自定义线程池参数
- 批量操作:注意控制批量大小,避免大事务
- 日志记录:记录关键步骤,方便排查问题
- 幂等性:考虑重复请求的处理
通过本文的方案,你可以轻松实现异步更新表字段+事务保证+异常隔离的业务需求,代码简洁且易于维护。TransactionTemplate 是 Spring 提供的一把利器,值得在每个项目中熟练掌握