• 第六章:Springmvc事务管理和配置文件xml详解和七大事务和注解@Transactional


    事务的定义

    1.一个事务单元中包含多个数据库表的操作,可能有增、删、改等操作,有一个sql语句执行失败,整个事务都执⾏失败。
    作用:执行增删改时,保持数据的一致、安全,不会像无事务那样出错了仍然提交

    把将要执行的命令步骤给包裹在事务中,使之随时能检测命令的运行过程的异常,
    及时止血,防止半途而废,事务是逻辑上的一组操作,要么都执行,要么都不执行。
    
    • 1
    • 2

    事务开启后

    开启事务后,执⾏的语句均属于当前事务,成功再执⾏ COMIIT,失 败要进⾏ ROLLBACK
    spring事务也是通过动态代理来实现的,spring事务也是借鉴了AOP的思想

    事务数据库执行原理

    数据库会为每⼀个客户端都维护⼀个空间独⽴的缓存区(回滚段),⼀个事务中所有的增删改语句的执⾏结果都会缓存在回滚段中,只有当事务中所有SQL 语句 均正常结束(commit),才会将回滚段中的数据同步到数据库。否则⽆论因为 哪种原因失败,整个事务将回滚 (rollback)。

    需要注意

    事务能否生效与数据库引擎是否支持事务是关键。
    
    • 1

    绕不开的ACID特性

    1.原子性:事务要么全执行,要么全不执行

    原子性的特点:
    1、针对单事务的控制,命令执行的过程中不会被加塞和干扰
    2、针对多个insert,update,delete操作
    典型场景,一个方法中包含多个insert,update,delete操作通过添加事务保证原子性,要么全部成功,要么全部失败
    
    • 1
    • 2
    • 3
    • 4

    2.一致性:事务执行前后,数据完整性一致

    事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的
    在成功提交或失败回滚事务之后以及正在进行的事务之间,数据库始终保持不变的状态,
    如果正在多个表之间更新相关数据,那么查询将看到所有旧值或所有新值,而不会一部分是新值,一部分是旧值。
    
    • 1
    • 2
    • 3

    3.隔离性:一个事务的执行不能被其他事务干扰。

    隔离性的特点:
    事务处理过程中的中间状态应该对外部不可见,换句话说,
    事务在进行过程中是隔离的,事务之间不能互相干扰,不能访问到彼此未提交的数据。 
    即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
    1、针对多事务间数据可见性的控制。
    2、控制加锁的粒度和加锁、释放锁的时机,提高事务的并发能力。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4.持久性:指一个事务一旦提交,对数据库中数据的改变是永久性,接下来的其他操作或故障不应该对其有任何影响。

    何为脏读

    脏读就是读到其他事务没有提交的数据,只要隔离级别不是读未提交(Read Uncommitted)就不会出现。
    举例:
    A用户修改了数据,随后B用户又读出该数据,但A用户因为某些原因取消了对数据的修改,数据恢复原值,此时B得到的数据就与数据库内的数据产生了不一致
    
    • 1
    • 2
    • 3

    何为回滚

    即还原到事务开始前的状态,就像这个事务从来没有执行过一样
    
    • 1

    什么情况下不需要添加事务?

    对于多查询的不进行添加事务,当进行大量的增加、删除、修改表的数据进行添加事务处理。

    两种引擎比较

    1. MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择。
    2. InnoDB用于事务处理应用程序,具有众多特性,包括ACID事务支持。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,为的是提高多用户并发操作的性能。

    不是所有引擎都支持事务的处理

    比如常用的 MySQL 数据库默认使用支持事务的 InnoDB引擎。但是,如果把数据库引擎变为 MyISAM,那么程序也就不再支持事务了!
    大尺寸的数据集趋向于选择InnoDB引擎,因为它支持事务处理和故障恢复。数据库的大小决定了故障恢复的时间长短,InnoDB可以利用事务日志进行数据恢复,这会比较快。

    分布式事务的应用场景

    1、支付
    2、在线下单
    3.金融系统中的银行卡充值(银行系统和金融系统)、
    4.教育系统中下单选课业务(订单系统和选课系统)

    事务的配置

    Spring的配置事务方式:声明式事务处理、注解方式的声明式事务管理

    两种方式
    1.通过注解的形式
    2.通过xml的方式
    
    • 1
    • 2
    • 3

    举例:通过xml 方式配置,
    创建文件SpringContext-transaction.xml

    (1).在Spring核心配置文件中引入相关约束:
    <?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:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
    		http://www.springframework.org/schema/context
    		http://www.springframework.org/schema/context/spring-context.xsd
    		http://www.springframework.org/schema/aop
     		http://www.springframework.org/schema/aop/spring-aop.xsd
     		http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd">
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (2).配置事务管理器–就是对Bean类注入属性值后的类进行事务管理
    id为dataSource_ID1的BasicDataSource类配置文章链接

    <!--配置事务管理器-->
     <bean id="789——transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    //ref=" " 其数据源已经完成了class="org.apache.commons.dbcp.BasicDataSource"的配置 --仔细去看就知道了
                    <property name="dataSource" ref="dataSource_ID1"/>
            </bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (3).配置事务通知
    注意:

    <!-- 配置事务通知-->
            <tx:advice id="Advice" transaction-manager="789——transactionManager">
    <!--                配置事务属性,即哪些方法要执行事务-->
                    <tx:attributes>
    					 <!--自定义所有insert开头的方法配置事务,以下同理 -->
                            <tx:method name="insert*" propagation="REQUIRED"/> 
    					 <!--自定义所有update开头的方法配置事务,以下同理 -->
                            <tx:method name="update*" propagation="REQUIRED"/>
                            <tx:method name="delete*" propagation="REQUIRED"/>
                    </tx:attributes>
            </tx:advice>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (4).配置事务切面

    <!--配置事务切面-->
    <aop:config>
    	<!--要执行的方法在哪个包,意思为:com.wanshi.service下的所有包里面的包含任意参数的所有方法-->
    	<aop:pointcut id="AdviceAop" expression="execution(public * com.xxx.service.*.*(..))"/> 
    	<!-- 配置为AdviceAop执行哪个事务通知 -->
    	<aop:advisor advice-ref="Advice" pointcut-ref="AdviceAop"/> 
    </aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    事务配置的总结

    1.事务配置为事务配置管理即为DataSourceTransactionManager类中dataSource属性赋值
    2.配置事务通知,通知指的是动作,自定义哪些字符串开头的命名方法添加事务动作。
    3.将这些事务动作放在哪些位置或者哪个点上?即配置pointcut,像交警一样设卡查哨
    这里边的expression中execution(public * com.xxx.service..(…))表达式
    4.配置事务切面即将配置好的事务通知和事务的切点交给事务管理。

    关于切入点表达式

    主要关注 返回方法、哪个类、类中哪个方法、方法的对应参数限制
    execution(* com.test1.ce.epet.dao.mpl.*.*(..)) 
    这是com.test1.ce.epet.dao.impl 包下所有的类的所有方法。
    配置事务的方法必须是 public
    第一个 * 代表所有的返回值类型
    第二个 *代表所有的类
    第三个 * 代表类中所有方法 
    最后一个…代表所有的参数。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Spring七大事务传播行为

    1、propagation_required:支持当前事务,无事务、另起新事物
    2、propagation_required-new:新建事务,若有旧事务,挂起。
    3、propagation_supports:支持雪前事雾,无事务,以非事务执行
    4、propagation_mandatory:以事务方式执行,无事务,抛异常
    5、propagation_not_supported:不支持事务,如有事务,挂起
    6、propagation_never:以非事务执行,有事务,抛异常
    7、propagation_nested:内切事务

    1、propagation_required

    加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务。
    比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED,那么由于执行ServiceA.methodA的时候,ServiceA.methodA已经起了事务。
    这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA的事务内部,就不再起新的事务。
    而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
    这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。
    即使ServiceB.methodB的事务已经被提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚

    2.propagation_required-new

    比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为ROPAGATION_REQUIRES_NEw。
    那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,
    ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,他才继续执行。
    他与PROPAGATION_REQUIRED的事务区别在于事务的回滚程度。
    因为ServiceB.methodB是新起一个事务,那么就是存在>两个不同的事务。
    如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。
    如果ServiceB.methodB失败回滚,抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。

    3.propagation_supports

    如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行。

    4.propagation_mandatory

    必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常。

    5.propagation_not_supported

    当前不支持事务。
    比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED。
    那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而ServiceB.methodB以非事务的状态运行完,再继续ServiceA.methodA的事务。

    6.propagation_never

    不能在事务中运行。
    假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED,而ServiceB.methodB的事务级别是PROPAGATION_NEVER,
    那么ServiceB.methodB就要抛出异常了。

    7、propagation_nested:内切事务

    没有在EJB中定义
    理解Nested的关键是savepoint
    他与PROPAGATION_REQUIRES_NEW的区别是: PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立;而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。

    外围方法开启事务的情况下**Propagation.NESTED**修饰的内部方法属于外部事务的子事务,
    外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务。
    举例
    @Transactional
    @Override
    public void transaction_nested_nested_exception_try(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);
        User2 user2=new User2();
        user2.setName("李四");
        try {
            user2Service.addNestedException(user2);
        } catch (Exception e) {
            System.out.println("方法回滚");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    ServiceA {
    “事务漓性配置为PROPAGATION_REQUIRED"
    void methodA(){
    	try {
    iisavepoint
    ServiceB.methodB();!PROPAGATION_NESTED 级别
    		}
    catch (SomeException)  {
    		//执行其他业务
    						}
    			}
    }
    ServiceC.methodC();
    也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,
    比如
    ServiceC.methodC,继续执行,来尝试完成自己的事务。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    使用注解的方式声明事务并且加深理解Spring七大事务

    使用注解的方式声明事务并且加深理解Spring七大事务

    PROPAGATION_REQUIRED

    支持当前事务,如果不存在则创建一个新事务。最常用的选择
    场景一:service层方法内部开启事务

    @Service
    public class test1ServiceImpl implements test1Service {
        //省略其他...
        @Override
        #方法内部开启事务
        @Transactional(propagation = Propagation.REQUIRED)
        public void addRequired(test1 user){
            test1Mapper.update(user);
        }
    }
    @Service
    public class test2ServiceImpl implements test2Service {
        //省略其他...
        @Override
        @Transactional(propagation = Propagation.REQUIRED)
        public void addRequired(test2 user){
            test2Mapper.update(user);
        }
        @Override
        #方法内部开启事务
        @Transactional(propagation = Propagation.REQUIRED)
        public void addRequiredException(test2 user){
            test2Mapper.update(user);
            throw new Exception();
        }
    }
    
    • 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

    结论:在外围方法未开启事务的情况下,PROPAGATION_REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰.

    ============================================
    
    • 1

    场景2:外部开启事务
    开发中使用较高的场景(默认情况)

       @Override
        @Transactional(propagation = Propagation.REQUIRED)
        public void transaction_exception_required_required(){
            User1 user1=new User1();
            user1.setName("张三");
            user1Service.addRequired(user1);
    
            User2 user2=new User2();
            user2.setName("李四");
            user2Service.addRequired(user2);
    
            throw new RuntimeException();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    结论:外围开启事务,内部方法出现一个异常均回滚。原因是外围开启事务,PROPAGATION_REQUIRED修饰的所有内部方法会加入到外围方法的事务中,内部事务和外部事务均属于同一个事务。

    ============================
    
    • 1
    PROPAGATION_REQUIRES_NEW

    创建一个新事务,如果当前事务存在,则暂停当前事务
    场景一:外部不开启事务
    Service1

    @Service
    public class test1ServiceImpl implements test1Service {
        //省略其他...
        @Override
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void addRequiresNew(test1 user){
            test1Mapper.insert(user);
        }
        @Override
        @Transactional(propagation = Propagation.REQUIRED)
        public void addRequired(test1 user){
            test1Mapper.insert(user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Service2

    @Service
    public class User2ServiceImpl implements User2Service {
        //省略其他...
        @Override
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void addRequiresNew(User2 user){
            user2Mapper.insert(user);
        }
        
        @Override
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void addRequiresNewException(User2 user){
            user2Mapper.insert(user);
            throw new RuntimeException();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    验证方法1:外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,外围方法抛出异常回滚不会影响内部方法。
    验证方法2:张三正常插入,李四无法插入,李四在自己的方法中开启新事务,但是抛出异常回滚。
    场景2:外部开启事务

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_required_requiresNew_requiresNew_exception_try(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);
        User2 user3=new User2();
        user3.setName("王五");
        try {
            user2Service.addRequiresNewException(user3);
        } catch (Exception e) {
            System.out.println("回滚");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    结论:外围方法开启事务的情况下,Propagation.REQUIRES_NEW修饰的内部方法全部单独开启独立事务,不同的内部方法、外部事务间相互独立,互不干扰。

    点解连接
    第一章:Spring流程执行步骤
    第二章:Spring核心思想和IOC和AOP依赖注入
    第三章:Spring常用注解解释
    第四章:Spring七大核心模块Bean、Core、Context
    第五章:Spring细分一如何让mapper文件夹下的SQL语句对数据库生效,jdbc和mybatis-config.xml
    第六章:Springmvc事务管理和配置文件xml详解和七大事务和注解@Transactional

  • 相关阅读:
    基于逻辑回归和神经网络识别手写数字(从0到9)(Matlab代码实现)
    如何提升Java项目质量,代码是关键
    插入排序.
    想要精通算法和SQL的成长之路 - 行程和用户
    大模型时代的基础设施:云原生峰会 KubeCon 2023 中国站重磅来袭!
    Go微服务框架及基础平台选择
    【文件操作的重难点详解】
    【计网】计算机网络概述
    GoEXP规则变更通知 l 信创额外奖励活动
    安装多个不同版本chrome浏览器
  • 原文地址:https://blog.csdn.net/beiback/article/details/127968755