Spring中的事务隔离级别和传播机制

一、什么是事务?

  在这之前,我们先来回顾一下什么是事务。

  设想一个场景,有一个表叫table,里面有三个字段分别为字段1、字段2。我们现在做了一个程序——当字段1的值减少1,那么字段2的值就增加1。这时候,我们就需要两句sql语句去执行这个操作:sql1=“让字段1减少1”,sql2=“让字段2增加1”。这时候,先执行sql1,再执行sql2就完成了我们的需求。但是,有时候事情总不会那么顺利,如果sql1执行之后,程序突然出错了嗝屁了,那么问题就很尴尬了,字段1的值减少了1,但是字段2却没有增加。那么,如何应对这种情况呢?这时候,就需要用到事务了。

  事务的主要作用可以理解为控制sql语句的执行,一个事务中的更新操作,要么都执行,要么都不执行,这个特征也叫做事务的原子性。一般情况下,只有在事务执行完毕,调用commit的时候,才会对数据库中的数据进行修改(如果表中的主键是自增长的话,就算不执行commit,也会发生主键值增加的情况)。

 

事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

    原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
    一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
    隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
    持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

二、什么是事务的隔离?

  现在,我们继续设想一个场景,一个程序有两个事务分别为事务1,事务2,前面提到了,只有当事务commit的时候,表中的数据才会改变。如果现在,事务1已经对一个表的某一行进行了操作,但是由于事务1没有commit,所以这一列的数据并没有改变,但是!尽管事务1没有提交,但是它也没有回滚,所以程序会认为此时该行的数据是已经修改过的数据了。这时候,如果事务2再去读这行数据,读到的是没有被修改过的数据,这时候,问题就出现了,事务1提供给程序的数据是新数据,然而事务2读到的信息却是旧数据。这非常致命,稍有不慎就会引起所有操作的回滚。 而这种情况,叫脏读

  除了脏读之外,使用事务的时候还有其他的问题,比如:

  •    更新丢失

      两个事务都同时更新一行数据,一个事务对数据的更新把另一个事务对数据的更新覆盖了。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。
  •            不可重复读

      一个事务对同一行数据重复读取两次,但是却得到了不同的结果。

      包括以下情况

      • 虚读:事务1查询了某数据之后,事务2对该数据进行了修改,事务1再去读取该数据时,就会发现两次数据的数据结果不一样
      • 幻读:事务1查询了某些数据之后,事务2对这些数据进行了插入或者删除操作,事务1再去查询这些数据时,就会发现第二次读取时的结果集和第一次查询的不一样。

  其实上面的情况和Java中的多线程操作一个资源时会发生的情况一样。为了避免上面的情况发生,我们需要把每个事务隔离起来,人为的规定好两个事务的相互影响程度。甚至让两个事务完全隔离,不让他们互相影响,这就叫事务的隔离。规定事务之间的相互影响程度,就是规定事务的隔离级别,不同的隔离级别对事务的处理不同。

  在标准SQL规范中,定义了4个事务隔离级别。

 

  • 未授权读取(Read Uncommitted)

    • 也称为读未提交:允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,就不让别的事务数据同时进行写操作,但是允许其他事务读此行程序
  • 授权读取(Read Committed)

    • 也成为读提交:允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”
  • 可重复读取(Repeatable Read)

    • 禁止不可重复读取和脏读取,但是有时可能出现幻读数据。读取数据的事务会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
  • 序列化(Serializable)

    • 提供严格的事务隔离。它要求事务序列化执行,事务只能一个接一个的执行,不能并发执行。

 

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

 

三、什么是事务的传播级别?

  继续设想一个场景,一个程序中有两个method,分别为method1和method2。当我们为方法method1配置了事务,当method1执行的时候,就会开启一个事务,只有等到method1执行完毕,这个事务才会被提交。这时候,如果method1调用了method2,method2就会有几种选择:1、加入method1创建的事务、2、自己新开一个事务嵌套着method1的事务运行、2、不以事务的形式执行,并且还要把method1的事务暂停,等到method2执行完毕再继续method1的事务等等。。 这个method2做出的选择,就是事务的传播级别(行为),我们可以为method2配置传播级别,让他在遇到这种情况时,知道怎么选择

 

在TransactionDefinition接口中定义了七个事务传播行为。

 

 

PROPAGATION_REQUIRED 如果存在一个事务,则加入当前事务。如果没有事务则开启一个新的事务。

PROPAGATION_SUPPORTS 如果存在一个事务,加入当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。

PROPAGATION_MANDATORY 如果已经存在一个事务,加入当前事务。如果没有一个活动的事务,则抛出异常。

PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。

PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常

PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行

四、如何在spring中配置事务?

  spring中对事务的控制主要依赖AOP

  1、使用xml的方式配置事务

    ①导入依赖

1         <dependency>
2             <groupId>org.springframework</groupId>
3             <artifactId>spring-tx</artifactId>
4             <version>5.0.2.RELEASE</version>
5         </dependency>

    ②将主配置文件中的头目录增加支持事务的声明

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

    ③配置事务管理器

 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
 </bean>

    ④配置事务的通知以及需要用到事务的方法

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--
isolation="" 指定事务的隔离级别,默认是数据库中设定的隔离级别。如果该属性的设定值和数据库的设定值不一样,则以该属性值为准
propagation="" 指定事务的传播行为,默认是REQUIRED 
read-only="" 用于指定事务是否只读 默认值是false
rollback-for="" 用于指定一个异常,当产生该异常时,事务回滚。产生其他事务时,不回滚。 没有默认值
no-rollback-for="" 用于指定一个异常,当产生该异常时,事务不回滚
timeout="" 用于指定事务的超时时间,单位是秒
-->
<tx:attributes>
<tx:method name="*" isolation="DEFAULT"/> <-- 这里就是配置要用到事务的方法和事务的隔离级别,传播行为等。name属性可以使用*通配符-->
</tx:attributes>
</tx:advice>

    ⑤配置切入点表达式以及建立切入点表达式和事务通知的对应关系

    <aop:config>
        <aop:pointcut id="pt1" expression="execution(* com.sunlin.service.impl.*.*(..))"/> <--这里配置切入点表达式-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> <--这里让切入点表达式和事务通知建立关系-->
    </aop:config>

在平常开发中,一般增删改等会修改数据库的操作,传播级别设置为REQUIRED ,查操作设置为SUPPORTS

posted @ 2020-11-22 02:59  深林slinnr  阅读(341)  评论(0)    收藏  举报