@Transactional使用以及失效原因

  • Post author:
  • Post category:其他



目录


一、事务定义


二、springboot中@Transactional的使用


三、失效原因


一、事务定义

先了解一下什么是DDL和DML

DDL:操作数据库、表、列等(这些对象进行操作),使用的关键字:CREATE、 ALTER、 DROP。

DML:是对表中的数据进行增、删、改的操作。使用的关键字:INSERT 、UPDATE、 DELETE。

事务简单来讲就是由多个DML操作组成一个业务,例如减少商品库存和添加订单共同构成一个业务,其中一个操作出现问题就将所有操作进行回滚。


事务四大特征(ACID)

  • 原子性(A):事务是最小单位,不可再分

  • 一致性(C):事务要求所有的DML语句操作的时候,必须保证同时成功或者同时失败

  • 隔离性(I):事务A和事务B之间具有隔离性

  • 持久性(D):事务结束后将数据保存到硬盘


事务隔离性

重点了解一下事务的隔离性

以下几个概念是事务隔离级别要实际解决的问题,所以需要搞清楚都是什么意思。


脏读

脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读。

可重复读

可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新(UPDATE)操作。


不可重复读

对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。


幻读

幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。


事务隔离级别

SQL 标准定义了四种隔离级别,MySQL 全都支持。这四种隔离级别分别是:

读未提交(READ UNCOMMITTED):所有情况都有可能

读提交 (READ COMMITTED):不可能脏读

可重复读 (REPEATABLE READ):不可能脏读和不可重复读,

是Mysql的默认隔离级别

串行化 (SERIALIZABLE):避免所有情况


当然,隔离强度越强也意味着性能越差。

实际上MySQL 事务隔离是依靠锁来实现的

除了读未提交

,下面挨个简单介绍一下隔离级别


读未提交:

按字面意思来理解就是可以读到其他事务未提交的数据,但不能保证数据一定时正确的,上面提到读未提交隔离级别是不加锁的,也就没有上锁和解锁的过程,所有性能特别好,但基本上就不存在解决问题的能力。


读提交:

读提交就是读取其他事务已提交的数据,这样可以保证不出现脏读,但如果事务A在修改库存时,事务B查询到库存和在事务A提交后事务B查询到库存不一致,就是事务的不同时刻同样的查询条件,查询出来的记录内容是不一样的。


可重复读:

事务不会读到其他事务对已有数据的修改,即使其他事务已提交,比如事务A读到库存为100,事务B修改为200后,事务A仍会读到100,但是,对于其他事务新插入的数据是可以读到的,这也就引发了幻读问题。(Mysql已在该隔离级别下解决幻读问题)


串行化:

它将事务的执行变为顺序执行,就变成了个单线程,后一个事务的执行必须等待前一个事务结束。很暴力,也很有效,但性能损失也很严重。

具体原理参考

https://baijiahao.baidu.com/s?id=1662096005584873447&wfr=spider&for=pc

二、springboot中@Transactional的使用

直接在方法和类上加入@Transactional注解即可

对于注解的属性如下:


propagation:

设置事务的传播行为。

  • REQUIRED(0) 使用当前的事务,如果当前没有事务,则自己新建一个事务,子方法必须运行在一个事务中的,如果当前存在事务,则加入这个事务,成为一个整体。

  • SUPPORTS(1) 如果当前有事务,则使用事务;如果当前没有事务,则不使用事务。

  • MANDATORY(2) 该传播属性强制必须存在一个事务,如果不存在,则抛出异常。

  • REQUIRES_NEW(3) 如果当前有事务,则挂起该事务,并且自己创建一个新的事务给自己使用。

  • NOT_SUPPORTED(4) 如果当前有事务,则把事务挂起,自己不使用事务取运行数据库操作。

  • NEVER(5) 如果当前有事务存在,则抛出异常。

  • NESTED(6) 如果它报错了,不会使主事务回滚,但主事务报错,它会跟着回滚,如果当前有事务,则开启子事务(嵌套事务),嵌套事务是独立提交或者回滚,如果当前有事务,则开启子事务(嵌套事务),嵌套事务是独立提交或者回滚。如果当前没有事务,则同REQUIRED,但是如果事务提交,会携带子事务起提交。如果主事务回滚,则子事务会一起回滚。相反,子事务异常,则父事务可以回滚或不回滚。


isolation:

设置数据库的事务隔离级别,上文有详细介绍,通常使用数据库的默认隔离级别即可


timeout:

设置事务的超时时间(秒),默认值为-1表示永不超时


readOnly:

设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。


rollbackFor:

设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:

@Transactional(rollbackFor==Exception.class)

@Transactional(rollbackFor={Exception.class, IOException.class})


rollbackForClassName:

设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:

@Transactional(rollbackForClassName=”Exception”)

@Transactional(rollbackForClassName={“Exception”,”IOException”})


noRollBackFor:

设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:

@Transactional(noRollbackFor=Exception.class)

@Transactional(noRollbackFor={Exception.class, IOException.class})


noRollBackForClassName:

该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:

@Transactional(noRollbackForClassName=”Exception”)

@Transactional(noRollbackForClassName={“Exception”,”IOException”})

三、失效原因

1、存在try{}catch

 @Transactional(rollbackFor = Exception.class, transactionManager = "appTransactionManager")
  public ResponseEntity<Object> updateTableDataByExcel(String tableName,Worksheet worksheet, SourceConfig sourceConfig) throws Exception {
      try {
          tableMappingService.createMappingField(tableName);
      } catch (Exception e) {
          e.printStackTrace();
      }
  } 

解决方案:将try{}catch操作放到上层Controller或是在直接抛出,总而言之@Transactional和try{}catch最好不要同时存在

2、事务代码出现DDL操作

事务只对DML有效,如果中间有个DDL操作会使事务失效

start transaction;
update goods set name ='888888' WHERE id =10000;//修改成功
update goods set name ='999999' WHERE id =10001;//修改成功
CREATE TABLE `ooo` (                            //DDL语句
  `number` int(11) DEFAULT NULL COMMENT 'number',
  `ddddd` int(11) DEFAULT NULL COMMENT 'ddddd',
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='000';
update goods set 1111 =1/0 WHERE id =10001;//错误语句
COMMIT;

解决方案:将DDL和DML分离到service,事务添加到service方法上,由controller调用

3、数据库引擎要支持事务,如果是MySQL,注意表要使用支持事务的引擎,比如innodb

4、方法必须是public

5、

同一个类

中A方法调用B方法,都需要@Transactional修饰



版权声明:本文为qq_40277158原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。