Spring5事务管理

本文详细介绍了Spring事务管理的概念,包括ACID特性、事务操作环境搭建以及在转账场景中的应用。在没有事务的情况下,转账操作可能因异常导致数据不一致。通过Spring的声明式事务管理,可以利用@Transactional注解实现事务的自动回滚,确保数据的完整性和一致性。文章还探讨了事务的传播行为、隔离级别、超时和只读属性,并提供了基于注解和XML配置的事务管理方式。
摘要由CSDN通过智能技术生成

事务概念复习

什么是事务?

  • 什么是事务?

    事务是数据库操作的最基本单元,逻辑上的一组操作,要么都成功,要么都失败

  • 典型事务场景:银行转账

    比如:从 A 账户向 B 账户中转账 10000,将 A 账户的钱减去 10000,将 B 账户的钱增加 10000

事务四大特性(ACID)

  • A:原子性(说明事务是最小的工作单元,不可再分)
  • C:一致性(所有事务要求,在同一个事务当中,所有操作必须同时成功,或者同时失败,以保证数据的安全性)
  • I:隔离性(A 事务和 B 事务之间具有一定的隔离(比如:教室 A 和教室 B 中间有一道墙,这道墙就是隔离性))
  • D:持久性(事务最终结束的一个保障,事务提交,就相当于将没有保存到硬盘上的数据保存到硬盘上)

详情可看https://blog.csdn.net/Cicci_/article/details/121952760?spm=1001.2014.3001.5501

搭建事务操作环境

  • 案例:使用事务实现简单的转账操作

这里会给大家演示没有事务时出现的问题和解决的方案

一、创建数据库表(t_account),添加记录

表结构

在这里插入图片描述

表数据

在这里插入图片描述

二、创建 service,搭建 dao,完成对象创建和注入关系

  • service 注入 dao,在 dao 中注入 JdbcTemplate,在 JdbcTemplate 中注入DataSource

    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:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    ">
        <!-- 数据库连接池 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone">
            <property name="url" value="jdbc:mysql:///spring_test" />
            <property name="username" value="root" />
            <property name="password" value="123456" />
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        </bean>
    
        <!-- 开启组件扫描 -->
        <context:component-scan base-package="com.laoyang.spring" />
    
        <!-- 创建 JdbcTemplate 对象 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!-- 注入 dataSource -->
            <property name="dataSource" ref="dataSource" />
        </bean>
    </beans>
    

    UserDao 接口

    package com.laoyang.spring.dao;
    
    public interface UserDao {
        
    }
    

    UserDaoImpl 实现类

    package com.laoyang.spring.dao;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class UserDaoImpl implements UserDao {
        /**
         * 注入 JdbcTemplate
         */
        @Autowired
        private JdbcTemplate jdbcTemplate;
    }
    

    UserService 类

    package com.laoyang.spring.service;
    
    import com.laoyang.spring.dao.UserDao;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserService {
        /**
         * 注入 DAO
         */
        @Autowired
        private UserDao userDao;
    }
    

三、在 dao 和 service 创建对应的方法

  • dao 创建两个方法(多钱和少钱的方法)

    package com.laoyang.spring.dao;
    
    public interface UserDao {
        /**
         * 多钱
         */
        public void addMoney();
    
        /**
         * 少钱
         */
        public void reduceMoney();
    }
    
    package com.laoyang.spring.dao;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class UserDaoImpl implements UserDao {
        /**
         * 注入 JdbcTemplate
         */
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        /**
         * 多钱
         * 需求:把 lucy 转的钱增加到 mary 的账户上
         */
        @Override
        public void addMoney() {
            String sql = "update t_account set money = money+? where username = ?";
            Object[] objects = {100, "mary"};
            int update = jdbcTemplate.update(sql, objects);
            if (update > 0) {
                System.out.println("收款成功!");
                return;
            }
            System.out.println("收款失败!");
        }
    
        /**
         * 少钱
         * 需求:lucy 转账 100 给 mary
         */
        @Override
        public void reduceMoney() {
            String sql = "update t_account set money = money-? where username = ?";
            Object[] objects = {100, "lucy"};
            int update = jdbcTemplate.update(sql, objects);
            if (update > 0) {
                System.out.println("转账成功!");
                return;
            }
            System.out.println("转账失败!");
        }
    }
    
  • service 创建一个方法(转账的方法)

    package com.laoyang.spring.service;
    
    import com.laoyang.spring.dao.UserDao;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserService {
        /**
         * 注入 DAO
         */
        @Autowired
        private UserDao userDao;
    
        /**
         * 转账操作
         */
        public void transferAccounts () {
            // 先减去 luay 的 100 元
            userDao.reduceMoney();
    
            // 在增加 mary 的 100 元
            userDao.addMoney();
        }
    }
    
  • 测试效果

    package com.laoyang.spring.test;
    
    import com.laoyang.spring.service.UserService;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class UserTest {
        @Test
        public void testTransferAccounts() {
            ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
            UserService userService = context.getBean("userService", UserService.class);
    
            userService.transferAccounts();
        }
    }
    
  • 数据库效果

    在这里插入图片描述

小问题

以上代码正常执行是没有任何问题的,但是如果在执行过程中出现了异常,就会出现一些问题,比如:

  • lucy 转出 100 元之后出现了异常,导致 mary 没有接收到转过来的 100 元(也就是说你钱没了,但是事没办成)

演示出现问题时的效果

  • 先将数据库中的数据手动设置回每个人 1000 元

  • 手动在 service 转账方法中制造一个异常

    package com.laoyang.spring.service;
    
    import com.laoyang.spring.dao.UserDao;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserService {
        /**
         * 注入 DAO
         */
        @Autowired
        private UserDao userDao;
    
        /**
         * 转账操作
         */
        public void transferAccounts () {
            // 先减去 luay 的 100 元
            userDao.reduceMoney();
    
            // 制造异常
            int i = 10 / 0;
    
            // 在增加 mary 的 100 元
            userDao.addMoney();
        }
    }
    
  • 测试效果

    package com.laoyang.spring.test;
    
    import com.laoyang.spring.service.UserService;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class UserTest {
        @Test
        public void testTransferAccounts() {
            ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
            UserService userService = context.getBean("userService", UserService.class);
    
            userService.transferAccounts();
        }
    }
    
  • 数据库效果

    在这里插入图片描述

我们可以明显的看到,lucy 虽然转出了 100 元,但是这 100 元没有成功转到 mary 的账户上

解决方案

使用事务进行解决,步骤如下:

  1. 开启事务
  2. 进行业务操作(使用 try - catch 捕获异常)
  3. 如果没有出现异常,则提交事务
  4. 如果出现了异常,则回滚事务
package com.laoyang.spring.service;

import com.laoyang.spring.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;

@Service
public class UserService {
    /**
     * 注入 DAO
     */
    @Autowired
    private UserDao userDao;

    /**
     * 转账操作
     * 以下是使用普通方式解决,但是比较麻烦,所以只把步骤写了一下,,没有写实现的操作
     */
    public void transferAccounts () {
        // 1、开启事务

        // 2、进行业务操作

        try {
            // 先减去 luay 的 100 元
            userDao.reduceMoney();

            // 模拟异常
            int i = 10 / 0;

            // 在增加 mary 的 100 元
            userDao.addMoney();

            // 3、如果没有出现异常,则提交事务
        } catch (Exception e) {
            // 4、如果出现了异常,则回滚事务
        }
    }
}

虽然现在也可以用代码实现,但是既然是学 Spring,所以还是使用 Spring 的方式实现吧(而且 Spring 的方式更加简单),下面就会为大家一 一介绍 Spring 事务的使用方法

Spring 事务管理

说明

  • 事务添加到 JavaEE 三层结构中的 Service 层(业务逻辑层)

  • 在 Spring 中进行事务管理操作有两种方式编程式事务管理声明式事务管理

    开发中一般都是使用声明式事务管理,因为比较简便。

    上面解决方案中说的代码实现就是编程式事务管理,这种方式比较繁琐。

  • 声明式事务管理:

    • 基于注解方式实现(推荐)
    • 基于 xml 配置文件方式实现
  • Spring 声明式事务管理,底层使用了 AOP 原理

Spring 事务管理提供的 API

  • PlatformTransactionManager 接口:代表事务管理器,这个接口针对不同的框架提供了不同的实现类

在这里插入图片描述

声明式事务管理(基于注解实现)

  • 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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    ">
        <!-- 数据库连接池 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone">
            <property name="url" value="jdbc:mysql:///spring_test" />
            <property name="username" value="root" />
            <property name="password" value="123456" />
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        </bean>
    
        <!-- 开启组件扫描 -->
        <context:component-scan base-package="com.laoyang.spring" />
    
        <!-- 创建 JdbcTemplate 对象 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!-- 注入 dataSource -->
            <property name="dataSource" ref="dataSource" />
        </bean>
        
        <!-- ====================下面是新增的代码==================== -->
    
        <!-- 创建事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 注入数据源 -->
            <property name="dataSource" ref="dataSource" />
        </bean>
    </beans>
    
  • 2、在 Spring 配置文件中引入 tx 名称空间

    <?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: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 http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
    ">
    
  • 3、开启事务注解

    <?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: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 http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
    ">
        <!-- 数据库连接池 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone">
            <property name="url" value="jdbc:mysql:///spring_test" />
            <property name="username" value="root" />
            <property name="password" value="123456" />
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        </bean>
    
        <!-- 开启组件扫描 -->
        <context:component-scan base-package="com.laoyang.spring" />
    
        <!-- 创建 JdbcTemplate 对象 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!-- 注入 dataSource -->
            <property name="dataSource" ref="dataSource" />
        </bean>
    
        <!-- 创建事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 注入数据源 -->
            <property name="dataSource" ref="dataSource" />
        </bean>
        
         <!-- ====================下面是新增的代码==================== -->
        
        <!--
            开启事务注解
            transaction-manager:通过哪个事务管理器来开启注解
         -->
        <tx:annotation-driven transaction-manager="transactionManager" />
    </beans>
    
  • 4、在 service 类上(或者 service 类里面的方法上)添加事务注解(@Transactional)

    @Transactional:该注解可以加在类 / 方法上

    (如果添加到类上,则表示这个类中的所有方法都添加事务,如果添加到方法上,则表示为该方法添加事务)

    package com.laoyang.spring.service;
    
    import com.laoyang.spring.dao.UserDao;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * @Transactional:该注解可以加在类 / 方法上,
     * 		如果添加到类上,则表示这个类中的所有方法都添加事务
     * 		如果添加到方法上,则表示为该方法添加事务
     * @Date 2021/12/21 17:27
     */
    @Service
    @Transactional
    public class UserService {
        /**
         * 注入 DAO
         */
        @Autowired
        private UserDao userDao;
    
        /**
         * 转账操作
         */
        public void transferAccounts () {
            // 先减去 luay 的 100 元
            userDao.reduceMoney();
    
            // 模拟异常
            int i = 10 / 0;
    
            // 在增加 mary 的 100 元
            userDao.addMoney();
        }
    }
    
  • 测试效果

    package com.laoyang.spring.test;
    
    import com.laoyang.spring.service.UserService;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.transaction.PlatformTransactionManager;
    
    public class UserTest {
        @Test
        public void testTransferAccounts() {
            ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
            UserService userService = context.getBean("userService", UserService.class);
    
            userService.transferAccounts();
        }
    }
    
  • 数据库效果

    在这里插入图片描述

这波我们可以看到,添加事务之后,如果中途出现异常,也不会影响到数据,就比如 lucy 刚开始转了 100 元出去,但是因为中途出现了问题,钱又退回来了

@Transactional 注解参数说明

  • 刚才在 service 类上添加了 @Transactional 注解,在这个注解中可以配置事务相关参数

在这里插入图片描述

主要使用框起来的这几个参数

propagation:事务传播行为

  • 事务传播行为:多事务方法之间进行调用,这个过程中事务进行管理的方式就被称为事务传播行为

    在这里插入图片描述

  • Spring 框架事务传播行为有 7 种

    • REQUIRED:如果有事务在运行,当前的方法就在这个事务内运行,否则就启动一个新的事务,并在自己的事务内运行

      如果 add 方法本身就有事务,调用 update 方法之后,update 使用当前 add 方法里面的事务

      如果 add 方法本身没有事务,调用 update 方法之后,创建新的事务

    • REQUIRED_NEW:当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起

      使用 add 方法调用 update 方法,add 不管有没有事务,都会创建新的事务

    • 以上两种是比较常用的事务传播行为,其余的大家也可以了解一下,然后根据自己的需求来进行使用

在这里插入图片描述

// 设置事务传播行为
@Transactional(propagation = Propagation.REQUIRED)

isolation:事务隔离级别

  • 事务中的隔离性:多事务操作之间互不影响(如果不考虑隔离性,就会出现很多问题)

  • 主要有三个读问题:脏读、不可重复读、幻影读

  • 脏读:一个未提交事务读取到另一个未提交事务的数据

  • 不可重复读:一个未提交事务读取到另一个已提交事务的数据

  • 幻影读:一个未提交事务每次读取的都是开启事务时的数据

  • 通过设置事务隔离级别,解决读问题(下图中的无表示可解决,有表示可能出现该问题)

    在这里插入图片描述

详情可看https://blog.csdn.net/Cicci_/article/details/121952760?spm=1001.2014.3001.5501

// 设置隔离级别
@Transactional(isolation = Isolation.REPEATABLE_READ)

timeout:超时时间

  • 事务需要在一定的时间内进行提交,如果不提交就会进行回滚
  • 默认超时时间是 -1,以秒为单位
// 设置超时时间
@Transactional(timeout = 5)

readOnly:是否只读

  • :查询操作
  • :增删改操作
  • readOnly 默认值为 false,表示可以查询,也可以进行增删改操作
  • 注意:当 readOnly 设置为 true 后,就只能执行查询操作!
// 设置只读
@Transactional(readOnly = true)

rollbackFor:回滚

  • 设置出现哪些异常进行事务回滚
// 设置出现哪些异常进行事务回滚
@Transactional(rollbackFor = Exception.class)

noRollbackFor:不回滚

  • 设置出现哪些异常不进行事务回滚
// 设置出现哪些异常进行事务回滚
@Transactional(noRollbackFor = Exception.class)

声明式事务(基于 xml 文件实现)

  • 这里还是使用前面的 service 和 dao,所以这里就不写了

  • 1、在 Spring 配置文件中进行配置

    第一步:配置事务管理器

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 引入 aop 和 tx 名称空间 -->
    <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:tx="http://www.springframework.org/schema/tx"
           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/context http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    ">
        <!-- 数据库连接池 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone">
            <property name="url" value="jdbc:mysql:///spring_test" />
            <property name="username" value="root" />
            <property name="password" value="123456" />
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        </bean>
    
        <!-- 开启组件扫描 -->
        <context:component-scan base-package="com.laoyang.spring" />
    
        <!-- 创建 JdbcTemplate 对象 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!-- 注入 dataSource -->
            <property name="dataSource" ref="dataSource" />
        </bean>
    
        <!-- 创建事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 注入数据源 -->
            <property name="dataSource" ref="dataSource" />
        </bean>
    </beans>
    

    第二步:配置通知

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 引入 aop 和 tx 名称空间 -->
    <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:tx="http://www.springframework.org/schema/tx"
           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/context http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    ">
        <!-- 数据库连接池 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone">
            <property name="url" value="jdbc:mysql:///spring_test" />
            <property name="username" value="root" />
            <property name="password" value="123456" />
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        </bean>
    
        <!-- 开启组件扫描 -->
        <context:component-scan base-package="com.laoyang.spring" />
    
        <!-- 创建 JdbcTemplate 对象 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!-- 注入 dataSource -->
            <property name="dataSource" ref="dataSource" />
        </bean>
    
        <!-- 创建事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 注入数据源 -->
            <property name="dataSource" ref="dataSource" />
        </bean>
    
        <!-- 配置通知 -->
        <tx:advice id="txAdvice">
            <!-- 配置事务相关参数 -->
            <tx:attributes>
                <!--
                指定哪种规则的方法上添加事务
                name:对应的方法名
                propagation就是注解中的参数,可根据自己的需求来进行对应的事务配置
                > 这里只是演示效果,就只写一个 propagation 了
                 -->
                <!-- 方式一:直接写对应的方法名 -->
                <tx:method name="transferAccounts" propagation="REQUIRED"/>
    
                <!-- 方式二:写符合规则的方法,这里表示以 transfer 开头的所有方法 -->
    <!--            <tx:method name="transfer*"/>-->
            </tx:attributes>
        </tx:advice>
    </beans>
    

    第三步:配置切入点和切面

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 引入 aop 和 tx 名称空间 -->
    <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:tx="http://www.springframework.org/schema/tx"
           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/context http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    ">
        <!-- 数据库连接池 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="clone">
            <property name="url" value="jdbc:mysql:///spring_test" />
            <property name="username" value="root" />
            <property name="password" value="123456" />
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        </bean>
    
        <!-- 开启组件扫描 -->
        <context:component-scan base-package="com.laoyang.spring" />
    
        <!-- 创建 JdbcTemplate 对象 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!-- 注入 dataSource -->
            <property name="dataSource" ref="dataSource" />
        </bean>
    
        <!-- 创建事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 注入数据源 -->
            <property name="dataSource" ref="dataSource" />
        </bean>
    
        <!-- 配置通知 -->
        <tx:advice id="txAdvice">
            <!-- 配置事务相关参数 -->
            <tx:attributes>
                <!--
                指定哪种规则的方法上添加事务
                name:对应的方法名
                propagation就是注解中的参数,可根据自己的需求来进行对应的事务配置
                > 这里只是演示效果,就只写一个 propagation 了
                 -->
                <!-- 方式一:直接写对应的方法名 -->
                <tx:method name="transferAccounts" propagation="REQUIRED"/>
    
                <!-- 方式二:写符合规则的方法,这里表示以 transfer 开头的所有方法 -->
    <!--            <tx:method name="transfer*"/>-->
            </tx:attributes>
        </tx:advice>
    
        <!-- 配置切入点和切面 -->
        <aop:config>
            <!-- 配置切入点 -->
            <aop:pointcut id="pt" expression="execution(* com.laoyang.spring.service.UserService.*(..))"/>
    
            <!--
                配置切面
                advice-ref:通知
                pointcut-ref:切入点
             -->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
        </aop:config>
    </beans>
    
  • 测试效果

    package com.laoyang.spring.test;
    
    import com.laoyang.spring.service.UserService;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.transaction.PlatformTransactionManager;
    
    public class UserTest {
        @Test
        public void testTransferAccounts2() {
            ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
            UserService userService = context.getBean("userService", UserService.class);
    
            userService.transferAccounts();
        }
    }
    

    因为我们之前在 service 中手动制造了一个 bug,所以现在执行肯定的报错的,并且数据库中的数据是不收到影响的

  • 数据库效果

    在这里插入图片描述

声明式事务(完全注解方式)

  • 不使用 xml 配置文件,只使用注解进行实现

  • 1、创建配置类,使用配置类替代 xml 配置文件

    package com.laoyang.spring.config;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    import javax.sql.DataSource;
    
    /**
     * @ClassName TxConfig
     * @Description: 配置类
     * @Author Laoyang
     * @Date 2021/12/21 22:27
     */
    @Configuration
    @ComponentScan(basePackages = "com.laoyang.spring")
    @EnableTransactionManagement
    public class TxConfig {
        /*
        @Configuration:将当前类作为配置类
        @ComponentScan:组件扫描,相当于<context:component-scan base-package="com.laoyang.spring" />
        @EnableTransactionManagement:开启事务,相当于<tx:annotation-driven transaction-manager="transactionManager" />
        @Bean:创建对象
         */
    
        /**
         * 配置数据库连接池
         * @return
         */
        @Bean
        public DruidDataSource getDruidDataSource() {
            DruidDataSource druidDataSource = new DruidDataSource();
            druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
            druidDataSource.setUrl("jdbc:mysql:///spring_test");
            druidDataSource.setUsername("root");
            druidDataSource.setPassword("123456");
            return druidDataSource;
        }
    
        /**
         * 创建 JdbcTemplate 对象
         * @param dataSource 在 IOC 容器中根据类型找到对应的 dataSource
         * @return
         */
        @Bean
        public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
            JdbcTemplate jdbcTemplate = new JdbcTemplate();
            // 注入 dataSource
            jdbcTemplate.setDataSource(dataSource);
    
            /*
             如果直接调用方法会不太好,因为 getDruidDataSource() 执行的时候已经创建了 dataSource 对象了
             如果我们这里调用该方法注入到 JdbcTemplate 中,那么就相当于又创建了一个 dataSource,所以不太建议这样写
             可以根据类型直接从 IOC 容器中找到对应的 dataSource
             */
    //        jdbcTemplate.setDataSource(getDruidDataSource());
            return jdbcTemplate;
        }
    
        /**
         * 创建事务管理器
         * @param dataSource 在 IOC 容器中根据类型找到对应的 dataSource
         * @return
         */
        @Bean
        public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
            transactionManager.setDataSource(dataSource);
            return transactionManager;
        }
    }
    
  • 测试效果

    package com.laoyang.spring.test;
    
    import com.laoyang.spring.config.TxConfig;
    import com.laoyang.spring.service.UserService;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.transaction.PlatformTransactionManager;
    
    public class UserTest {
        @Test
        public void testTransferAccounts3() {
            ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
            UserService userService = context.getBean("userService", UserService.class);
    
            userService.transferAccounts();
        }
    }
    
  • 数据库效果

    在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值