欢迎光临
我们一直在努力

Spring中什么是编程式事务,和声明式事务有什么区别

编程式事务是通过代码显式管理事务的一种方式,开发者直接在代码中手动控制事务的开启、提交或回滚。与之相对的声明式事务(如使用 @Transactional 注解)则是通过配置自动管理事务边界。

在示例代码中的体现

你提供的代码中,transactionTemplate.execute(status -> { … }) 是典型的编程式事务实现:

return transactionTemplate.execute(status -> {
// 事务内的数据库操作
boolean update = blogService.lambdaUpdate()
.eq(Blog::getId, blogId)
.setSql("thumbCount = thumbCount + 1")
.update();

Thumb thumb = new Thumb();
thumb.setUserId(loginUser.getId());
thumb.setBlogId(blogId);
return update && this.save(thumb);
});

关键特点

  • 显式控制:
      • 通过 TransactionTemplate 的 execute() 方法明确界定事务范围。
      • 事务的提交或回滚由框架根据回调函数执行结果自动处理(无异常则提交,有异常则回滚)。
  • 灵活性:
      • 可在代码中根据条件动态决定是否开启事务。
      • 适合需要精细控制事务边界的场景,例如在循环中分批处理数据并提交事务。
  • 代码侵入性:
      • 事务管理代码与业务逻辑耦合,增加了代码量。
      • 声明式事务通过注解解耦,但灵活性较低。
  • 事务配置:
      • 可在 TransactionTemplate 中预设隔离级别、传播行为、超时时间等属性,例如:

    transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);

    代码中的事务逻辑

    • 原子性保证:更新博客点赞数(blogService.lambdaUpdate())和插入点赞记录(this.save(thumb))被包裹在同一事务中,确保两者同时成功或失败。
    • 异常回滚:若操作中抛出未捕获的异常(如 throw new RuntimeException("用户已经点赞")),事务会自动回滚。
    • 锁与事务的协作:外层 synchronized 锁防止用户并发请求,而事务确保数据库操作的原子性,两者共同保障数据一致性。

    适用场景

    • 需要根据运行时条件动态决定是否开启事务。
    • 涉及多个数据源或复杂事务链(需精细控制提交/回滚点)。
    • 与旧代码集成时,声明式事务无法满足需求。

    对比声明式事务

    特性

    编程式事务

    声明式事务

    控制方式

    手动编写代码管理事务

    通过注解或XML配置自动管理

    灵活性

    高(可精确控制事务边界)

    低(基于方法或类级别)

    代码侵入性

    高(事务代码与业务逻辑混合)

    低(通过AOP代理解耦)

    可读性

    较低(事务代码分散在业务逻辑中)

    较高(事务定义集中)

    总结

    编程式事务通过代码直接控制事务,适用于需要高度定制化事务管理的场景,而示例代码通过 TransactionTemplate 确保了点赞操作与计数更新的原子性,结合同步锁有效解决了并发问题。

    在项目中的体现

    更新博客点赞数量和插入点赞记录都成功的时候才会提交事务,

    同时防止用户的并发请求,也就是在短时间内,对一个博客进行多次点赞

    注意

    在这使用了本地锁,但是在分布式的场景下有可能会失效,如果在事务内加锁,也会导致本地锁失效

    在事务内加锁有可能失效的原理

  • 线程 A 获取锁-》执行数据库操作-》释放锁->事务提交
  • 线程 B 在锁被释放之后立即获取锁
  • 在默认的隔离级别可重复读下,由于在 A 提交事务之前 B 已经开启了事务,所以 B 此时只能读取到A 操作之前的数据,导致重复操作
  • 关键结论:

    必须让锁的作用域完全包裹事务的操作,保证其他线程获取锁的收,前序事务必然已经完成了事务的提交。也就是:

    先获取锁——》k 开启事务-》执行业务逻辑-》提交事务-》释放锁,保证点赞错做的原子性和一致性,不被其他线程干扰。

    点赞和取消点赞功能优化

    使用 redis 来减轻数据库的压力,但是 redis 中不存在数据,可能是因为缓存过期,或者是因为本来就没点赞,就必须要去数据库中查询,这样就会造成,先去 redis 查一次,再去 MySQL 中查询一次,不仅没有降低 mysql 的压力,反而多了一次查 reids 的过程

    这样可以采用冷热分离的策略,比如,我们认为最近一个月锁发布的博客是热数据,那么可以让 redis 中点赞记录的存在时间是帖子的发布时间再多加上一个月。如果点赞的时候这个博客的发布的时间还不超过一个月的时间,就查询 redis 校验是否已经点赞,如果发布时的时间超过了一个月,就通过 mysql 及逆行校验是否已经点赞。还可以引入布隆过滤器,在过滤器中进行调整。

    但是 redis 中是不知针对 hash节后的某个具体的属性进行设置过期时间的,但是可以调整 value 的实际结构,比如调整为:

    {
    "thumbId":xxx,
    "expireTime":xxx
    }

    然后在内存中判断是否过期。

    赞(0)
    未经允许不得转载:171主机测评 » Spring中什么是编程式事务,和声明式事务有什么区别
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址