编程式事务&声明式事务

编程式事务&声明式事务

事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种。

一、编程式事务

基于底层的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 来管理事务。

@Transactional
public void test() {
     // 事务操作  
}

如上,使用@Transactional即可给test方法增加事务控制。
当然,上面的代码只是简化后的,想要使用事务还需要一些配置内容。

2.1、声明式事务的约定流程

首先Spring通过事务管理器(PlatformTransactionManager的子类)创建事务,与此同时会把事务定义中的隔离级别、超时时间等属性根据配置内容往事务上设置。而根据传播行为配置采取一种特定的策略,后面会谈到传播行为的使用问题,这是Spring根据配置完成的内容,你只需要配置,无须编码。然后,启动开发者提供的业务代码,我们知道Spring会通过反射的方式调度开发者的业务代码,但是反射的结果可能是正常返回或者产生异常返回,那么它给的约定是只要发生异常,并且符合事务定义类回滚条件的,Spring就会将数据库事务回滚,否则将数据库事务提交,这也是Spring自己完成的。

编程式事务每次实现都要单独实现,但业务量大且功能复杂时,使用编程性事务无疑是痛苦的;而声明式事务不同,声明式事务属于非侵入性,不会影响业务逻辑的实现,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中;
非侵入式的开发方式,声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持;
是侵入性事务管理,直接使用底层的PlatformTransactionManager、使用TransactionTemplate(Spring推荐使用);
编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法;

三、总结

3.1、声明式事务的优点

声明式事务帮助我们节省了很多代码,他会自动帮我们进行事务的开启、提交以及回滚等操作,把程序员从事务管理中解放出来。
声明式事务管理使用了 AOP 实现的,本质就是在目标方法执行前后进行拦截。 在目标方法执行前加入或创建一个事务,在执行方法执行后,根据实际情况选择提交或是回滚事务。
使用这种方式,对代码没有侵入性,方法内只需要写业务逻辑就可以了。

3.2、声明式事务的粒度问题

声明式事务有一个局限,那就是他的最小粒度要作用在方法上。
也就是说,如果想要给一部分代码块增加事务的话,那就需要把这个部分代码块单独独立出来作为一个方法。
但是,正是因为这个粒度问题,并不建议过度的使用声明式事务。
首先,因为声明式事务是通过注解的,有些时候还可以通过配置实现,这就会导致一个问题,那就是这个事务有可能被开发者忽略。
事务被忽略了有什么问题呢?
首先,如果开发者没有注意到一个方法是被事务嵌套的,那么就可能会再方法中加入一些如RPC远程调用、消息发送、缓存更新、文件写入等操作。
我们知道,这些操作如果被包在事务中,有两个问题:

  • 1、这些操作自身是无法回滚的,这就会导致数据的不一致。可能RPC调用成功了,但是本地事务回滚了,可是PRC调用无法回滚了。
  • 2、在事务中有远程调用,就会拉长整个事务。那么久会导致本事务的数据库连接一直被占用,那么如果类似操作过多,就会导致数据库连接池耗尽。

有些时候,即使没有在事务中进行远程操作,但是有些人还是可能会不经意的进行一些内存操作,如运算。或者如果遇到分库分表的情况,有可能不经意间进行跨库操作。
但是如果是编程式事务的话,业务代码中就会清清楚楚看到什么地方开启事务,什么地方提交,什么时候回滚。这样有人改这段代码的时候,就会强制他考虑要加的代码是否应该方法事务内。

3.3、声明式事务用不对容易失效

除了事务的粒度问题,还有一个问题那就是声明式事务虽然看上去帮我们简化了很多代码,但是一旦没用对,也很容易导致事务失效。
如以下几种场景就可能导致声明式事务失效:

  • 1、@Transactional 应用在非 public 修饰的方法上。
  • 2、@Transactional 注解属性 propagation 设置错误。
  • 3、@Transactional 注解属性 rollbackFor 设置错误。
  • 4、同一个类中方法调用,导致@Transactional失效。
  • 5、异常被catch捕获导致@Transactional失效。
  • 6、数据库引擎不支持事务。

以上几个问题,如果使用编程式事务的话,很多都是可以避免的。
因为Spring的事务是基于AOP实现的,但是在代码中,有时候我们会有很多切面,不同的切面可能会来处理不同的事情,多个切面之间可能会有相互影响。

四、事务特性

  • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用;

  • 一致性(Consistency):一旦事务完成(不管是成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏;

  • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏;

  • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中;

评论

暂无

添加新评论