• Spring事务


    1 事务简介

    1.1 什么是事务

    事务是数据库管理系统中的一个重要概念,它是由一组操作组成的逻辑单元,这些操作要么全部成功执行,要么全部回滚到初始状态,以确保数据的一致性和完整性。

    1.2 事务的特性(ACID)

    • 原子性(Atomicity):事务中的所有操作要么全部成功执行,要么全部失败回滚。如果任何操作失败,整个事务将被回滚到起始状态,不会对数据库造成任何改变。
    • 一致性(Consistency):事务的执行使数据库从一个一致状态转变为另一个一致状态。在事务开始之前和结束之后,数据库必须满足预先定义的一致性规则。
    • 隔离性(Isolation):每个事务的操作应该与其他事务的操作相互隔离,以防止互相干扰。事务应该以一种看起来它们是并发执行的方式运行,而不会相互干扰。
    • 持久性(Durability):一旦事务提交,其所做的更改应该永久保存在数据库中,即使系统发生故障或重新启动。

    1.3 事务的隔离级别

    事务的隔离级别是指多个并发事务之间的相互影响程度。

    隔离级别定义了一个事务在读取或修改数据时,能否看到其他事务对同一数据所做的更改,以及能否被其他事务看到自己所做的未提交更改。

    关系型数据库中定义了四个标准的隔离级别,每个级别提供了不同的数据一致性和并发性能之间的权衡:

    • 读未提交(Read Uncommitted):最低级别的隔离级别,允许一个事务读取另一个事务尚未提交的数据。这种级别可能导致脏读(Dirty Read)
      • 脏读(Dirty Read):事务可以看到其他事务“尚未提交”的修改。如果另一个事务回滚,那么当前事务读到的数据就是脏数据。
    • 读已提交(Read Committed):在这个级别下,一个事务只能读取到已经提交的数据。这样可以避免脏读,但可能会导致不可重复读(Non-Repeatable Read)
      • 不可重复读(Non-Repeatable Read):在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
    • 可重复读(Repeatable Read):在这个级别下,一个事务在执行期间多次读取同一数据,将会看到一致的结果。其他事务对该数据的修改将被阻塞,直到当前事务结束。这样可以避免脏读和不可重复读,但可能会导致幻读(Phantom Read)
      • 幻读(Phantom Read):在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。
    • 串行化(Serializable):最高级别的隔离级别,要求事务串行执行,完全隔离了并发事务之间的影响。这种级别可以避免脏读、不可重复读和幻读,但对并发性能有较大的影响,因为事务需要顺序执行。
    隔离级别脏读不可重复读幻读
    READ-UNCOMMITTED
    READ-COMMITTED×
    REPEATABLE-READ××
    SERIALIZABLE×××

    2 spring的事务支持

    Spring框架提供了全面的事务管理支持,使得在应用中使用事务变得更加简单和灵活。Spring的事务管理支持主要通过以下几个关键组件来实现:

    • 事务管理器(Transaction Manager):Spring通过事务管理器来统一管理事务。事务管理器负责处理事务的开始、提交、回滚等操作,并协调底层的事务资源。
    • 事务定义(Transaction Definition):事务定义描述了事务的隔离级别传播行为超时设置等属性。Spring提供了多种方式来定义事务,包括编程式事务声明式事务
    • 事务切面(Transaction Aspect):Spring使用切面(Aspect) 来实现声明式事务。通过AOP的方式,将事务逻辑织入到业务逻辑中,使得事务的管理与业务逻辑解耦。
    • 事务注解(Transaction Annotation):Spring支持使用注解的方式来声明事务。通过在方法或类上添加@Transactional注解,可以指定事务的属性,如隔离级别、传播行为等。

    2.1 事务注解(Transaction Annotation)

    事务注解(Transaction Annotation)是Spring框架中用于声明式事务管理的一种方式。通过在方法或类上添加@Transactional注解,可以指定方法或类的事务属性,从而实现对方法或类的事务管理。

    使用事务注解可以简化事务管理的配置,使得开发者可以通过注解的方式来定义事务的行为,而无需显式编写事务管理的代码。

    2.1.1 注解的属性

    • isolation(隔离级别):指定事务的隔离级别,默认为数据库的默认隔离级别。
      • READ-UNCOMMITTED(读未提交)
      • READ-COMMITTED(读已提交)
      • REPEATABLE-READ(可重复读)
      • SERIALIZABLE(串行化)
    • propagation(传播行为):指定事务的传播行为,即在方法调用链中如何传播事务,默认为REQUIRED。
      • REQUIRED:如果当前存在事务,则加入该事务;否则创建一个新事务。这是默认的传播行为。
      • SUPPORTS:如果当前存在事务,则加入该事务;否则以非事务方式执行。
      • MANDATORY:必须在一个已有的事务中执行;否则抛出异常。
      • REQUIRES_NEW:创建一个新的事务,并在它自己的事务内执行。如果当前存在事务,则挂起该事务。
      • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则挂起该事务。
      • NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
      • NESTED:如果当前存在事务,则在嵌套事务内执行;否则创建一个新事务。嵌套事务是独立于外部事务的,它有自己的提交和回滚操作。如果外部事务回滚,则嵌套事务也会回滚。但是,如果嵌套事务回滚,则只会回滚嵌套事务本身,而不会影响到外部事务。
    • readOnly(只读):指定事务是否为只读事务,默认为false。如果设置为true,表示事务只读,不会对数据库进行修改操作,可以提高性能。
    • timeout(超时时间):指定事务的超时时间,单位为秒。如果事务执行时间超过指定的超时时间,事务将被强制回滚,默认为-1,表示没有超时限制。
    • rollbackFor(回滚异常):指定需要回滚事务的异常类型数组。当方法抛出指定类型的异常时,事务将回滚。
    • noRollbackFor(不回滚异常):指定不需要回滚事务的异常类型数组。当方法抛出指定类型的异常时,事务将不会回滚。

    2.1.2 事务注解的使用

    首先应在全局配置文件,如applicationContext.xml中配置事务管理器开启注解事务

    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p" 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
           http://www.springframework.org/schema/beans/spring-beans.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
            http://www.springframework.org/schema/tx
           https://www.springframework.org/schema/tx/spring-tx.xsd">
    
    
            <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
                    <property name="url" value="jdbc:mysql:///mybatis">property>
                    <property name="username" value="root">property>
                    <property name="password" value="123456">property>
                    <property name="driverClassName" value="com.mysql.jdbc.Driver">property>
            bean>
            
            <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    
                    <property name="dataSource" ref="dataSource">property>
            bean>
    
            <context:component-scan base-package="com.yy">context:component-scan>
    
            <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                    <property name="dataSource" ref="dataSource">property>
            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

    配置完成后可在方法或类上利用事务注解来进行声明式事务管理

    public class BankService {
        //@Autowired
        private BankDao bankDao;
    
        public BankDao getBankDao() {
            return bankDao;
        }
    
        public void setBankDao(BankDao bankDao) {
            this.bankDao = bankDao;
        }
    	//利用事务注解来进行事务管理
        @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ,timeout = 1,readOnly = false,rollbackFor = Exception.class)
        public void countMoney(){
            bankDao.addMoney();
            System.out.println("------");
            bankDao.reduceMoney();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    注意:一般情况下,事务注解(Transaction Annotation)会被应用在Service层。
    这是因为Service层通常是应用程序的业务逻辑层,包含了多个DAO层的方法调用,需要确保整个Service方法的执行是一个原子操作,确保这些操作要么全部成功,要么全部回滚。这样可以避免数据不一致的问题,提高系统的稳定性和可靠性。

    2.2 事务切面(Transaction Aspect)

    事务切面(Transaction Aspect)是Spring框架中用于实现声明式事务的关键组件之一。

    它通过AOP(Aspect-Oriented Programming)的方式将事务逻辑织入到业务逻辑中,实现了事务的管理与业务逻辑的解耦。

    2.2.1 事务切面的组成部分

    • 切点(Pointcut):切点定义了哪些方法需要被事务增强处理。它可以通过表达式、注解或者自定义规则来指定目标方法。
    • 通知(Advice):通知定义了在切点处执行的具体逻辑。在事务切面中,通常使用的是Around通知,即在目标方法执行前后添加事务管理的逻辑。
    • 事务管理器(Transaction Manager):事务管理器负责处理事务的开始、提交、回滚等操作。在事务切面中,需要配置一个事务管理器,用于协调底层的事务资源。
    • 事务属性(Transaction Attributes):事务属性定义了事务的隔离级别、传播行为、超时设置等属性。在事务切面中,可以通过注解或者XML配置的方式来指定事务属性。

    2.2.2 优点

    • 通过配置事务切面,Spring会自动将切面织入到目标方法中,实现对目标方法的事务管理。当目标方法被调用时,事务切面会拦截方法的执行,并根据事务属性进行事务的开启、提交或回滚等操作。
    • 使用事务切面可以大大简化事务管理的代码,提高开发效率。同时,由于事务逻辑与业务逻辑解耦,使得系统的可维护性和可测试性都得到了提升。

    2.2.3 事务切面的使用

    首先应在全局配置文件,如applicationContext.xml中配置事务管理器配置通知,切面与切入点

    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p" 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
           http://www.springframework.org/schema/beans/spring-beans.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
            http://www.springframework.org/schema/tx
           https://www.springframework.org/schema/tx/spring-tx.xsd">
    
    
            <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
                    <property name="url" value="jdbc:mysql:///mybatis">property>
                    <property name="username" value="root">property>
                    <property name="password" value="123456">property>
                    <property name="driverClassName" value="com.mysql.jdbc.Driver">property>
            bean>
            
            <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    
                    <property name="dataSource" ref="dataSource">property>
            bean>
    
            <context:component-scan base-package="com.yy">context:component-scan>
    
            <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                    <property name="dataSource" ref="dataSource">property>
            bean>
    
            <tx:advice id="txadvice">
    
                    <tx:attributes>
                            <tx:method name="countMoney" propagation="REQUIRED"/>
    
    
                    tx:attributes>
            tx:advice>
    
    
            <aop:config>
    
                    <aop:pointcut id="p" expression="execution(* com.yy.service.BankService.countMoney(..))" />
    
                    <aop:advisor advice-ref="txadvice" pointcut-ref="p">aop:advisor>
            aop:config>
    
            <bean id="bankDaoImpl" class="com.yy.dao.impl.BankDaoImpl">
                    <property name="jdbcTemplate" ref="jdbcTemplate">property>
            bean>
    
            <bean id="bankService" class="com.yy.service.BankService">
                    <property name="bankDao" ref="bankDaoImpl">property>
            bean>
    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

    此时,只需在service层使用对应方法即可

    public class BankService {
        //@Autowired
        private BankDao bankDao;
    
        public BankDao getBankDao() {
            return bankDao;
        }
    
        public void setBankDao(BankDao bankDao) {
            this.bankDao = bankDao;
        }
    	//由于在全局配置文件中使用了事务切面,这里就无需使用事务注解来进行事务管理了
        //@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ,timeout = 1,readOnly = false,rollbackFor = Exception.class)
        public void countMoney(){
            bankDao.addMoney();
            System.out.println("------");
            bankDao.reduceMoney();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    Goland设置头注释
    java-net-php-python-7jsp在线购物计算机毕业设计程序
    【知识点随笔分析 | 第八篇】什么是布谷鸟过滤器(缓解Redis穿透)
    Windows下MySQL的安装和删除
    【Hadoop】学习笔记(二)
    [深度学习]yolov9+deepsort+pyqt5实现目标追踪
    时间、空间复杂度的例题详解
    CVE-2022-31137 Roxy-WI未经身份验证的远程代码执行漏洞复现
    HTML静态网页作业——澳门英文旅游网站设计与实现HTML+CSS+JavaScript
    【图像处理】小波编码图像中伪影和纹理的检测(Matlab代码实现)
  • 原文地址:https://blog.csdn.net/qq_58216564/article/details/134088456