当前位置: 首页 > news >正文

事务-Transaction

文章目录

  • 1. 基本概念
  • 2. 事务配置
  • 3. 事务的传播行为
  • 4. 转账(案例)
    • 4.1 准备工作
    • 4.2 代码实现

1. 基本概念

  • 定义:事务是一组操作的集合,它是一个不可分割的工作单位。事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败

  • Spring事务的作用:在数据层或业务层保障一系列的数据库操作同时成功或同时失败。

  • 事务的操作主要有三步:

    1. 开启事务(一组操作开始前,开启事务):start transaction / begin ;
    2. 提交事务(这组操作全部成功后,提交事务):commit ;
    3. 回滚事务(中间任何一个操作出现异常,回滚事务):rollback ;
  • Spring实现事务:Spring为了管理事务,提供了一个平台事务管理器PlatformTransactionManager

    在这里插入图片描述

PlatformTransactionManager只是一个接口,Spring还为其提供了一个具体的实现(DataSourceTransactionManager):我们只需要给它一个DataSource对象,它就可以帮你去在业务层管理事务。其内部采用的是JDBC的事务。所以说如果你持久层采用的是JDBC相关的技术,就可以采用这个事务管理器来管理你的事务。而Mybatis内部采用的就是JDBC的事务,所以后期我们Spring整合Mybatis就采用的这个DataSourceTransactionManager事务管理器。

在这里插入图片描述

  • 如何在代码中实现事务:Spring框架当中就已经把事务控制的代码都已经封装好了,并不需要我们手动实现。我们使用了Spring框架,我们只需要通过一个简单的注解@Transactional就搞定了。

    • @Transactional作用:就是在当前这个方法执行开始之前来开启事务,方法执行完毕之后提交事务。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作。

    • @Transactional注解:我们一般会在业务层当中来控制事务,因为在业务层当中,一个业务功能可能会包含多个数据访问的操作。在业务层来控制事务,我们就可以将多个数据访问操作控制在一个事务范围内。

    • @Transactional注解书写位置:

      • 方法
        • 当前方法交给Spring进行事务管理
        • 当前类中所有的方法都交由Spring进行事务管理
      • 接口
        • 接口下所有的实现类当中所有的方法都交给Spring进行事务管理

2. 事务配置

在这里插入图片描述

  • 上面这些属性都可以在@Transactional注解的参数上进行设置。

    • readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。
    • timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超时时间。
    • rollbackFor:当出现指定异常进行事务回滚。
    • noRollbackFor:当出现指定异常不进行事务回滚。
    • rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串。
    • noRollbackForClassName等同于noRollbackFor,只不过属性为异常的类全名字符串。
    • isolation设置事务的隔离级别:
      • DEFAULT:默认隔离级别, 会采用数据库的隔离级别
      • READ_UNCOMMITTED:读未提交
      • READ_COMMITTED: 读已提交
      • REPEATABLE_READ:重复读取
      • SERIALIZABLE: 串行化

    注意事项:Spring的事务只会对Error异常RuntimeException异常及其子类进行事务回顾,其他的异常类型是不会回滚的。

3. 事务的传播行为

在这里插入图片描述

  • 事务的传播行为:是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

  • 我们要想控制事务的传播行为,在@Transactional注解的后面指定一个属性propagation,通过 propagation 属性来指定传播行为。接下来我们就来介绍一下常见的事务传播行为。

    属性值含义
    REQUIRED【默认值】需要事务,有则加入,无则创建新事务
    REQUIRES_NEW需要新事务,无论有无,总是创建新事务
    SUPPORTS支持事务,有则加入,无则在无事务状态中运行
    NOT_SUPPORTED不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务
    MANDATORY必须有事务,否则抛异常
    NEVER必须没事务,否则抛异常

4. 转账(案例)

  • 需求:银行转账操作保证两个账户的金额变动要么都成功,要么都失败,并提供操作日志,并保存在数据表中,便于后期数据追踪。

    操作日志信息包含:

    • 转账人、收账人、金额、执行状态、转账时间。

4.1 准备工作

  1. 添加相关依赖:

    // Spring工程
    <dependencies>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>5.2.10.RELEASE</version>
        </dependency>
        
        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.1.16</version>
        </dependency>
    
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis</artifactId>
          <version>3.5.6</version>
        </dependency>
    
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.47</version>
        </dependency>
    
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>5.2.10.RELEASE</version>
        </dependency>
    
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis-spring</artifactId>
          <version>1.3.0</version>
        </dependency>
    </dependencies>
    
    // SpringBoot工程
    <dependencies>
            <!--mybatis起步依赖-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.2.2</version>
            </dependency>
            <!--mysql驱动-->
            <dependency>
                <groupId>com.mysql</groupId>
                <artifactId>mysql-connector-j</artifactId>
                <scope>runtime</scope>
            </dependency>
    </dependencies>      
    
  2. 按需进行如下配置:

    Spring Boot 工程:无需手动添加 @EnableTransactionManagement,只要引入事务相关依赖(如 spring-boot-starter-jdbc),Spring Boot 会自动配置事务管理。

    传统 Spring 工程:必须显式添加 @EnableTransactionManagement 并在 XML 中配置事务管理器(如 <tx:annotation-driven/>

    @Configuration
    @ComponentScan("com.itheima")
    @PropertySource("classpath:jdbc.properties")
    @Import({JdbcConfig.class,MybatisConfig.class})
    @EnableTransactionManagement
    public class SpringConfig {}
    
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
    jdbc.username=root
    jdbc.password=root
    
    public class JdbcConfig {
        @Value("${jdbc.driver}")
        private String driver;
        @Value("${jdbc.url}")
        private String url;
        @Value("${jdbc.username}")
        private String userName;
        @Value("${jdbc.password}")
        private String password;
    
        @Bean
        public DataSource dataSource(){
            DruidDataSource ds = new DruidDataSource();
            ds.setDriverClassName(driver);
            ds.setUrl(url);
            ds.setUsername(userName);
            ds.setPassword(password);
            return ds;
        }
        
        //配置事务管理器,mybatis使用的是jdbc事务
        @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource){
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
            transactionManager.setDataSource(dataSource);
            return transactionManager;
        }
    }
    
    public class MybatisConfig {
        @Bean
        public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
            SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
            ssfb.setTypeAliasesPackage("com.itheima.domain");
            ssfb.setDataSource(dataSource);
            return ssfb;
        }
    
        @Bean
        public MapperScannerConfigurer mapperScannerConfigurer(){
            MapperScannerConfigurer msc = new MapperScannerConfigurer();
            msc.setBasePackage("com.itheima.dao");
            return msc;
        }
    }    
    

4.2 代码实现

  1. 数据库表结构(简略)

    -- 账户表
    CREATE TABLE account (
        id BIGINT PRIMARY KEY,
        balance DECIMAL(15,2) NOT NULL
    );
    
    -- 操作日志表
    CREATE TABLE transfer_log (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        from_account BIGINT NOT NULL,
        to_account BIGINT NOT NULL,
        amount DECIMAL(15,2) NOT NULL,
        status VARCHAR(20) NOT NULL,
        create_time DATETIME NOT NULL
    );
    
  2. 核心代码分层设计

    1. Service 层 - 转账业务与日志分离:
    @Service
    public class TransferService {
        @Autowired
        private AccountService accountService;
        
        @Autowired
        private TransferLogService logService;
    
        // 外层事务:控制转账整体逻辑
        @Transactional(propagation = Propagation.REQUIRED)
        public void transfer(Long fromId, Long toId, BigDecimal amount) {
            // 1. 记录日志(独立事务:无论转账成功与否都记录尝试)
            logService.logAttempt(fromId, toId, amount);
            
            try {
                // 2. 执行转账(内层事务)
                accountService.transferFunds(fromId, toId, amount);
                
                // 3. 记录成功日志(独立事务)
                logService.logSuccess(fromId, toId, amount);
            } catch (Exception e) {
                // 4. 记录失败日志(独立事务)
                logService.logFailure(fromId, toId, amount, e.getMessage());
                throw e; // 抛出异常触发外层事务回滚
            }
        }
    }
    
    @Service
    public class AccountService {
        @Transactional(propagation = Propagation.MANDATORY) // 必须在外层事务中调用
        public void transferFunds(Long fromId, Long toId, BigDecimal amount) {
            // 扣减转出账户余额
            accountRepository.deduct(fromId, amount);
            // 增加转入账户余额
            accountRepository.add(toId, amount);
        }
    }
    
    @Service
    public class TransferLogService {
        // 独立事务:日志记录不受外层事务回滚影响
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void logAttempt(Long fromId, Long toId, BigDecimal amount) {
            logRepository.save(new TransferLog(fromId, toId, amount, "ATTEMPT"));
        }
    
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void logSuccess(...) { 
            logRepository.save(new TransferLog(fromId, toId, amount, "Success"));
        }
    
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void logFailure(...) { 
            logRepository.save(new TransferLog(fromId, toId, amount, "Failure"));
        }
    }
    
    1. Repository 层(JPA 示例)

      public interface AccountRepository extends JpaRepository<Account, Long> {
          @Modifying
          @Query("UPDATE Account a SET a.balance = a.balance - :amount WHERE a.id = :id")
          void deduct(@Param("id") Long id, @Param("amount") BigDecimal amount);
      
          @Modifying
          @Query("UPDATE Account a SET a.balance = a.balance + :amount WHERE a.id = :id")
          void add(@Param("id") Long id, @Param("amount") BigDecimal amount);
      }
      
      public interface TransferLogRepository extends JpaRepository<TransferLog, Long> {}
      

相关文章:

  • EXCEL自动化13 | 批量重命名工作簿中的工作表
  • 【AD】5-15 Active Route的自动布线辅助
  • postman接口请求中的 Raw是什么
  • 【愚公系列】《Python网络爬虫从入门到精通》045-Charles的SSL证书的安装
  • AIP-161 域掩码
  • AI 时代的新宠儿:向量数据库
  • 渗透测试之利用sql拿shell(附完整流程+防御方案)
  • 深度学习笔记——CNN卷积神经网络
  • Python----数据可视化(Seaborn一:介绍,应用)
  • css动画
  • GetWindowLongPtr函数分析
  • OpenCV 拆分、合并图像通道方法及复现
  • nginx的安装以及相关的全局性配置
  • K8s面试题总结(十一)
  • 使用Arduino和ESP8266进行基于物联网的垃圾箱监控
  • Ubuntu 下 nginx-1.24.0 源码分析 - ngx_cycle_modules
  • 哈夫曼树的讲解
  • 【微知】如何命令行查看Linux上连接的Wi-Fi信息?(iw dev wlp4s0 link ; information wireless)
  • 基于大数据的商品数据可视化及推荐系统
  • C++11新特性 5.static静态
  • top域名的网站/产品优化是什么意思
  • 馆陶网站建设费用/搜索引擎广告
  • 公众号里的功能怎么开发/网站优化seo方案
  • 外贸营销策划方案/郴州seo网络优化
  • 网站开发文档模板下载/免费建网站知乎
  • 网站降权怎么做/青岛关键词排名系统