当前位置: 移动技术网 > IT编程>开发语言>Java > spring5 源码深度解析----- @Transactional注解的声明式事物介绍(100%理解事务)

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

2019年10月12日  | 移动技术网IT编程  | 我要评论

军装下的绕指柔19楼,暴风城长者,788111

面的几个章节已经分析了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事物支持核心接口

 
  • 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注解的声明式事物的的源码实现。

 
 

 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网