• Spring对事务的实现


    事务概述

    • 在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。
    • 多条DML要么同时成功,要么同时失败,这叫做事务。

    事务的四个处理过程

    第一步:开启事务 (start transaction)
    第二步:执行核心业务代码
    第三步:提交事务(如果核心业务处理过程中没有出现异常)
    第四步:回滚事务(如果核心业务处理过程中出现异常)

    事务的四个特性

    原子性:事务是最小的工作单元,不可再分。
    一致性:事务要求要么同时成功,要么同时失败。事务前和事务后的总量不变。
    隔离性:事务和事务之间因为有隔离性,才可以保证互不干扰。
    持久性:持久性是事务结束的标志。

    引入事务场景

    例:两个账户act-001和act-002。act-001账户向act-002账户转账10000,必须同时成功,或者同时失败。

    新建模块:spring6-jdbc

    连接数据库的技术采用Spring框架的JdbcTemplate

    pom.xml

    
    <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.wgroupId>
        <artifactId>spring6-jdbcartifactId>
        <version>1.0-SNAPSHOTversion>
    
        <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>junitgroupId>
                <artifactId>junitartifactId>
                <version>4.13.2version>
                <scope>testscope>
            dependency>
            
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>8.0.30version>
            dependency>
            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-jdbcartifactId>
                <version>6.0.0-M2version>
            dependency>
    
    
            <dependency>
                <groupId>jakarta.annotationgroupId>
                <artifactId>jakarta.annotation-apiartifactId>
                <version>2.1.1version>
            dependency>
            
            <dependency>
                <groupId>org.junit.jupitergroupId>
                <artifactId>junit-jupiterartifactId>
                <version>RELEASEversion>
                <scope>testscope>
            dependency>
    
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>druidartifactId>
                <version>1.1.8version>
            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
    • 67
    • 68
    • 69
    • 70

    第一步:准备数据库表
    t_act
    表结构
    在这里插入图片描述
    表数据
    在这里插入图片描述
    第二步:创建包结构
    com.w.spring6.bank.pojo
    com.w.spring6.bank.service
    com.w.spring6.bank.service.impl
    com.w.spring6.bank.dao
    com.w.spring6.bank.dao.impl

    第三步:准备POJO类
    Account.java

    package com.w.spring6.bank.pojo;
    
    public class Account {
        private String actno;
        private Double balance;
    
        @Override
        public String toString() {
            return "Account{" +
                    "actno='" + actno + '\'' +
                    ", balance=" + balance +
                    '}';
        }
    
        public Account() {
        }
    
        public Account(String actno, Double balance) {
            this.actno = actno;
            this.balance = balance;
        }
    
        public String getActno() {
            return actno;
        }
    
        public void setActno(String actno) {
            this.actno = actno;
        }
    
        public Double getBalance() {
            return balance;
        }
    
        public void setBalance(Double balance) {
            this.balance = balance;
        }
    }
    
    
    • 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

    第四步:编写持久层
    AccountDao接口

    package com.w.spring6.bank.dao;
    
    import com.w.spring6.bank.pojo.Account;
    
    public interface AccountDao {
    
        /**
         * 根据账号查询余额
         * @param actno
         * @return
         */
        Account selectByActno(String actno);
    
        /**
         * 更新账户
         * @param act
         * @return
         */
        int update(Account act);
    
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    AccountDaoImpl.java

    package com.w.spring6.bank.dao.impl;
    
    import com.w.spring6.bank.dao.AccountDao;
    import com.w.spring6.bank.pojo.Account;
    
    import jakarta.annotation.Resource;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    @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
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    第五步:编写业务层
    AccountService接口

    package com.w.spring6.bank.service;
    
    public interface AccountService {
    
        /**
         * 转账
         * @param fromActno
         * @param toActno
         * @param money
         */
        void transfer(String fromActno, String toActno, double money);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    AccountServiceImpl.java

    package com.w.spring6.bank.service.impl;
    
    import com.w.spring6.bank.dao.AccountDao;
    import com.w.spring6.bank.pojo.Account;
    import com.w.spring6.bank.service.AccountService;
    import jakarta.annotation.Resource;
    import org.springframework.stereotype.Service;
    
    @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
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    第六步:编写Spring配置文件
    Spring-bank.xml

    
    <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 https://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:component-scan base-package="com.w.spring6.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:3307/idea-spring-test"/>
            <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

    第七步:编写表示层(测试程序)
    BankTest.java

    package com.w.spring6.test;
    
    import com.w.spring6.bank.service.AccountService;
    import org.junit.jupiter.api.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    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
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    运行结果:
    在这里插入图片描述
    在这里插入图片描述

    模拟异常:
    在这里插入图片描述
    卧槽,少了10000
    在这里插入图片描述

    Spring实现事务的两种方式

    • 编程式事务:通过编写代码的方式来实现事务的管理。
    • 声明式事务:基于注解方式,基于XML配置方式

    声明式事务之注解实现方式

    第一步:在spring配置文件中配置事务管理器。
    spring-bank.xml

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource"/>
    bean>
    
    • 1
    • 2
    • 3

    第二步:在spring配置文件中引入tx命名空间,在spring配置文件中配置“事务注解驱动器”,开始注解的方式控制事务。

    第三步:在service类上或方法上添加@Transactional注解
    @Transactional
    public class AccountServiceImpl implements AccountService {
    }

    接着继续运行测试
    发现没有丢钱啦!
    在这里插入图片描述

  • 相关阅读:
    three.js线条line宽度问题
    【camera】摄像头模组简单介绍
    Java 项目 服务器 日志配置
    【离线/并查集】CF1213 G
    ModuleNotFoundError: No module named ‘hurry‘
    iOS-前半周【Zara】iOStableView And iOS无线轮播视图
    python json包
    【LeetCode】31. 下一个排列
    基于双向长短期神经网络bilstm的径流量预测,基于gru神经网络的径流量预测
    期权投资的优势有哪些方面?
  • 原文地址:https://blog.csdn.net/m0_61689418/article/details/134456802