• 04【Spring声明式事、传播行为、AOP事务控制】


    04【Spring声明式事、传播行为、AOP事务控制】

    一、Spring声明式事务

    1.1 回顾事务

    1.1.1 什么是事务?

    如果一个业务操作中多次访问了数据库,必须保证每条SQL语句都执行成功。如果其中有一条执行失败,所有已经执行过的代码必须回滚。回到没有执行前的状态。称为事务。要么所有的SQL语句全部执行成功,要么全部失败。

    1.1.2 事务四大特性

    事务特性含义
    原子性(Atomicity)事务是工作的最小单元,整个工作单元要么全部执行成功,要么全部执行失败
    一致性(Consistency)事务执行前与执行后,数据库中数据应该保持相同的状态。如:转账前总金额与转账后总金额相同。
    隔离性(Isolation)事务与事务之间不能互相影响,必须保持隔离性。
    持久性(Durability)如果事务执行成功,对数据库的操作是持久的。

    1.1.3 事务并发访问问题

    并发访问下事务产生的问题:

    当同时有多个用户在访问同一张表中的记录,每个用户在访问的时候都是一个单独的事务。

    事务在操作时的理想状态是:事务之间不应该相互影响,实际应用的时候会引发下面三种问题。应该尽量避免这些问题的发生。通过数据库本身的功能去避免,设置不同的隔离级别

    • 脏读: 一个事务(用户)读取到了另一个事务没有提交的数据
    • 不可重复读:一个事务多次读取同一条记录,出现读取数据不一致的情况。一般因为另一个事务更新了这条记录而引发的。
    • 幻读:在一次事务中,多次读取到的条数不一致

    1.1.4 事务隔离级别

    级别名字隔离级别脏读不可重复读幻读数据库默认隔离级别
    1读未提交read uncommitted
    2读已提交read committedOracle和SQL Server
    3可重复读repeatable readMySQL
    4串行化serializable
    • 1)Read uncommitted (读未提交): 简称RU隔离级别,所有事务中的并发访问问题都会发生,读取的是其他事务没有提交的数据

    • 2)Read committed (读已提交):简称RC隔离级别,会引发不可重复读和幻读的问题,读取的永远是其他事务提交的数据

    • 3)Repeatable read (可重复读):简称RR隔离级别,有可能会引发幻读的问题,一次事务读取到的同一行数据,永远是一样

    • 4)Serializable (串行化): 可以避免所有事务产生的并发访问的问题 效率及其低下

    1.1.5 MySQL事务相关命令

    • 测试表:
    drop table if exists account;
    
    -- 创建数据表  
    CREATE TABLE account (  
        id INT PRIMARY KEY AUTO_INCREMENT,  
        name VARCHAR(10),  
        money DOUBLE  
    );  
    
    -- 添加数据  
    INSERT INTO account (name, money) VALUES ('a', 1000), ('b', 1000);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 查询事务隔离级别:
    select @@tx_isolation;
    
    • 1
    • 设置事务隔离级别:
    set global transaction isolation level 四种隔离;
    
    set global transaction isolation level read uncommitted;
    set global transaction isolation level read committed;
    set global transaction isolation level repeatable read;
    set global transaction isolation level serializable;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    需要重启客户端会话

    • 开启事务:
    start transaction;
    或者
    begin;
    
    • 1
    • 2
    • 3
    • 提交事务:
    commit;
    
    • 1
    • 回滚事务:
    rollback;
    
    • 1

    1.2 Spring事务管理介绍

    1.2.1 编程式事务

    • 什么是编程式事务?

    编程式事务简单的来说就是采用编程的方式来管理事务,编程式事务需要将事务管理的代码写入到业务方法中,相对于核心业务而言,事务管理的代码显然属于非核心业务对核心业务代码的侵入性太大;而且如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余

    我们之前使用的JDBC API来操作事务就是编程式事务;

    1.2.2 声明式事务

    • 什么是声明式事务?

    声明式事务以声明的方式来实现事务管理,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

    声明式事务简单的来说就是通过一些表达式声明(拦截)一堆需要被事务管理的方法,然后被声明(拦截)的方法通过AOP的方式进行事务管理(执行目标方法之前开启事务,执行目标方法之后提交或者回滚事务)

    显然声明式事务管理要优于编程式事务管理:它将事务管理代码从业务方法中分离出来,这正是Spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受干扰;一个普通的对象,只要加上注解就可以获得完全的事务支持。

    声明式事务唯一不足地方是,它的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

    1.2.3 Spring事务管理器

    Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。

    Spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口。

    它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。

    • 事务管理器主要的实现:
      • DataSourceTransactionManager:使用Spring JDBC 或 Mybatis 等基于DataSource数据源的持久化技术时,使用 该事务管理器
      • JpaTransactionManager:使用JPA时采用的事务管理器
      • JtaTransactionManager:具有多个数据源的全局事务使用该事务管理器
      • JdoTransactionManager:使用JDO进行持久化时 ,使用该事务管理器
      • HibernateTransactionManager:使用Hibernate进行持久化时,使用该事务管理器

    1.2.4 事务传播行为

    当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。

    例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为,在org.springframework.transaction.TransactionDefinition类中被定义。

    静态常量说明
    PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择,也是Spring的默认值
    PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
    PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
    PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
    PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
    PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

    TransactionDefinition类中不仅定义了事务的传播行为,也定义了很多事务的隔离级别:

    静态常量说明
    ISOLATION_DEFAULT使用数据库默认的隔离级别,Spring默认值
    ISOLATION_READ_UNCOMMITTED读未提交
    ISOLATION_READ_COMMITTED读已提交
    ISOLATION_REPEATABLE_READ可重复读
    ISOLATION_SERIALIZABLE串行化

    1.3 Spring实现声明式事务

    1.3.1 案例准备

    1)SQL脚本:
    drop database if exists spring;
    
    create database spring;
    
    use spring;
    
    -- 创建数据表  
    CREATE TABLE account (  
        id INT PRIMARY KEY AUTO_INCREMENT,  
        name VARCHAR(10),  
        money DOUBLE  
    );  
    
    -- 添加数据  
    INSERT INTO account (name, money) VALUES ('a', 1000), ('b', 1000);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    2)Maven依赖:
    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
    
        <groupId>com.dfbzgroupId>
        <artifactId>01_SpringartifactId>
        <version>1.0-SNAPSHOTversion>
    
        <dependencies>
            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-contextartifactId>
                <version>5.2.9.RELEASEversion>
            dependency>
    
            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-jdbcartifactId>
                <version>5.2.9.RELEASEversion>
            dependency>
    
            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-txartifactId>
                <version>5.2.9.RELEASEversion>
            dependency>
    
            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-aspectsartifactId>
                <version>5.2.9.RELEASEversion>
            dependency>
    
            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-testartifactId>
                <version>5.2.9.RELEASEversion>
            dependency>
    
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>5.1.48version>
            dependency>
    
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>druidartifactId>
                <version>1.2.1version>
            dependency>
    
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>4.12version>
                <scope>testscope>
            dependency>
            
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <version>1.18.18version>
            dependency>
        dependencies>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    3)jdbc.properties:
    jdbc.username=root
    jdbc.password=admin
    jdbc.url=jdbc:mysql:///spring
    jdbc.driverClassName=com.mysql.jdbc.Driver
    
    • 1
    • 2
    • 3
    • 4
    4)实体类:
    package com.dfbz.entity;
    
    import lombok.Data;
    
    /**
     * @author lscl
     * @version 1.0
     * @intro:
     */
    @Data
    public class Account {
        private Integer id;
        private String name;
        private Double money;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    1.3.2 事务通知XML配置

    1)Spring配置:
    
    <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"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        
        <context:component-scan base-package="com.dfbz" />
    
        
        <context:property-placeholder location="classpath:jdbc.properties"/>
    
        
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="username" value="${jdbc.username}">property>
            <property name="password" value="${jdbc.password}">property>
            <property name="url" value="${jdbc.url}">property>
            <property name="driverClassName" value="${jdbc.driverClassName}">property>
        bean>
    
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource">property>
        bean>
    
        
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            
            <property name="dataSource" ref="dataSource"/>
        bean>
    
        
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
    
                
    
    
    
    
    
    
    
    
    
    
    
                
                <tx:method name="transfer" no-rollback-for="java.lang.ArithmeticException"/>
            tx:attributes>
    
        tx:advice>
    
        
        <aop:config>
            
            <aop:pointcut id="myPoint" expression="execution(* com.dfbz.service.AccountService.*(..))"/>
    
            
            <aop:advisor advice-ref="txAdvice" pointcut-ref="myPoint">aop:advisor>
        aop:config>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    **默认情况下,任何的运行时异常事务都会回滚,但编译时异常都不会进行回滚;**我们可以通过一些配置来调整

    • no-rollback-for:指定什么异常不回滚,可以写多个,以逗号隔开;

    • rollback-for:该配置针对于编译时异常;可以写多个,以逗号隔开;

    上述两个参数在指定异常时,如果指定某个异常的父类,包括这个异常的所有子类异常都会回滚;

    如:no-rollback-for指定的参数是Exception则代表Java中的任何异常都不回滚,

    rollback-for指定的参数是Exception则Java中任何的异常都回滚;

    2)业务类:
    package com.dfbz.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * @author lscl
     * @version 1.0
     * @intro:
     */
    @Service
    public class AccountService {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        /**
         * @param str     : 谁要转账
         * @param target: 转账给谁
         * @param flag:   模拟异常 true:出现异常 false:不出现异常
         */
        public void transfer(String str, String target, Boolean flag) {
    
            jdbcTemplate.update("update account set money=money-500 where name=?", str);
    
            if (flag) {
                // 模拟异常
                int i = 1 / 0;
            }
    
            jdbcTemplate.update("update account set money=money+500 where name=?", target);
        }
        
        /**
         *
         * @param id
         */
        public void findById(Integer id) {
    
            Account account = jdbcTemplate.queryForObject(
                    "select * from account where id=?",
                    new BeanPropertyRowMapper<>(Account.class),
                    id
            );
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    3)测试类
    package com.dfbz.test;
    
    import com.dfbz.service.AccountService;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringRunner;
    
    /**
     * @author lscl
     * @version 1.0
     * @intro:
     */
    @RunWith(SpringRunner.class)
    @ContextConfiguration(locations = "classpath:spring.xml")
    public class Demo01 {
    
        @Autowired
        private AccountService accountService;
    
        @Test
        public void test1(){
            accountService.transfer("a","b",false);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    1.3.3 注解配置

    1)Spring配置
    
    <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"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        
        <context:component-scan base-package="com.dfbz" />
    
        
        <context:property-placeholder location="classpath:jdbc.properties"/>
    
        
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="username" value="${jdbc.username}">property>
            <property name="password" value="${jdbc.password}">property>
            <property name="url" value="${jdbc.url}">property>
            <property name="driverClassName" value="${jdbc.driverClassName}">property>
        bean>
    
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource">property>
        bean>
    
        
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            
            <property name="dataSource" ref="dataSource"/>
        bean>
    
        
        <tx:annotation-driven transaction-manager="transactionManager">tx:annotation-driven>
    
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    2)业务类
    package com.dfbz.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Isolation;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * @author lscl
     * @version 1.0
     * @intro:
     */
    @Service
    public class AccountService {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        /**
         * @param str     : 谁要转账
         * @param target: 转账给谁
         * @param flag:   模拟异常 true:出现异常 false:不出现异常
         */
    /*    @Transactional(
                transactionManager = "transactionManager",              // 事务管理器的名称
                propagation = Propagation.REQUIRED,                     // 传播行为
                isolation = Isolation.DEFAULT,                          // 隔离级别
                timeout = 3000,                                         // 超时时间
                readOnly = false,                                       // 是否只读
                rollbackFor = {MyException.class},       				// 出现该异常回滚
                noRollbackFor = {ArithmeticException.class}             // 出现指定异常不回滚
        )    */
        @Transactional              // 使用注解方式管理事务
        public void transfer(String str, String target, Boolean flag) {
    
            jdbcTemplate.update("update account set money=money-500 where name=?", str);
    
            if (flag) {
                // 模拟异常
                int i = 1 / 0;
            }
    
            jdbcTemplate.update("update account set money=money+500 where name=?", target);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    3)测试类
    package com.dfbz.test;
    
    import com.dfbz.service.AccountService;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringRunner;
    
    /**
     * @author lscl
     * @version 1.0
     * @intro:
     */
    @RunWith(SpringRunner.class)
    @ContextConfiguration(locations = "classpath:spring2.xml")
    public class Demo02 {
    
        @Autowired
        private AccountService accountService;
    
        @Test
        public void test1(){
    
            accountService.transfer("a","b",false);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
  • 相关阅读:
    IT入门知识第七部分《移动开发》(7/10)
    【c++百日刷题计划】 ———— DAY16,刷题百天,养成刷题好习惯
    B2R Raven: 2靶机渗透
    esp32s3通过mqtt协议连接阿里云(wifi+mqtt+vscode+espidf4.4.4+py3.8.7)
    Java面试题消息队列
    Skill Check: Fundamentals of Large Language Models
    MySQL常用语句一章解读数据库的底层原理
    java计算机毕业设计springboot+vue协同过滤算法及在个性化音乐推荐系统
    基于内存的分布式NoSQL数据库Redis(三)常用命令
    win10启动项在什么地方
  • 原文地址:https://blog.csdn.net/Bb15070047748/article/details/128104237