• 01-Spring中事务的实现之编程式事务和声明式事务,以及事务的属性之传播行为,隔离级别,事务的回滚


    银行账户转账异常

    需求: 实现act-001账户向act-002账户转账10000,要求两个账户的余额一个减成功一个加成功,即执行的两条update语句必须同时成功或失败

    实现步骤

    第一步: 引入项目所需要的依赖

    
    <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.powernodegroupId>
        <artifactId>spring6-013-tx-bankartifactId>
        <version>1.0-SNAPSHOTversion>
        <packaging>jarpackaging>
        
        <repositories>
            
            <repository>
                <id>repository.spring.milestoneid>
                <name>Spring Milestone Repositoryname>
                <url>https://repo.spring.io/milestoneurl>
            repository>
        repositories>
        
        <dependencies>
            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-contextartifactId>
                <version>6.0.0-M2version>
            dependency>
            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-jdbcartifactId>
                <version>6.0.0-M2version>
            dependency>
            
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>8.0.30version>
            dependency>
            
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>druidartifactId>
                <version>1.2.13version>
            dependency>
           	
            <dependency>
                <groupId>jakarta.annotationgroupId>
                <artifactId>jakarta.annotation-apiartifactId>
                <version>2.1.1version>
            dependency>
            
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>4.13.2version>
                <scope>testscope>
            dependency>
        dependencies>
    
        <properties>
            <maven.compiler.source>17maven.compiler.source>
            <maven.compiler.target>17maven.compiler.target>
        properties>
    
    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

    第二步: 准备t_act表并向表中插入两条账户记录act-001和act-002

    在这里插入图片描述

    第三步: 编写t_act表对应的实体类

    @Date
    public class Account {
        private String actno;
        private Double balance;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    第四步: 编写Dao(持久层)中的AccountDao接口及其实现类AccountDaoImpl专门负责t_act表的CRUD操作, 没有任何业务逻辑代码

    public interface AccountDao {
        // 根据账号查询余额
        Account selectByActno(String actno);
        // 	更新账户信息
        int update(Account act);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    @Repository("accountDao")
    public class AccountDaoImpl implements AccountDao {
        @Resource(name = "jdbcTemplate")
        private JdbcTemplate jdbcTemplate;
    
        @Override
        public Account selectByActno(String actno) {
            // 根据账号查询账户的信息,并把查询结果封装到对应的实体类中
            String sql = "select actno, balance from t_act where actno = ?";
            Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);
            return account;
        }
    
        @Override
        public int update(Account act) {
            // 根据账号更新账户的余额
            String sql = "update t_act set balance = ? where actno = ?";
            int count = jdbcTemplate.update(sql, act.getBalance(), act.getActno());
            return count;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    第五步: 编写Service(业务层)中的AccountService及其实现类AccountServiceImpl专门负责关于账户的业务逻辑处理,如事务控制的相关代码

    public interface AccountService {
        // 转账方法
        void transfer(String fromActno, String toActno, double money);
    }
    
    • 1
    • 2
    • 3
    • 4
    @Service("accountService")
    public class AccountServiceImpl implements AccountService {
        @Resource(name = "accountDao")
        private AccountDao accountDao;
        // 因为在这个方法中要完成所有的转账业务,所以需要控制事务
        @Override
        public void transfer(String fromActno, String toActno, double money) {
            // 查询账户余额是否充足
            Account fromAct = accountDao.selectByActno(fromActno);
            if (fromAct.getBalance() < money) {
                throw new RuntimeException("账户余额不足");
            }
            // 如果余额充足开始转账
            Account toAct = accountDao.selectByActno(toActno);
            // 将内存中两个对象的余额先修改
            fromAct.setBalance(fromAct.getBalance() - money);
            toAct.setBalance(toAct.getBalance() + money);
            
            // 将数据库账户的余额更新
            int count = accountDao.update(fromAct);
            count += accountDao.update(toAct);
            if (count != 2) {
                throw new RuntimeException("转账失败,请联系银行");
            }
        }
    }
    
    • 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

    第六步: 编写spring的配置文件

    
    <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">
        
        <context:component-scan base-package="com.powernode.bank"/>
        
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        bean>
        
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"/>
        bean>
    
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    第七步: 编写测试程序,模拟表示/控制层处理用户的需求,后台调用对应业务层完成业务

    public class BankTest {
        @Test
        public void testTransfer(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
            try {
                accountService.transfer("act-001", "act-002", 10000);
                System.out.println("转账成功");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    第八步: 模拟转账异常,如果在更新两个账户余额的操作中出现了异常,此时就会出现前者的余额减了但后者的余额没有加上

    @Service("accountService")
    public class AccountServiceImpl implements AccountService {
        @Resource(name = "accountDao")
        private AccountDao accountDao;
    
        @Override
        public void transfer(String fromActno, String toActno, double money) {
            // 查询账户余额是否充足
            Account fromAct = accountDao.selectByActno(fromActno);
            if (fromAct.getBalance() < money) {
                throw new RuntimeException("账户余额不足");
            }
            // 余额充足,开始转账
            Account toAct = accountDao.selectByActno(toActno);
            fromAct.setBalance(fromAct.getBalance() - money);
            toAct.setBalance(toAct.getBalance() + money);
            int count = accountDao.update(fromAct);
            
            // 模拟异常
            String s = null;
            s.toString();
    
            count += accountDao.update(toAct);
            if (count != 2) {
                throw new RuntimeException("转账失败,请联系银行");
            }    
        }
    }
    
    • 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

    Spring事务的实现

    编程式事务

    编程式事务(了解),自己在业务方法中手写控制事务的代码

    @Override
    public void transfer(String fromActno, String toActno, double money) {
        // 第一步开启事务
    
        // 第二步执行核心业务逻辑
    
        // 第三步如果执行核心业务流程中没有异常则提交事务
        
        // 第四步如果执行核心业务流程中有异常则回滚事务
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    声明式事务

    声明式事务(常用),基于注解方式XML配置方式实现事务的控制

    Spring事务管理的底层是基于AOP实现的,所以Spring专门针对事务开发了一套APIPlatformTransactionManager(事务管理器的核心接口)并且有两个实现类

    实现类描述
    DataSourceTransactionManager支持JdbcTemplate、MyBatis、Hibernate等事务管理
    JtaTransactionManager支持分布式事务管理

    第一步: 在spring配置文件中引入tx的命名空间及其约束文件,spring-jdbc依赖中关联了事务相关的依赖

    第二步:在spring配置文件中配置事务管理器,如果在Spring6中使用JdbcTemplate就要使用DataSourceTransactionManager事务管理器来管理事务

    • 由于事务管理器DataSourceTransactionManager底层利用的还是Connection连接连接对象开启关闭事务,所以需要给事务管理器配置数据源

    第三步: 在spring配置文件中开启事务注解驱动器告诉Spring框架采用注解的方式控制事务

    • 事务管理器就是我们的切面类,@Transactional注解所标识的方法就是连接点(可以织入切面的位置)
    
    <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">  
        
        <context:component-scan base-package="com.powernode.bank"/>
        
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        bean>
        
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"/>
        bean>
         
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        bean>
        
        <tx:annotation-driven transaction-manager="transactionManager"/>
    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

    第四步: 在Service层中的业务类上或业务方法上添加@Transactional注解,表示当前类/方法开启了事务,业务方法中批量的DML操作可以保证同时成功或失败

    • 在业务类上添加注解: 表示该类中所有的方法都开启事务
    • 在业务方法上添加注解: 表示只有当前方法开启事务
    @Service("accountService")
    @Transactional// 开启事务
    public class AccountServiceImpl implements AccountService {
        @Resource(name = "accountDao")
        private AccountDao accountDao;
        @Override
        public void transfer(String fromActno, String toActno, double money) {
            // 查询账户余额是否充足
            Account fromAct = accountDao.selectByActno(fromActno);
            if (fromAct.getBalance() < money) {
                throw new RuntimeException("账户余额不足");
            }
            // 余额充足,开始转账
            Account toAct = accountDao.selectByActno(toActno);
            fromAct.setBalance(fromAct.getBalance() - money);
            toAct.setBalance(toAct.getBalance() + money);
            int count = accountDao.update(fromAct);
    
            // 模拟异常
            String s = null;
            s.toString();
    
            count += accountDao.update(toAct);
            if (count != 2) {
                throw new RuntimeException("转账失败,请联系银行");
            }
        }
    }
    
    • 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
  • 相关阅读:
    【PAT(甲级)】1062 Talent and Virtue
    axios 请求拦截器&响应拦截器与router的导航守卫
    OpenCV-Python实战(5) —— OpenCV 使用cv.setMouseCallback实现截图功能
    2022年9月电子学会考级试卷真题解析(含答案和所有文档下载)
    【LeetCode贪心#09】用最少数量的箭引爆气球,无重叠区间,合并区间(涉及区间重叠情况判断与处理)
    一码跑遍众多小程序,饿了么开源自研多端框架 MorJS
    软件测试/测试开发/人工智能丨聊聊AutoGPT那些事儿
    保护敏感数据的艺术:数据安全指南
    overleaf 写论文Latex语法记录
    水文-ts中对中文进行base64编码
  • 原文地址:https://blog.csdn.net/qq_57005976/article/details/134241825