大家吼鸭!今天学习新项目的时候,项目中运用了编程式项目,有点不理解什么叫编程式事务,于是我去查询了一些资料,大概了解了一下。现在做一个简单的介绍。
编程式事务和声明式事务的区别
现在想象一个场景,你妈让你去买酱油(事务操作),如果此时是
- 编程式事务(手动档)
你妈对你说:“你先带钱出门(开始事务),买酱油(操作),如果超市关门了(失败),就把钱带回来(回滚),否则付钱(提交)“,你全程需要自己判断,如果超市没关门,买酱油,关门了,不买。灵活但累。
- 声明式事务
你妈直接给你贴个纸条,“买酱油,买不到就回家”(@Transactional
)。你无需自己动脑,但是纸条写死了规则,遇到特殊情况(比如超市排队太长)没法临时改主意。
编程式事务
定义:
通过编写代码手动控制事务的开启、提交、回滚等操作,直接调用事务管理 API(如 TransactionTemplate
或 PlatformTransactionManager
)。
特点:
- 代码侵入性强:事务管理与业务逻辑耦合。
- 灵活性高:可以精确控制事务的边界(例如在嵌套逻辑中动态决定事务行为)。
- 事务边界指的是开始点和结束点
- 开始点:开启事务(如
transactionTemplate.execute()
或@Transactional
方法入口)。 - 结束点:提交事务或回滚事务的时候
- 开始点:开启事务(如
- 事务边界指的是开始点和结束点
- 需要手动处理:需要显式调用
execute()
、commit()
、rollback()
等方法。
例如:
return transactionTemplate.execute(status -> {// 事务操作(更新数据库、删除缓存等)return success;
});
这里通过 TransactionTemplate
的 execute()
方法手动包裹事务逻辑。
下面是编程式事务的简单示例,以点赞项目为例:
// 加锁,避免用户短时间多次取消点赞synchronized (loginUser.getId().toString().intern()) {// 编程式事务return transactionTemplate.execute(status -> {// 获取当前当前进行点赞的博客idLong blogId = doThumbRequest.getBlogId();// 判断当前用户是否已经点赞过该博客
// Thumb thumb = this.lambdaQuery()
// .eq(Thumb::getBlogId, blogId)
// .eq(Thumb::getUserId, loginUser.getId())
// .one();Object thumbIdObj = redisTemplate.opsForHash().get(ThumbConstant.USER_THUMB_KEY_PREFIX + loginUser.getId().toString(), blogId.toString());// 如果没有点赞过,抛出异常ThrowUtils.throwIf(thumbIdObj == null, ErrorCode.OPERATION_ERROR, "未点赞过");Long thumbId = Long.valueOf(thumbIdObj.toString());// 更新博客点赞数 - 1boolean update = blogService.lambdaUpdate().eq(Blog::getId, blogId).setSql("thumbCount = thumbCount - 1").update();// 删除点赞记录boolean success = update && this.removeById(thumbId);if(success) {// 如果成功删除点赞记录,则将其从缓存中删除redisTemplate.opsForHash().delete(ThumbConstant.USER_THUMB_KEY_PREFIX + loginUser.getId().toString(), blogId.toString());}return success;});}
声明式事务
定义:
通过注解(如 @Transactional
)或 XML 配置声明事务规则,由框架自动管理事务的提交和回滚,无需编写事务管理代码。
特点:
- 代码侵入性低:通过 AOP 将事务逻辑与业务代码解耦。
- 简洁性:只需一个注解即可完成事务控制。
- 依赖代理机制:基于 Spring AOP 实现,通过动态代理在方法调用前后插入事务逻辑。
核心区别
特性 | 编程式事务 | 声明式事务 |
---|---|---|
控制方式 | 手动编写事务代码 | 通过注解或配置声明 |
侵入性 | 强(与业务代码耦合) | 弱(通过 AOP 分离) |
灵活性 | 高(可动态控制事务边界) | 较低(基于方法或类级别配置) |
代码复杂度 | 高(需处理提交、回滚) | 低(只需一个注解) |
适用场景 | 需要精细控制事务的复杂逻辑 | 大多数常见业务场景 |
声明式事务举例
@Service
public class BlogService {@Autowiredprivate BlogMapper blogMapper;@Autowiredprivate ThumbService thumbService;// 声明式事务注解@Transactional(rollbackFor = Exception.class) // 指定所有异常都回滚public boolean cancelThumb(LoginUser loginUser, DoThumbRequest doThumbRequest) {// 业务逻辑(无需手动管理事务)Long blogId = doThumbRequest.getBlogId();Long userId = loginUser.getId();// 检查是否已点赞(直接操作数据库)Thumb thumb = thumbService.findByUserAndBlog(userId, blogId);if (thumb == null) {throw new BusinessException(ErrorCode.OPERATION_ERROR, "未点赞过");}// 更新博客点赞数boolean updateSuccess = blogMapper.decrementThumbCount(blogId) > 0;// 删除点赞记录boolean deleteSuccess = thumbService.removeById(thumb.getId());if (updateSuccess && deleteSuccess) {// 更新缓存(确保在事务提交后执行)redisTemplate.opsForHash().delete(ThumbConstant.USER_THUMB_KEY_PREFIX + userId, blogId.toString());}return updateSuccess && deleteSuccess;}
}
关键点说明:
@Transactional
注解:- 添加在方法或类上,声明该方法需要事务管理。
rollbackFor = Exception.class
表示所有异常(包括检查型异常)都会触发回滚(默认仅回滚运行时异常)。
- 事务边界:
- 方法开始时自动开启事务,方法正常结束时提交事务,抛出异常时回滚。
- 缓存与事务一致性:
- 缓存操作应放在事务提交之后(避免脏读)。如果使用声明式事务,缓存删除操作会在方法返回前(事务提交后)执行。
- 注意事项:
- 避免在同一个类中非事务方法调用
@Transactional
方法(代理失效问题)。 - 确保数据库引擎支持事务(如 InnoDB)。
- 避免在同一个类中非事务方法调用