Spring事务

2022-11-23  本文已影响0人  Hey_Shaw

1. 数据库事务特征

1.1 ACID特性

事务 (Transaction) 是数据库系统中一系列操作的一个逻辑单元,所有操作要么全部成功要么全部失败

事务是区分文件存储系统Nosql 数据库重要特性之一,其存在的意义是为了保证即使在并发情况下也能挣钱的执行crud操作。怎样才算是正确的呢?这时提出了事务需要保证的四个特性即ACID

扩展

WAL原则

InnoDB 的 ARIES 三原则 Write Ahead Logging (WAL)

并发事务控制

1.2 事务隔离级别

在高并发的情况下,要完全保证其ACID特性是非常困难的,除非把所有的事务串行化执行,但带来的负面的影响将是性能大打折扣。很多时候我们有些业务对事务的要求是不一样的,所以数据库中设计了四种隔离级别,供用户基于业务进行选择。

数据库默认隔离级别:
# 查看 mysql 的默认隔离级别
SELECT @@tx_isolation

# 设置为读未提交
SET tx_isolation='read-uncommitted';

# 设置为读已提交
SET tx_isolation='read-committed';

# 设置为可重复读
SET tx_isolation='REPEATABLE-READ';

# 设置为串行化
SET tx_isolation='SERIALIZABLE'
隔离级别 脏读(Dirty Read) 不可重复读(NonRepeatable Read) 幻读(Phantom Read)
未提交读(Read uncommitted) 可能 可能 可能
已提交读(Read committed) 不可能 可能 可能
可重复读(Repeatable read) 不可能 不可能 可能
可串行化(Serializable) 不可能 不可能 不可能
脏读
   一个事务读取到另一个事务未提交的更新数据

session_1

# 设置为读未提交
SET tx_isolation='read-uncommitted';

BEGIN;

INSERT INTO `tbl_employee` (`last_name`,`email`,  `gender`)
    VALUES ( '2222', '111111@qq.com', '1' );
# 1. 执行到此处,执行session_2

ROLLBACK;
# 3. 执行到此处,再执行session_2

COMMIT;  -- 无用

session_2

# 设置为读未提交
SET tx_isolation='read-uncommitted';

SELECT * FROM `tbl_employee`;
# 2. 执行完成,session_1 事务未提交,却读出了数据;回到session_1执行回滚
# 4. 此时查询不到数据,说明第2步出现脏读
不可重复读
   在同一事务中,多次读取同一数据返回的结果有所不同,换句话说,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是后续读取不能读到另一事务已提交的更新数据。

事务B修改数据导致当前事务A前后读取数据不一致,侧重点在于事务B的修改

当前事务读到了其他事务修改的数据

session_1

# 设置为读已提交
SET tx_isolation='read-committed';

BEGIN;

SELECT * FROM `tbl_employee`;
# 1. 执行session_2中内容

SELECT * FROM `tbl_employee`;

# 3. 和前面查询的数据不一致
COMMIT;

session_2

# 设置为读已提交
SET tx_isolation='read-committed';

UPDATE `tbl_employee` SET email = "222222@qq.com" WHERE id = 5;
# 2. 执行session_1中内容
幻读
    查询表中一条数据,如果不存在就插入一条,并发的时候却发现,里面居然有两条相同的数据。

    事务A修改表中数据,此时事务B插入一条新数据,事务A查询发现表中还有没修改的数据,像是出现幻觉

    事务A读到了事务B新增的数据,导致结果不一致,侧重点在于事务B新增数据

session_1

# 设置为可重复读
SET tx_isolation='REPEATABLE-READ';

BEGIN;

SELECT * FROM `tbl_employee` WHERE last_name = "jack";

# 1. 此时在session_2事务插入数据

SELECT * FROM `tbl_employee` WHERE last_name = "jack";

INSERT INTO `tbl_employee` (`last_name`,`email`,  `gender`)
    VALUES ( 'jack', '333333333@qq.com', '1' );
    
SELECT * FROM `tbl_employee` WHERE last_name = "jack";  

UPDATE `tbl_employee` SET email = "444444444@qq.com" WHERE last_name = "jack";

SELECT * FROM `tbl_employee` WHERE last_name = "jack";

COMMIT;

session_2

# 设置为可重复读
SET tx_isolation='REPEATABLE-READ';

INSERT INTO `tbl_employee` (`last_name`,`email`,  `gender`)
    VALUES ( 'jack', '1111111@qq.com', '1' );

2. Spring事务应用及源码分析

2.1 Spring事务相关API

Spring 事务是在数据库事务的基础上进行封装扩展,其主要特性如下:

Spring 提供了事务相关接口:

相关实现类

2.2 编程式事务

public class SpringTransactionExample {
    private static String url = "jdbc:mysql://127.0.0.1:3306/test";
    private static String user = "root";
    private static String password = "root";
    
    public static void main(string[] args) {
      // 获取数据源
      final Datasource ds = new DriverManagerDatasource(url, user, password);
      //编程式事务
      final TransactionTemplate template = new TransactionTemplate();
      // 设置事务管理器
      template.setTransactionManager(new DataSourceTransactionManager(ds));
        
      template.execute(new TransactionCa11back<0bject>() {
         @override
         public object doInTransaction(Transactionstatus status) {
             Connection conn = DatasourceUtils.getConnection(ds);
             object savePoint = null;
             try {
                 {
                    // 插入
                    PreparedStatement prepare = conn.preparestatement("insert INTO account (accountName,user,money) VALUES (?, ?, ?)");
                    preparp.setstring(1, "111");
                    prepare.setstring(2, "aaa");
                    prepare.setInt(3, 10000);
                    prepare.executeUpdate():;
                 }
                 //设置保存点
                savepoint = status.createSavepoint();
                {
                   //捕入
                   PreparedStatement prepare = conn.preparestatement("insert INTO account (accountName.user,money) VALUES (?, ?, ?)");
                    prepare.setstring(1,"222");
                    prepare.setstring(2, "bbb");
                    prepare.setInt(3, 10000);
                    prepare.executeupdate() ; 
                }
                {
                    //更新
                    Preparedstatement prepare = conn.preparestatement("UPDATE account SET money= money+100 where user=?");
                    prepare.setstring(1, "aaa");
                    prepare.executeUpdate();
                    //int i=1/0;
                }
             } catch (SQLException e) {
                e.printstackTrace();
             } catch (Exception e) {
                System.out.print1n("更新失败");
                if (savePoint != null) [
                    status.ro11backTosavepoint(savePoint);
                } else {
                    status.setRollbackOnly();
                }  
             }
             return null;
       }
    });
}

2.3 声明式事务

Xml配置

添加tx名字空间

xmlns:tx="http://www.springframework.org/schema/tx"

配置事务管理器和数据源

<bean id="datasource"
  class="org.springframework.jdbc.datasource.DriverManagerDatasource">
    <constructor-arg name="ur1" value="jdbc:mysq1://127 .0.0.1/test"/>
    <constructor-arg name="username" va]ue="root"/>
    <constructor-arg name="password" value="root"/>
</bean>
<bean id="txManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="datasource" ref="datasource"/>
</bean>

事务切面配置

<!-- 事物切面配置 -->
<tx:advice id="advice" transaction-manager="txManager"
    <tx:attributes>
        <tx:method name="update*" propagation-"REQUIRED" read-only="false" rollback-for="Java.1ang.Exception"/>
        <tx:method name="add*" propagation="REQUIRED" read-only="false"/>
    </tx:attributess>
</tx:advice>
<!-- 事物切入点 -->
<aop:config>
    <aop:pointcut expression="execution(* bat.ke.qq.com.service.*.*(..))" id="txPointCut"/>
    <aop:advisor advice-ref="advice" pointcut-ref="txPointCut"/>
</aop:config>

@Transactional

<!-- 开启事务控制的注解支持 -->
<tx:annotation-driven transaction-manager="txManager"/>

事务主机配置,作用于类,方法上

属性名 说明
name 当在配置文件中有多个TransactionManager,可以用该属性指定选择哪个事务管理器
propagation 事务的传播行为,默认值为REOUIRED
isolation 事务的隔离度,默认值采用DEFAULT
timeout 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务
read-only 指定事务是否为只读事务,默认值为false;为了忽略那些不需要事务的方法,比如读取数
据,可以设置 read-only 为 true。
rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通
过逗号分隔
no-rollback-for 抛出 no-rollback-for 指定的异常类型,不回滚事务

Java Configuration

@EnableTransactionManagement

利用 TransactionManagementConfigurationSelector 向容器中注册两个组件

Spring的事务传播机制

    多个事务方法相互调用时,事务如何在这些方法之间进行传播,Spring中提供了 `7种` 不同的传播特性,来保证事务的正常执行

NESTED 和 REQUIRED_NEW 的区别:

REQUIRED_NEW 是新建一个事务并且新开始的这个事务与原有事务无关,而 NESTED 则是当前存在事务时会开启一个嵌套事务,在 NESTED 情况下,父事务回滚时,子事务也会回滚,而 REQUIRED_NEW 情况下,原有事务回滚,不会影响新开启的事务

NESTED 和 REQUIRED 的区别:

REQUIRED 情况下,调用方存在事务时,则被调用方和调用方使用同一个事务,那么被调用方出现异常时,由于共用一个事务,所以无论是否 catch异常,事务都会回滚,而在 NESTED 情况下,被调用方发生异常时,调用方可以 catch其异常,这样只有子事务回滚,父事务不会回滚

Spring事务的实现原理

    在使用Spring框架的时候,可以有两种事务的实现方式,一种是编程式事务,有用内自己通过代码来撑制事务的处理逻辑,还有一种是声明式事务,通过 `@Transactional` 注解来实现。

    其实事务的操作本来应该是由数据库来进行控制,但是为了方便用户进行业务逻辑的操作,Spring对事务功能进行了扩展实现,一般我们很少会用编程式事务,更多的是通过添加 @Transactional 注解来进行实现,当添加此注解之后事务的自动功能就会关闭,有Spring框架来帮助进行控制。

    其实事务操作是AOP的一个核心体现,当一个方法添加 @Transactional 注解之后,Spring会基于这个类生成个代理对象,会将这个代理对象作为bean,当使用这个代理对象的方法的时候,如果有事务处理,那么会先把事务的自动提交给关系,然后去执行具体的业务逻辑,如果执行逻辑没有出现异常,那么代理逻辑就会直接提交如果出现任何异常情况,那么直接进行回滚操作,当然用户可以控制对哪些异常进行回滚操作。

Spring事务失效场景

上一篇 下一篇

猜你喜欢

热点阅读