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


    面的几个章节已经分析了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注解的声明式事物的的源码实现。

     
     
  • 相关阅读:
    Android中echarts使用
    小程序搜索定位导航
    去除数组对象中的某属性相同时删除整个对象
    小程序rtmp监控视频播放
    小程序定义优惠券样式
    小程序购物车实现
    JavaScript Cookie
    JqueryValidate使用
    oracel 创建表空间、用户、导入数据库
    跨域访问Jquery实现[转]
  • 原文地址:https://www.cnblogs.com/java-chen-hao/p/11635525.html
Copyright © 2020-2023  润新知