欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

spring5 源码深度解析----- @Transactional注解的声明式事物介绍(100%理解事务)

程序员文章站 2022-03-25 22:53:22
面的几个章节已经分析了spring基于@AspectJ的源码,那么接下来我们分析一下Aop的另一个重要功能,事物管理。 事务的介绍 1.数据库事物特性 原子性多个数据库操作是不可分割的,只有所有的操作都执行成功,事物才能被提交;只要有一个操作执行失败,那么所有的操作都要回滚,数据库状态必须回复到操作 ......

面的几个章节已经分析了spring基于@aspectj的源码,那么接下来我们分析一下aop的另一个重要功能,事物管理。

事务的介绍

1.数据库事物特性

  • 原子性
    多个数据库操作是不可分割的,只有所有的操作都执行成功,事物才能被提交;只要有一个操作执行失败,那么所有的操作都要回滚,数据库状态必须回复到操作之前的状态
  • 一致性
    事物操作成功后,数据库的状态和业务规则必须一致。例如:从a账户转账100元到b账户,无论数据库操作成功失败,a和b两个账户的存款总额是不变的。
  • 隔离性
    当并发操作时,不同的数据库事物之间不会相互干扰(当然这个事物隔离级别也是有关系的)
  • 持久性
    事物提交成功之后,事物中的所有数据都必须持久化到数据库中。即使事物提交之后数据库立刻崩溃,也需要保证数据能能够被恢复。

2.事物隔离级别

当数据库并发操作时,可能会引起脏读、不可重复读、幻读、第一类丢失更新、第二类更新丢失等现象。

  • 脏读
    事物a读取事物b尚未提交的更改数据,并做了修改;此时如果事物b回滚,那么事物a读取到的数据是无效的,此时就发生了脏读。
  • 不可重复读
    一个事务执行相同的查询两次或两次以上,每次都得到不同的数据。如:a事物下查询账户余额,此时恰巧b事物给账户里转账100元,a事物再次查询账户余额,那么a事物的两次查询结果是不一致的。
  • 幻读
    a事物读取b事物提交的新增数据,此时a事物将出现幻读现象。幻读与不可重复读容易混淆,如何区分呢?幻读是读取到了其他事物提交的新数据,不可重复读是读取到了已经提交事物的更改数据(修改或删除)

对于以上问题,可以有多个解决方案,设置数据库事物隔离级别就是其中的一种,数据库事物隔离级别分为四个等级,通过一个表格描述其作用。

隔离级别 脏读 不可重复读 幻象读
read uncommitted 允许 允许 允许
read committed 脏读 允许 允许
repeatable read 不允许 不允许 允许
serializable 不允许 不允许 不允许

3.spring事物支持核心接口

 spring5 源码深度解析----- @Transactional注解的声明式事物介绍(100%理解事务)
  • transactiondefinition-->定义与spring兼容的事务属性的接口
public interface transactiondefinition {
    // 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中。
    int propagation_required = 0;
    // 支持当前事物,如果当前没有事物,则以非事物方式执行。
    int propagation_supports = 1;
    // 使用当前事物,如果当前没有事物,则抛出异常。
    int propagation_mandatory = 2;
    // 新建事物,如果当前已经存在事物,则挂起当前事物。
    int propagation_requires_new = 3;
    // 以非事物方式执行,如果当前存在事物,则挂起当前事物。
    int propagation_not_supported = 4;
    // 以非事物方式执行,如果当前存在事物,则抛出异常。
    int propagation_never = 5;
    // 如果当前存在事物,则在嵌套事物内执行;如果当前没有事物,则与propagation_required传播特性相同
    int propagation_nested = 6;
    // 使用后端数据库默认的隔离级别。
    int isolation_default = -1;
    // read_uncommitted 隔离级别
    int isolation_read_uncommitted = connection.transaction_read_uncommitted;
    // read_committed 隔离级别
    int isolation_read_committed = connection.transaction_read_committed;
    // repeatable_read 隔离级别
    int isolation_repeatable_read = connection.transaction_repeatable_read;
    // serializable 隔离级别
    int isolation_serializable = connection.transaction_serializable;
    // 默认超时时间
    int timeout_default = -1;
    // 获取事物传播特性
    int getpropagationbehavior();
    // 获取事物隔离级别
    int getisolationlevel();
    // 获取事物超时时间
    int gettimeout();
    // 判断事物是否可读
    boolean isreadonly();
    // 获取事物名称
    @nullable
    string getname();
}
  1. spring事物传播特性表:
传播特性名称 说明
propagation_required 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中
propagation_supports 支持当前事物,如果当前没有事物,则以非事物方式执行
propagation_mandatory 使用当前事物,如果当前没有事物,则抛出异常
propagation_requires_new 新建事物,如果当前已经存在事物,则挂起当前事物
propagation_not_supported 以非事物方式执行,如果当前存在事物,则挂起当前事物
propagation_never 以非事物方式执行,如果当前存在事物,则抛出异常
propagation_nested

如果当前存在事物,则在嵌套事物内执行;

如果当前没有事物,则与propagation_required传播特性相同

  1. spring事物隔离级别表:
事务隔离级别  脏读  不可重复读  幻读
 读未提交(read-uncommitted)  是  是  是
 不可重复读(read-committed)  否   是  是
 可重复读(repeatable-read)  否   否   是
 串行化(serializable)  否   否   否 

mysql默认的事务隔离级别为 可重复读repeatable-read

  3.platformtransactionmanager-->spring事务基础结构中的中心接口

public interface platformtransactionmanager {
    // 根据指定的传播行为,返回当前活动的事务或创建新事务。
    transactionstatus gettransaction(@nullable transactiondefinition definition) throws transactionexception;
    // 就给定事务的状态提交给定事务。
    void commit(transactionstatus status) throws transactionexception;
    // 执行给定事务的回滚。
    void rollback(transactionstatus status) throws transactionexception;
}

spring将事物管理委托给底层的持久化框架来完成,因此,spring为不同的持久化框架提供了不同的platformtransactionmanager接口实现。列举几个spring自带的事物管理器:

事物管理器 说明
org.springframework.jdbc.datasource.datasourcetransactionmanager 提供对单个javax.sql.datasource事务管理,用于spring jdbc抽象框架、ibatis或mybatis框架的事务管理
org.springframework.orm.jpa.jpatransactionmanager 提供对单个javax.persistence.entitymanagerfactory事务支持,用于集成jpa实现框架时的事务管理
org.springframework.transaction.jta.jtatransactionmanager 提供对分布式事务管理的支持,并将事务管理委托给java ee应用服务器事务管理器

 

  • transactionstatus-->事物状态描述
  1. transactionstatus接口
public interface transactionstatus extends savepointmanager, flushable {
    // 返回当前事务是否为新事务(否则将参与到现有事务中,或者可能一开始就不在实际事务中运行)
    boolean isnewtransaction();
    // 返回该事务是否在内部携带保存点,也就是说,已经创建为基于保存点的嵌套事务。
    boolean hassavepoint();
    // 设置事务仅回滚。
    void setrollbackonly();
    // 返回事务是否已标记为仅回滚
    boolean isrollbackonly();
    // 将会话刷新到数据存储区
    @override
    void flush();
    // 返回事物是否已经完成,无论提交或者回滚。
    boolean iscompleted();
}
  1. savepointmanager接口
public interface savepointmanager {
    // 创建一个新的保存点。
    object createsavepoint() throws transactionexception;
    // 回滚到给定的保存点。
    // 注意:调用此方法回滚到给定的保存点之后,不会自动释放保存点,
    // 可以通过调用releasesavepoint方法释放保存点。
    void rollbacktosavepoint(object savepoint) throws transactionexception;
    // 显式释放给定的保存点。(大多数事务管理器将在事务完成时自动释放保存点)
    void releasesavepoint(object savepoint) throws transactionexception;
}

spring编程式事物

create table `account` (
  `id` int(11) not null auto_increment comment '自增主键',
  `balance` int(11) default null comment '账户余额',
  primary key (`id`)
) engine=innodb auto_increment=5 default charset=utf8 comment='--账户表'
  • 实现
 1 import org.apache.commons.dbcp.basicdatasource;
 2 import org.springframework.dao.dataaccessexception;
 3 import org.springframework.jdbc.core.jdbctemplate;
 4 import org.springframework.jdbc.datasource.datasourcetransactionmanager;
 5 import org.springframework.transaction.transactiondefinition;
 6 import org.springframework.transaction.transactionstatus;
 7 import org.springframework.transaction.support.defaulttransactiondefinition;
 8 
 9 import javax.sql.datasource;
10 
11 /**
12  * spring编程式事物
13  * @author: chenhao
14  * @create: 2019-10-08 11:41
15  */
16 public class mytransaction {
17 
18     private jdbctemplate jdbctemplate;
19     private datasourcetransactionmanager txmanager;
20     private defaulttransactiondefinition txdefinition;
21     private string insert_sql = "insert into account (balance) values ('100')";
22 
23     public void save() {
24 
25         // 1、初始化jdbctemplate
26         datasource datasource = getdatasource();
27         jdbctemplate = new jdbctemplate(datasource);
28 
29         // 2、创建物管理器
30         txmanager = new datasourcetransactionmanager();
31         txmanager.setdatasource(datasource);
32 
33         // 3、定义事物属性
34         txdefinition = new defaulttransactiondefinition();
35         txdefinition.setpropagationbehavior(transactiondefinition.propagation_required);
36 
37         // 3、开启事物
38         transactionstatus txstatus = txmanager.gettransaction(txdefinition);
39 
40         // 4、执行业务逻辑
41         try {
42             jdbctemplate.execute(insert_sql);
43             //int i = 1/0;
44             jdbctemplate.execute(insert_sql);
45             txmanager.commit(txstatus);
46         } catch (dataaccessexception e) {
47             txmanager.rollback(txstatus);
48             e.printstacktrace();
49         }
50 
51     }
52 
53     public datasource getdatasource() {
54         basicdatasource datasource = new basicdatasource();
55         datasource.setdriverclassname("com.mysql.jdbc.driver");
56         datasource.seturl("jdbc:mysql://localhost:3306/my_test?usessl=false&useunicode=true&characterencoding=utf-8");
57         datasource.setusername("root");
58         datasource.setpassword("chenhao1991@");
59         return datasource;
60     }
61 
62 }
  • 测试类及结果
public class mytest {
    @test
    public void test1() {
        mytransaction mytransaction = new mytransaction();
        mytransaction.save();
    }
}

运行测试类,在抛出异常之后手动回滚事物,所以数据库表中不会增加记录。

基于@transactional注解的声明式事物

其底层建立在 aop 的基础之上,对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。通过声明式事物,无需在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。

  • 接口
import org.springframework.transaction.annotation.propagation;
import org.springframework.transaction.annotation.transactional;

/**
 * 账户接口
 * @author: chenhao
 * @create: 2019-10-08 18:38
 */
@transactional(propagation = propagation.required)
public interface accountserviceimp {
    void save() throws runtimeexception;
}
  • 实现
import org.springframework.jdbc.core.jdbctemplate;

/**
 * 账户接口实现
 * @author: chenhao
 * @create: 2019-10-08 18:39
 */
public class accountserviceimpl implements accountserviceimp {

    private jdbctemplate jdbctemplate;

    private static string insert_sql = "insert into account(balance) values (100)";


    @override
    public void save() throws runtimeexception {
        system.out.println("==开始执行sql");
        jdbctemplate.update(insert_sql);
        system.out.println("==结束执行sql");

        system.out.println("==准备抛出异常");
        throw new runtimeexception("==手动抛出一个异常");
    }

    public void setjdbctemplate(jdbctemplate jdbctemplate) {
        this.jdbctemplate = jdbctemplate;
    }
}
  • 配置文件
<?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:tx="http://www.springframework.org/schema/tx"
       xsi:schemalocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--开启tx注解-->
    <tx:annotation-driven transaction-manager="transactionmanager"/>

    <!--事物管理器-->
    <bean id="transactionmanager" class="org.springframework.jdbc.datasource.datasourcetransactionmanager">
        <property name="datasource" ref="datasource"/>
    </bean>

    <!--数据源-->
    <bean id="datasource" class="org.apache.commons.dbcp.basicdatasource">
        <property name="driverclassname" value="com.mysql.jdbc.driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/my_test?usessl=false&amp;useunicode=true&amp;characterencoding=utf-8"/>
        <property name="username" value="root"/>
        <property name="password" value="chenhao1991@"/>
    </bean>

    <!--jdbctemplate-->
    <bean id="jdbctemplate" class="org.springframework.jdbc.core.jdbctemplate">
        <property name="datasource" ref="datasource"/>
    </bean>

    <!--业务bean-->
    <bean id="accountservice" class="com.chenhao.aop.accountserviceimpl">
        <property name="jdbctemplate" ref="jdbctemplate"/>
    </bean>

</beans>
  • 测试
import org.junit.test;
import org.springframework.context.applicationcontext;
import org.springframework.context.support.classpathxmlapplicationcontext;

/**
 * @author: chenhao
 * @create: 2019-10-08 18:45
 */
public class mytest {

    @test
    public void test1() {
        // 基于tx标签的声明式事物
        applicationcontext ctx = new classpathxmlapplicationcontext("aop.xml");
        accountserviceimp studentservice = ctx.getbean("accountservice", accountserviceimp.class);
        studentservice.save();
    }
}
  • 测试
==开始执行sql
==结束执行sql
==准备抛出异常

java.lang.runtimeexception: ==手动抛出一个异常

    at com.lyc.cn.v2.day09.accountserviceimpl.save(accountserviceimpl.java:24)
    at sun.reflect.nativemethodaccessorimpl.invoke0(native method)
    at sun.reflect.nativemethodaccessorimpl.invoke(nativemethodaccessorimpl.java:62)
    at sun.reflect.delegatingmethodaccessorimpl.invoke(delegatingmethodaccessorimpl.java:43)
    at java.lang.reflect.method.invoke(method.java:498)

测试方法中手动抛出了一个异常,spring会自动回滚事物,查看数据库可以看到并没有新增记录。

注意:默认情况下spring中的事务处理只对runtimeexception方法进行回滚,所以,如果此处将runtimeexception替换成普通的exception不会产生回滚效果。

接下来我们就分析基于@transactional注解的声明式事物的的源码实现。