事务模板 VS 声明式事务

本文涉及的产品
数据可视化DataV,5个大屏 1个月
可视分析地图(DataV-Atlas),3 个项目,100M 存储空间
简介: 本文比较声明式事务与事务模板的优缺点。

事务模板 VS声明式事务

 

一、  案例

我们首先看几个简单的案例。

1.  声明式事务示例一

   @Transactional
   public voidaddData(long orderId,Object orderDetailDTO){
       //查询操作
       OrderDO orderDO = orderManager.getById(orderId);
        // 组装数据操作
       OrderDetailDO orderDetailDO= OrderDetailHelper.create(orderDO, orderDetailDTO);
       
       //db写操作
       orderManager.update(orderDO);
       orderDetailManager.insert(orderDetailDO);
   }



这是一个数据组装和db写操作都放在同一个事务中的示例。

 

2.     声明式事务示例二

public voidaddDataV2(long orderId,Object orderDetailDTO){
       //查询操作
       OrderDO orderDO = orderManager.getById(orderId);
        // 组装数据操作
       OrderDetailDO orderDetailDO= OrderDetailHelper.create(orderDO, orderDetailDTO);
       this.addDataV2DoDB(orderDO, orderDetailDO);
   }
   
    @Transactional
   public voidaddDataV2DoDB(OrderDO orderDO, OrderDetailDOorderDetailDO){
      //db写操作
       orderManager.update(orderDO);
       orderDetailManager.insert(orderDetailDO);
   }

这是一个数据封装和db写操作分在两个方法中的示例,从代码上可以看到事务时间更短。

 

3.     声明式事务示例三

    @Transactional
   public voidaddDataV3(long orderId,Object orderDetailDTO){
       //查询操作
       OrderDO orderDO = orderManager.getById(orderId);
        // 组装数据操作
       OrderDetailDO orderDetailDO= OrderDetailHelper.create(orderDO, orderDetailDTO);
       
       //db写操作
       orderManager.update(orderDO);
       orderDetailManager.insert(orderDetailDO);
       
       orderManager.callback(orderDO);
   }

和示例一唯一的差别就是事务执行完毕以后加了一个回调操作。

 

 

4.     事务模板示例一

  public boolean addData(finalOrderDO orderDO, finalOrderDetailDO orderDetailDO){
        Boolean executeResult = transactionTemplate.execute(new TransactionCallback<Boolean>() {
           @Override
           public BooleandoInTransaction(TransactionStatus status) {
               try {
                   //插入order
                   long orderId = orderDao.insert(orderDO);
                   if (orderId <= 0 ) {
                       status.setRollbackOnly(); 
                       returnfalse;
                   }
                   //插入order detail
                   long orderDetailId = orderDetailDao.insert(orderDetailDO);
                   if (orderDetailId <= 0 ) {
                       status.setRollbackOnly(); 
                       returnfalse;
                   }
               } catch (Exception e) {
                   status.setRollbackOnly();
                   return false;
               }
               return true;
           }
        });
       return executeResult == null ? false : executeResult ;
    }



这是一个使用TransactionTemplate实现事务的示例。可以看到,需要我们手动控制事务的回滚,代码逻辑比较复杂。

 

 

 

二、  案例分析

看了上面的四个示例,估计很多小伙伴很容易得出事务模板是最不应该采用的事务方式,代码繁琐。但我这边给出的结论是,应该放弃声明式事务,使用事务模板。接下来分析一下原因。

1.     声明式事务

1)       示例代码分析

a)   “声明式事务示例一”的问题:事务范围过大,事务时间越长,占用的资源越多,性能越差,事务执行失败回滚的概率越高。

b)   “声明式事务示例二”的问题:事务不会生效。声明式事务依赖于Transactional注解和TransactionInterceptor拦截器。拦截器生效的前提是调用者是代理,而不是当前类本身。

c)   “声明式事务示例三”的问题:“声明式事务示例一”的所有问题,在此示例中都有;另外,业务层可能会进行RPC、HTTP请求,如果多次rpc中部分请求失败,事务会滚也不能保证数据一致性。

 

2)       声明式事务普遍性问题

声明式事务最大的优势是spring做了过多的封装,所以使用起来很简单,代码很简化;但这在使用中也经常会让我们的代码埋下一堆的坑。下面列举平常编码时会遇到的几个例子。

a)   绝大多数我们通过异常来回滚事务,但是如果底层模块捕获异常导致上层无法捕获异常,或者底层接口执行失败时不抛出异常(如update、delete时返回影响的记录数而不抛出异常),此时事务中的代码执行结果将会和我们的预期不一致。之前和不少人沟通过这个问题,但是不少人告诉我通过统一代码规范,来保证上层可以获取到异常,达到事务可以正常回滚的目的,但是从代码上看到的结果基本上都没有做到这一点。

b)   就我这么多年接触的平时喜欢使用@Transactional来做事务的开发同学而言,绝大多数都不清楚事务的5种隔离级别与7种传播属性他们是如何相互影响的。

c)   使用声明式事务的同学一般会将业务逻辑依平铺的方式写下来,举个例子来说,如果某个业务会在3个表中各生成一条记录,那写出来的程序很容易就出现生成一条记录插入一条记录的情况,然后在最外层包一个大事务。这容易造成逻辑不清晰,耦合严重,性能低下的问题,而且很容易在这个事务中写rpc、http调用,即出现“声明式事务示例三”的问题。

 

注意:上面列举的这些问题,在测试期间都不易发现问题,但是出问题后定位起来也比较麻烦。

 

 

2.     事务模板

1)       示例代码分析

从示例中可以看到使用事务模板的方式代码不够简化,但这种不够简化的代码正是我们验证每一步执行结果,判断和我们的预期是否一致引起的,这正是解决声明式事务不可靠(通过异常回滚事务)的关键,为此我们多写几行代码,我觉得是非常值得的事情。

保证数据正确性可以将我们从频繁定位线上问题、修复线上数据的泥潭中解救出来至关重要的一点。

事务模板不能解决事务中调用RPC、HTTP的问题,但是使用事务模板这种方式要求我们提前查询、计算好所有数据,然后放在一个事务模板中,一次更新掉,所以使用事务模板的性能会更好,因为提前已经将数据计算好了,所以事务会尽可能的短,性能会更好,会极大的避免事务中出现RPC、HTTP调用

我一向的观点是:数据是业务的生命,如果数据准确性不能保证,我们提供再多的功能也是没有什么意义的。如果实现成本差别不是非常大,应该尽一切可能保证数据可靠性。

 

 

另外,在传统的单系统的业务中,因为所有的表都在同一个库中,而且基本没有RPC、HTTP调用,所以使用声明式事务很多时候不会有问题,但是在SOA架构中,则绝大多数场景都会有问题。

 

 

三、  传播属性与隔离级别

1.     传播属性

REQUIRED

支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行。

MANDATORY

支持当前事务,如果当前没有事务,就抛出异常。

REQUIRES_NEW

新建事务,如果当前存在事务,把当前事务挂起。

NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

NEVER

以非事务方式执行,如果当前存在事务,则抛出异常。

NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作

REQUIRED为默认的传播属性。

 

2.     隔离级别

1)       名词解释

a)  脏读:

针对未提交数据。

脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。

b)  不可重复读:

针对其他提交前后,读取数据本身的对比。

是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。(即不能读到相同的数据内容)

c)  幻读

针对其他提交前后,读取数据条数的对比。

幻读是指同样一笔查询在整个事务过程中多次执行后,查询所得的结果集是不一样的。幻读针对的是多笔记录。在Read Uncommitted隔离级别下, 不管事务2的插入操作是否提交,事务1在插入操作之前和之后执行相同的查询,取得的结果集是不同的,所以,ReadUncommitted同样无法避免幻读的问题。

 

2)       隔离级别

DEFAULT

使用数据库默认的事务隔离级别

READ_UNCOMMITTED

这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。

READ_COMMITTED

保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。

REPEATABLE_READ

这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)

SERIALIZABLE

这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。

 

3)       影响

隔离级别 脏读 不可重复读 幻读
READ_UNCOMMITTED
READ_COMMITTED ×
REPEATABLE_READ × ×
SERIALIZABLE × × ×

√: 可能出    ×:不会出


相关实践学习
DataV Board用户界面概览
本实验带领用户熟悉DataV Board这款可视化产品的用户界面
阿里云实时数仓实战 - 项目介绍及架构设计
课程简介 1)学习搭建一个数据仓库的过程,理解数据在整个数仓架构的从采集、存储、计算、输出、展示的整个业务流程。 2)整个数仓体系完全搭建在阿里云架构上,理解并学会运用各个服务组件,了解各个组件之间如何配合联动。 3&nbsp;)前置知识要求 &nbsp; 课程大纲 第一章&nbsp;了解数据仓库概念 初步了解数据仓库是干什么的 第二章&nbsp;按照企业开发的标准去搭建一个数据仓库 数据仓库的需求是什么 架构 怎么选型怎么购买服务器 第三章&nbsp;数据生成模块 用户形成数据的一个准备 按照企业的标准,准备了十一张用户行为表 方便使用 第四章&nbsp;采集模块的搭建 购买阿里云服务器 安装 JDK 安装 Flume 第五章&nbsp;用户行为数据仓库 严格按照企业的标准开发 第六章&nbsp;搭建业务数仓理论基础和对表的分类同步 第七章&nbsp;业务数仓的搭建&nbsp; 业务行为数仓效果图&nbsp;&nbsp;
相关文章
|
数据库
通过基于注解的声明式事务实现事务功能~2
通过基于注解的声明式事务实现事务功能~
|
4月前
|
Java Spring
spring 事务控制 设置手动回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
spring 事务控制 设置手动回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
104 0
|
6月前
|
Java 数据库连接 数据库
Spring事务简介,事务角色,事务属性
Spring事务简介,事务角色,事务属性
55 2
|
6月前
|
Java 数据库 开发者
|
XML Java 关系型数据库
通过基于注解的声明式事务实现事务功能~1
通过基于注解的声明式事务实现事务功能~
通过基于注解的声明式事务实现事务功能~1
|
Java 数据库连接 API
spring事务管理
spring事务管理
69 0
|
监控 Java 数据库连接
Spring事务管理
Spring事务管理
|
Java 数据库连接 数据库
Spring 事务【Spring事务的定义与基本实现】
Spring 事务【Spring事务的定义与基本实现】
Spring 事务【Spring事务的定义与基本实现】
|
XML Java 关系型数据库
Spring的事务操作一站式学习【事务的概念、注解声明式事务管理、声明式事务管理参数配置、XML声明式事务管理、完全注解声明式事务管理】(超详细)
Spring的事务操作一站式学习【事务的概念、注解声明式事务管理、声明式事务管理参数配置、XML声明式事务管理、完全注解声明式事务管理】(超详细)
Spring的事务操作一站式学习【事务的概念、注解声明式事务管理、声明式事务管理参数配置、XML声明式事务管理、完全注解声明式事务管理】(超详细)
|
SQL Java 关系型数据库
Spring事务管理(二)分布式事务管理之JTA与链式事务
Spring事务管理(二)分布式事务管理之JTA与链式事务
Spring事务管理(二)分布式事务管理之JTA与链式事务