以文章发布为例,介绍Spring事务回滚在系统中的应用
事务回滚的核心概念
事务回滚是数据库管理系统中的关键机制,它确保数据库操作要么全部成功,要么全部失败。在Spring框架中,我们可以通过@Transactional注解轻松实现事务管理。
事务的ACID特性
- 原子性(Atomicity):操作不可分割,要么全部完成,要么全部不执行
- 一致性(Consistency):事务前后数据库状态保持一致
- 隔离性(Isolation):并发事务互不干扰
- 持久性(Durability):事务完成后结果永久保存
文章发布系统的事务应用
在我们的文章发布系统中,存在两种业务场景:
- 正式发布:所有操作(主表、文件、白名单)必须全部成功
- 草稿保存:主表必须成功,其他操作尽量完成但不强制
事务控制实现代码
@Transactional(rollbackFor = Exception.class)
public RestResponse publishArticle(PublishArticleRequest request) {
boolean isDraft = request.getIsDraft() == 2;
try {
// 设置默认值
request.setIsDelete(1);
// ...其他默认值设置
// 保存文章主表
int articleId = userArticlesMapper.publishArticle(request);
// 尝试保存附加数据
saveAdditionalData(request, articleId, isDraft);
return RestResponse.success(isDraft ? "草稿保存成功" : "发布成功");
} catch (Exception e) {
// 手动标记回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return RestResponse.fail(isDraft ? "草稿保存失败" : "发布失败");
}
}
private void saveAdditionalData(PublishArticleRequest request, int articleId, boolean isDraft) {
try {
// 1. 保存可见白名单
if (request.getWatchPermission() == 4) {
// ...构建白名单数据
userArticlesMapper.publishWatchPermissionnList(watchList);
}
// 2. 保存评价白名单
if (request.getEvaluatePermission() == 4) {
// ...构建评价白名单数据
userArticlesMapper.publishEvaluatePermissionnList(evaluateList);
}
// 3. 批量保存文件(性能优化关键)
if (request.getFiles() != null && !request.getFiles().isEmpty()) {
List<BaseUserArticlesFiles> fileList = request.getFiles().stream()
.map(fileUrl -> new BaseUserArticlesFiles(articleId, fileUrl))
.collect(Collectors.toList());
userArticlesMapper.batchInsertFiles(fileList);
}
} catch (Exception e) {
if (isDraft) {
// 草稿模式:只记录日志,不抛出异常
log.warn("草稿附加数据保存失败(不影响主表)", e);
} else {
// 发布模式:抛出异常触发事务回滚
throw e;
}
}
}
实体类优化支持批量操作
@Data
public class BaseUserArticlesFiles {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Integer userArticlesId;
private Integer isDelete = 1; // 默认值
private String fileUrl;
// 支持批量插入的构造方法
public BaseUserArticlesFiles(Integer userArticlesId, String fileUrl) {
this.userArticlesId = userArticlesId;
this.fileUrl = fileUrl;
}
}
MyBatis批量插入实现
<!-- 批量插入文件 -->
<insert id="batchInsertFiles" parameterType="list">
INSERT INTO base_user_articles_files
(user_articles_id, file_url, is_delete)
VALUES
<foreach collection="fileList" item="file" separator=",">
(#{file.userArticlesId}, #{file.fileUrl}, #{file.isDelete})
</foreach>
</insert>
事务回滚的工作原理
性能优化:批量操作
在文件保存场景中,使用批量插入显著提升性能:
// 普通单条插入(N+1问题)
for (String file : files) {
mapper.insertFile(new File(articleId, file));
}
// 批量插入(高效)
List<File> fileList = files.stream()
.map(file -> new File(articleId, file))
.collect(Collectors.toList());
mapper.batchInsertFiles(fileList);