官方推荐的@Transactional事务,我还是不建议使用


官方推荐的@Transactional事务,我还是不建议使用文章插图
事务管理在系统开发中是不可缺少的一部分 , Spring提供了很好事务管理机制 , 主要分为编程式事务和声明式事务两种 。
关于事务的基础知识 , 如什么是事务 , 数据库事务以及Spring事务的ACID、隔离级别、传播机制、行为等 , 就不在这篇文章中详细介绍了 。 默认大家都有一定的了解 。
本文 , 作者会先简单介绍下什么是声明式事务和编程式事务 , 再说一下为什么我不建议使用声明式事务 。
编程式事务基于底层的API , 如PlatformTransactionManager、TransactionDefinition 和 TransactionTemplate 等核心接口 , 开发者完全可以通过编程的方式来进行事务管理 。
编程式事务方式需要是开发者在代码中手动的管理事务的开启、提交、回滚等操作 。
public void test() {TransactionDefinition def = new DefaultTransactionDefinition();TransactionStatus status = transactionManager.getTransaction(def);try {// 事务操作// 事务提交transactionManager.commit(status);} catch (DataAccessException e) {// 事务提交transactionManager.rollback(status);throw e;}}如以上代码 , 开发者可以通过API自己控制事务 。
声明式事务声明式事务管理方法允许开发者配置的帮助下来管理事务 , 而不需要依赖底层API进行硬编码 。 开发者可以只使用注解或基于配置的 XML 来管理事务 。
@Transactionalpublic void test() {// 事务操作}如上 , 使用@Transactional即可给test方法增加事务控制 。
当然 , 上面的代码只是简化后的 , 想要使用事务还需要一些配置内容 。 这里就不详细阐述了 。
这两种事务 , 各自有各自的优缺点 , 那么 , 各自有哪些适合的场景呢?为什么有人会拒绝使用声明式事务呢?
声明式事务的优点通过上面的例子 , 其实我们可以很容易的看出来 , 声明式事务帮助我们节省了很多代码 , 他会自动帮我们进行事务的开启、提交以及回滚等操作 , 把程序员从事务管理中解放出来 。
声明式事务管理使用了 AOP 实现的 , 本质就是在目标方法执行前后进行拦截 。在目标方法执行前加入或创建一个事务 , 在执行方法执行后 , 根据实际情况选择提交或是回滚事务 。
使用这种方式 , 对代码没有侵入性 , 方法内只需要写业务逻辑就可以了 。
但是 , 声明式事务真的有这么好么?倒也不见得 。
声明式事务的粒度问题首先 , 声明式事务有一个局限 , 那就是他的最小粒度要作用在方法上 。
也就是说 , 如果想要给一部分代码块增加事务的话 , 那就需要把这个部分代码块单独独立出来作为一个方法 。
但是 , 正是因为这个粒度问题 , 本人并不建议过度的使用声明式事务 。
首先 , 因为声明式事务是通过注解的 , 有些时候还可以通过配置实现 , 这就会导致一个问题 , 那就是这个事务有可能被开发者忽略 。
事务被忽略了有什么问题呢?
首先 , 如果开发者没有注意到一个方法是被事务嵌套的 , 那么就可能会再方法中加入一些如RPC远程调用、消息发送、缓存更新、文件写入等操作 。
我们知道 , 这些操作如果被包在事务中 , 有两个问题:
1、这些操作自身是无法回滚的 , 这就会导致数据的不一致 。 可能RPC调用成功了 , 但是本地事务回滚了 , 可是PRC调用无法回滚了 。
2、在事务中有远程调用 , 就会拉长整个事务 。 那么久会导致本事务的数据库连接一直被占用 , 那么如果类似操作过多 , 就会导致数据库连接池耗尽 。
有些时候 , 即使没有在事务中进行远程操作 , 但是有些人还是可能会不经意的进行一些内存操作 , 如运算 。 或者如果遇到分库分表的情况 , 有可能不经意间进行跨库操作 。
但是如果是编程式事务的话 , 业务代码中就会清清楚楚看到什么地方开启事务 , 什么地方提交 , 什么时候回滚 。 这样有人改这段代码的时候 , 就会强制他考虑要加的代码是否应该方法事务内 。
有些人可能会说 , 已经有了声明式事务 , 但是写代码的人没注意 , 这能怪谁 。
话虽然是这么说 , 但是我们还是希望可以通过一些机制或者规范 , 降低这些问题发生的概率 。
比如建议大家使用编程式事务 , 而不是声明式事务 。 因为 , 作者工作这么多年来 , 发生过不止一次开发者没注意到声明式事务而导致的故障 。
因为有些时候 , 声明式事务确实不够明显 。
声明式事务用不对容易失效除了事务的粒度问题 , 还有一个问题那就是声明式事务虽然看上去帮我们简化了很多代码 , 但是一旦没用对 , 也很容易导致事务失效 。