美文网首页深度解析Spring5源码
39--SpringAop事物管理简介及编程式事物实现

39--SpringAop事物管理简介及编程式事物实现

作者: 闲来也无事 | 来源:发表于2018-12-20 19:59 被阅读19次

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

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

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

    • 脏读
      事物A读取事物B尚未提交的更改数据,并做了修改;此时如果事物B回滚,那么事物A读取到的数据是无效的,此时就发生了脏读。
    • 不可重复读
      一个事务执行相同的查询两次或两次以上,每次都得到不同的数据。如:A事物下查询账户余额,此时恰巧B事物给账户里转账100元,A事物再次查询账户余额,那么A事物的两次查询结果是不一致的。
    • 幻读
      A事物读取B事物提交的新增数据,此时A事物将出现幻读现象。幻读与不可重复读容易混淆,如何区分呢?幻读是读取到了其他事物提交的新数据,不可重复读是读取到了已经提交事物的更改数据(修改或删除)
    • 第一类丢失更新
      A事物的回滚覆盖了B事物已经提交的数据。如:账户有1000元,A事物执行取款100元操作,但未提交事物;此时B事物向账户存入100元并提交事物,账户余额改为1100元。此时A事物回滚了取款操作,账户余额被恢复成了1000元。
    • 第二类更新丢失
      A事物的提交覆盖了B事物已经提交的数据。如:账户有1000元,A事物操作向账户存入100元,但未提交事物;此时B事物从账户取出100元并提交事物,账户余额改为900元;此时A事物提交事物,账户余额变为1100元。

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

    隔离级别 脏读 不可重复读 幻象读 第一类丢失更新 第二类丢失更新
    READ UNCOMMITTED 允许 允许 允许 允许 允许
    READ COMMITTED 脏读 允许 允许 允许 允许
    REPEATABLE READ 不允许 不允许 允许 不允许 不允许
    SERIALIZABLE 不允许 不允许 不允许 不允许 不允许
    3.Spring事物支持核心接口
    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事物隔离级别表:
    隔离级别 脏读 不可重复读 幻象读 第一类丢失更新 第二类丢失更新
    ISOLATION_DEFAULT 同后端数据库 同后端数据库 同后端数据库 同后端数据库 同后端数据库
    ISOLATION_READ_UNCOMMITTED 允许 允许 允许 允许 允许
    ISOLATION_READ_COMMITTED 脏读 允许 允许 允许 允许
    ISOLATION_REPEATABLE_READ 不允许 不允许 允许 不允许 不允许
    ISOLATION_SERIALIZABLE 不允许 不允许 不允许 不允许 不允许
    • 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;
    }
    
    1. Flushable接口
    public interface Flushable {
        // 将会话刷新到数据存储区
        void flush() throws IOException;
    }
    
    4.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='--账户表'
    
    • 实现
    package com.lyc.cn.v2.day08;
    
    import org.apache.commons.dbcp.BasicDataSource;
    import org.springframework.dao.DataAccessException;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.TransactionDefinition;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.support.DefaultTransactionDefinition;
    
    import javax.sql.DataSource;
    
    /**
     * Spring编程式事物
     * @author: LiYanChao
     * @create: 2018-11-09 11:41
     */
    public class MyTransaction {
    
        private JdbcTemplate jdbcTemplate;
        private DataSourceTransactionManager txManager;
        private DefaultTransactionDefinition txDefinition;
        private String insert_sql = "insert into account (balance) values ('100')";
    
        public void save() {
    
            // 1、初始化jdbcTemplate
            DataSource dataSource = getDataSource();
            jdbcTemplate = new JdbcTemplate(dataSource);
    
            // 2、创建物管理器
            txManager = new DataSourceTransactionManager();
            txManager.setDataSource(dataSource);
    
            // 3、定义事物属性
            txDefinition = new DefaultTransactionDefinition();
            txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    
            // 3、开启事物
            TransactionStatus txStatus = txManager.getTransaction(txDefinition);
    
            // 4、执行业务逻辑
            try {
                jdbcTemplate.execute(insert_sql);
                //int i = 1/0;
                jdbcTemplate.execute(insert_sql);
                txManager.commit(txStatus);
            } catch (DataAccessException e) {
                txManager.rollback(txStatus);
                e.printStackTrace();
            }
    
        }
    
        public DataSource getDataSource() {
            BasicDataSource dataSource = new BasicDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/my_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8");
            dataSource.setUsername("root");
            dataSource.setPassword("liyanchao1989@");
            return dataSource;
        }
    
    }
    
    • 增加Gradle模块和包
    // 引入spring-jdbc模块
    optional(project(":spring-jdbc"))
    // https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp
    compile group: 'commons-dbcp', name: 'commons-dbcp', version: '1.4'
    // https://mvnrepository.com/artifact/mysql/mysql-connector-java
    compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.38'
    
    • 测试类及结果
    package com.lyc.cn.v2.day08;
    
    import org.junit.Test;
    
    /**
     * @author: LiYanChao
     * @create: 2018-11-07 18:45
     */
    public class MyTest {
        @Test
        public void test1() {
            MyTransaction myTransaction = new MyTransaction();
            myTransaction.save();
        }
    }
    

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

    相关文章

      网友评论

        本文标题:39--SpringAop事物管理简介及编程式事物实现

        本文链接:https://www.haomeiwen.com/subject/ccuukqtx.html