spring事务

518 阅读13分钟

基于以下版本 Spring Version:4.x(4.3.7.RELEASE) 版本差异性对比:Spring 各版本针对事务处理不存在概念差异;spring2.5之后 支持注解式开发

事务

一 、事务管理

什么是事务?(what)

一组sql语句的集合

集合中有多条sql语句,可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功, 或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。

常被用来确保数据的一致性。

事务的特性 ACID

1 、原子性(atomicity)
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
2 、一致性(consistency)
事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。
因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态。
3 、隔离性(isolation)
一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
4 、持续性(durability)
也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。

为什么要使用事务?(why)

1 保证数据一致性 image-20210824153651274 系统收到的过程出现错误,导致B没收到10分...分去哪了? 所以需要保证三个操作要么同时执行,要么同时失败。

2 业务控制

业务操作是否需要执行到数据库? 谁来控制(提交/回滚)?

通过事务实现业务执行过程中对数据库写入的控制,包括提交/回滚、回滚条件、隔离级别、事务传播行为、延时控制等...

什么时候想到要用事务?

1、并不是写sql的时候才用到事务,事务通常会和业务结合,所以在业务层通常要考虑事务的使用;

2、当业务操作,涉及得到多个表,或者是多个sql语句的insert,update,delete;需要保证这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的;

3、当我的操作需要实现数据库写入控制的时候; 比如不需要写入数据库时,Junit单元测试; 还比如出现某具体异常,要求回滚; ...

事务解决方式?

不同数据库的事务方式不一样

jdbc访问数据库,处理事务  Connection conn ; conn.commit(); conn.rollback();
mybatis访问数据库,处理事务, SqlSession.commit();  SqlSession.rollback();
hibernate访问数据库,处理事务, Session.commit(); Session.rollback();

问题:不同数据库事务处理方式不一致

解决不足?

spring提供处理事务的统一接口, 能使用统一步骤,完成多种不同数据库访问技术的事务处理。

image-20210729193654191

事务管理器

事务管理器是一个接口和他的众多实现类
    接口:PlatformTransactionManager ,定义了事务重要方法 commitrollback
    实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
        mybatis访问数据库---spring创建好的是DataSourceTransactionManager
        hibernate访问数据库----spring创建的是HibernateTransactionManager
怎么使用:你需要告诉spring 你是用那种数据库的访问技术,怎么告诉spring呢?
    声明数据库访问技术对于的事务管理器实现类, 在spring的配置文件中使用<bean>声明就可以了
    <bean id=“xxx" class="...DataSourceTransactionManager"> 

二、Spring事务使用(注解)

目标:

1、事务使用步骤

2、事务重要理论

3、事务使用过程中的重要注意事项

Spring 事务实现方式

在Spring中通常可以通过以下两种方式来实现对事务的管理:

(1)使用Spring的事务注解管理事务

(2)使用AspectJ的AOP(Aspect Oriented Programming)配置管理事务

Spring 事务实现步骤(注解)

Step1 配置事务

主要配置以下部分:

1、配置事务管理器 2、开启注解驱动

如图:

image-20210802160852061

Step2 添加注解使用事务

Spring支持声明式事务,即使用注解来选择需要使用事务的方法,它使用@Transactional注解在方法上表明该方法需要事务支持 在此处需要注意的是,此@Transactional注解来自org.springframework.transaction.annotation包,而不是javax.transaction。

image-20210802161045314


Cases

case: 数据库:

image-20210802171943221

image-20210802171956639

Case:

事件:方法上添加事务@Transactional;发生异常;
结果:goods表不变,sale表不变
结论:添加注解,事务产生作用

goods表不变,sale表不变

image-20210802172316164

Case:

事件:方法上不添加事务@Transactional;发生异常;
结果:goods表没有变化,sales表添加了一行数据
结论:不添加注解,数据库发生了部分变化,发生数据不一致

image-20210802172742138


事务属性

image-20210722164925910

事务传播行为

事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A事务中的方法funcA()调用B事务中的方法funcB(),在调用执行期间B事务的执行情况,就称为事务传播行为。事务传播行为是以方法为作用对象。

**PROPAGATION_REQUIRED**(默认、最常用)
**PROPAGATION_REQUIRES_NEW**(常用)
**PROPAGATION_SUPPORTS**(常用)
PROPAGATION_MANDATORY 
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED

aPROPAGATION_REQUIRED

1、指定的方法必须在事务内执行;
2、若当前存在事务,就加入到当前事务中;
3、若当前没有事务,则创建一个新事务;
4、即优先考虑调用者;
5、这种传播行为是最常见的选择,也是**Spring默认的事务传播行为**

⚠️

1、funcA与funcB应该在不同类(service)中,如果在同一类中funcA调用funcB,无论funcB加不加事务注解(任意注解,包括PROPAGATION_REQUIRES_NEW...),funcB都不会新建事务;
2、serviceB使用spring对象注入,而不是new ServiceB生成;
3、原因:事务注解需要通过 代理对象 实现事务,内部方法调用使用的对象是**this**,而不是**proxy**;同理,new ServiceB生成的对象也不是代理对象。

image-20210809232215899

Cases

Case:

事件:funcA不加事务;
     funcB加@Transactional(propagation = Propagation.REQUIRED);
     funcA抛异常;
结果:goods表变化,sale表变化
结论:funcA内部调用funcB,funcB不受方法外部异常影响,funcB没有实现事务

image-20210810204930117

Case:

事件:funcA不加事务;
     funcB加@Transactional(propagation = Propagation.REQUIRED);
     funcB抛异常;
结果:goods表不变,sale表变化
结论:funcA内部调用funcB,funcB没有实现事务

image-20210810205549782

Case:

事件:(serviceA) funcA不加事务;
      (serviceB) funcB加@Transactional(propagation = Propagation.REQUIRED);
      funcB抛异常;
结果:goods表不变,sale不变
结论:funcA跨服务调用funcB,funcB可以实现事务

image-20210810211513207

Case:

事件:(serviceA) funcA加事务;
      (serviceB) funcB不加事务;
      funcB抛异常 (或者funcA抛异常);
结果:goods表不变,sale不变
结论:funcA调用funcB,外部方法使用事务,内部方法不使用事务,内部方法遵从外部方法

image-20210810212145867

bPROPAGATION_SUPPORTS

1、指定的方法不一定在事务内执行;
2、若当前存在事务,就加入到当前事务中;
3、若当前没有事务,则按普通方法执行;
4、即完全跟从调用者;

指定的方法加入当前事务,但若当前没有事务,也可以以非事务方式执行。

2.png

cPROPAGATION_REQUIRES_NEW

1、指定的方法一定在新事务内执行;
2、无论当前是否存在事务,都创建新事务;
3、若当前存在事务,就将当前事务挂起,直到新事务执行完毕;
4、即完全不跟从调用者;

1.png

事务隔离级别

这些常量均是以ISOLATION_开头。即形如ISOLATION_XXX。

➢ DEFAULT:采用DB默认的事务隔离级别。MySql的默认为REPEATABLE_READ;Oracle默认为READ_COMMITTED。

➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。

➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。

➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读。

➢ SERIALIZABLE:串行化。不存在并发问题。

事务并发问题:

image-20210802215738909

事务并发问题实际上是三个不同层级上的问题:
1、脏读:读取到未提交的数据;通过添加行级查询锁解决;
2、不可重读:多次读取到的数据不一致;通过行级事务锁解决;
3、幻读:多次读取到的数据集不一致;通过添加表级事务锁解决;
锁级别越高,并发能力越差;

事务超时时间

常量TIMEOUT_DEFAULT定义了事务底层默认的超时时限,sql语句的执行时长。

注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。

数据只读

image-20210802220959101

事务回滚类型声明

Spring事务的默认回滚方式是:发生运行时异常(UncheckedError)和error时回滚,发生Checked(编译) Exception时提交。

不过,对于受查异常,程序员也可以手工设置其回滚方式

image-20210803102658280

Throwable类是Java语言中所有错误或异常的超类。只有当对象是此类(或其子类之一)的实例时,才能通过Java虚拟机或者Java的throw语句抛出。
​
Error是程序在运行过程中出现的无法处理的错误,比如OutOfMemoryError、ThreadDeath、NoSuchMethodError等。当这些错误发生时,程序是无法处理(捕获或抛出)的,JVM一般会终止线程。
​
程序在编译和运行时出现的另一类错误称之为异常,它是JVM通知程序员的一种方式。通过这种方式,让程序员知道已经或可能出现错误,要求程序员对其进行处理。
​
异常分为运行时异常与受查异常。
​
运行时异常,是RuntimeException类或其子类,即只有在运行时才出现的异常。如,NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 等均属于运行时异常。这些异常由JVM抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代码编写足够仔细,程序足够健壮,运行时异常是可以避免的。
​
受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理,则无法通过编译。如SQLException,ClassNotFoundException,IOException等都属于受查异常。
​
RuntimeException及其子类以外的异常,均属于受查异常。当然,用户自定义的Exception的子类,即用户自定义的异常也属受查异常。程序员在定义异常时,只要未明确声明定义的为RuntimeException的子类,那么定义的就是受查异常。

Cases

Case:

事件:funcA调用funcB,funcB添加@Transactional;抛 NullPointerException 错误
结果:goods表无变化,sale表无变化
结论:发生NullPointerException(UnCheckeException),事务回滚

image-20210816174113422

Case:

事件:funcA调用funcB,funcB添加@Transactional;抛 IOException 错误
结果:goods表无变化,sale表增加一条记录
结论:发生IOException(CheckeException),事务提交

3.png

Case:

事件:funcA调用funcB,funcB添加@Transactional(rollbackFor = Exception.class);抛 IOException 错误
结果:goods表无变化,sale无变化
结论:发生IOException(CheckeException),事务回滚; “rollbackFor = Exception.class” 常用

4.png

一般类加事务

@Transactional不仅可以注解在方法上,也可以注解在类上;
当注解在类上的时候意味着此类的所有public方法都是开启事务的;
如果类级别和方法级别同时使用了@Transactional注解,则使用在类级别的注解会重载方法级别的注解(即取决于方法上的注解)。

Cases

Case:

事件:@Transactional加在类上
结果:goods表、sale表都没变化
结论:事务加在类上,public方法都具有事务

image-20210802173954399

Case:

事件:类上定义事务回滚;方法上定义事务不回滚;
结果:最终数据库数据发生了变化,没有发生回滚
结论:类和方法上都有事务时,遵从方法上的事务定义

image-20210802174608210

抽象类加事务

1、抽象类上加事务,继承的子类都具有事务
2、抽象类中方法加事务,重载方法具有事务
Cases

Case:

事件:抽象类不加事务,方法不加事务
结果:goods表无变化,sale增加一条记录
结论:没有事务

5.png

case:

事件:抽象类加事务,方法不加事务
结果:goods表无变化,sale表没有变化
结论:事务回滚;抽象类上加事务,子类都具有事务

6.png

Case:

事件:抽象类中的方法加事务,子类重载方法不加事务
结果:goods表无变化,sale表没有变化
结论:事务回滚;抽象类的方法上加事务,重载方法具有了事务

7.png

接口加事务

1、接口上加事务,实现类都具有事务
2、接口中方法加事务,实现类的相应方法具有事务
Cases

Case:

事件:接口上加事务
结果:goods表无变化,sale表没有变化
结论:事务回滚;接口加事务,实现类具有了事务

8.png

Case:

事件:接口中方法加事务
结果:goods表无变化,sale表没有变化
结论:事务回滚;接口中方法加事务,实现类的对应方法具有了事务

9.png

测试方法加事务

在单元测试时,只要测试类开启了事务,所有的操作,无论抛出什么类型的异常,无论是否存在事务的传播行为,无论被调用的类上是否有@Transactional注解,还有无论是否捕获异常,都会回滚事务。

在单元测试时,测试类不开启事务,如果被调用的类上有@Transactional注解(开启事务),此时若被调用类的被调用方法发生不可查异常(RuntimeException及其子类或者error),则事务会回滚,其他操作不会回滚。 解释:虽然单元测试没有开启事务,但是被调用的类本身存在事务,所以会出现事务回滚的情况。

在单元测试时,测试类不开启事务,如果被调用的类不开启事务(即类上没有@Transactional注解),被调用类中的方法不抛出异常、抛出不可查异常RuntimeException及抛出可查异常IOException,这三种情况都不会回滚事务。解释:因为都不存在事务,自然不会有事务回滚。

因此,我们最好在测试类上或涉及事务的测试方法上开启事务,导入 javax.transaction.Transactional包或者org.springframework.transaction.annotation.Transactional的@Transactional注解,避免污染数据库。

通过@Transactional, @Rollback 注解来实现, @Rollback默认是true,事务会回滚,可以不写, false时事务不会回滚,数据会写到数据库中。

一般的场景下都是要对事务进行回滚的。要支持回滚,只需要增加一个@Transactional注解即可;

@Test
@Transactional

单独的@Transactional是回滚事务,在添加@Transactional的情况下如果要提交事务,只需要增加@Rollback(false);@Commit

@Test
@Transactional
@Rollback(value = false)

@Transaction注解失效的原因

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

参考:www.cnblogs.com/xhq1024/p/1…

三、使用AspectJAOP配置管理事务

使用XML配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。其用法很简单,只需将前面代码中关于事务代理的配置删除,再替换为如下内容即可。

AOP事务实现步骤

Step1 pom依赖

增加aspects的依赖

image-20210824141915448

Step2 配置事务管理器

image-20210824142031907

Step3 配置事务通知

image-20210824143512397

Step4 配置增强器

image-20210824143449156image-20210824143449156