Spring中的编程式、声明式事务

1. 事务

        事务(Transaction)是数据库管理系统中的一个重要概念,它是一个原子性的数据库操作序列。原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)是事务必须具备的四个核心特性,通常简称为ACID特性。

  • 原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部失败并回滚到操作之前的状态。这意味着,事务内的操作是不可分割的。

  • 一致性(Consistency:事务必须确保数据库从一个一致性状态转换到另一个一致性状态。一致性状态意味着数据库中的数据应满足完整性约束和业务规则。

  • 隔离性(Isolation):当多个事务并发执行时,它们之间的操作应该相互隔离。事务的隔离级别决定了事务操作的可见性和其他事务的干扰程度。

  • 持久性(Durability):一旦事务被提交(Commit),它对数据库所做的修改应该是永久性的。即使系统崩溃,事务的结果也应该被保存。

        在Spring框架中,事务管理是一个核心功能,它允许开发者以声明式或编程式的方式来处理事务,从而确保数据库操作的ACID特性。Spring的事务管理机制是基于代理模式和AOP(面向切面编程)的,这使得事务管理更加灵活和透明。

1.1 Spring事务管理的关键概念

  • PlatformTransactionManager:这是Spring事务管理的核心接口,负责抽象出不同事务管理器的共同操作。根据底层数据访问技术的不同,Spring提供了不同的实现,如DataSourceTransactionManagerHibernateTransactionManagerJpaTransactionManager

  • TransactionDefinition:这个接口定义了事务的属性,包括传播行为、隔离级别、超时时间等。

  • TransactionStatus:这是一个对象,代表了当前事务的状态,它可以被用来查询事务的当前状态,或者控制事务的执行(如设置回滚标志)。

  • TransactionCallbackTransactionTemplate:这是编程式事务管理的关键类。TransactionTemplate是一个模板类,它提供了执行事务的方法,而TransactionCallback是一个回调接口,用于执行实际的业务逻辑。

  • @Transactional 注解:这是声明式事务管理的核心,可以应用于类或方法上。它定义了事务的边界和属性,Spring容器在运行时会根据这些注解来自动管理事务。

1.2 Spring事务管理的工作流

  1.  获取事务:Spring通过PlatformTransactionManager获取当前线程的事务。

  2.  事务传播:根据TransactionDefinition的传播行为,Spring决定是创建一个新的事务,还是加入现有的事务。

  3.  执行业务逻辑:在事务的保护下执行业务逻辑。

  4.  事务提交或回滚:如果业务逻辑执行成功,则提交事务;如果发生异常,则根据配置决定是否回滚事务。

  5.  释放资源:无论事务是否成功,Spring都会释放事务资源。

2. 实例

JDK

8

Maven

3.6

SpringBoot

2.7.12

2. 1 环境搭建

2.1.1 添加依赖
<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.12</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>2.7.12</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.32</version>
    </dependency>
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.26</version>
    </dependency>
</dependencies>

 注:更换依赖版本需要注意依赖冲突问题

2.1.2 MySQL库表创建
user表

CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `iphone` varchar(128) DEFAULT NULL,
  `money` decimal(6,2) DEFAULT NULL,
  `address` varchar(128) DEFAULT NULL,
  `password` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

order表
CREATE TABLE `order` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int DEFAULT NULL,
  `name` varchar(128) DEFAULT NULL,
  `number` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

注:库表结构不一定非要按照这样,符合逻辑即可。

2.1.3 application.yml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://116.198.242.56:13306/transaction?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    hikari:
      minimum-idle: 5
      maximum-pool-size: 20
      auto-commit: true
      idle-timeout: 30000
      pool-name: DatebaseHikariPool
      max-lifetime: 1800000
      connection-timeout: 30000
      type: com.zaxxer.hikari.HikariDataSource

server:
  port: 8081

mybatis:
  mapper-locations: classpath:/mapper/*.xml

2.2 工程结构

project-root/
|-- main/
|   |-- java/
|   |   |-- com/
|   |   |   | -- xiaokai/
|   |   |       |-- TransactionApplication.java
|   |   |       |-- service/
|   |   |       |   |-- impl/
|   |   |       |   |   |-- IOrderServiceImpl.java
|   |   |       |   |   |-- IUserServiceImpl.java
|   |   |       |   |   `-- (其他 service impl 类)
|   |   |       |   `-- IOrderService.java
|   |   |       |   `-- IUserService.java
|   |   |       `-- tran/
|   |   |           |-- AnnotationService.java
|   |   |           |-- config/
|   |   |               |-- TransactionConfig.java
|   |   |           |-- entity
|   |                   |-- OrderEntity.java
|   |                   `-- UserEntity.java
|   |   |           |-- repository
|   |                   |-- OrderMapper.java
|   |                   `-- UserMapper.java
|   |-- resources/
|   |   |-- mapper/
|   |   |   |-- order_mapper.xml
|   |   |   `-- user_mapper.xml
|   |   `-- application.yml
|-- test/
|   |-- java/
|   |   |-- com/
|   |       `-- TransactionTest.java

注:工程结构只是提供一个版本,可以根据所需更换类名或者所在包。

2.3 编程式事务

2.3.1 config
package com.xiaokai.tran.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement
public class TransactionConfig {

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }

    @Bean
    public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
        return new TransactionTemplate(transactionManager);
    }
}

注:有的版本不需要这个配置bean类。

2.3.2 实体类
/**
 * Author:yang
 * Date:2024-09-26 10:52
 * Description:订单实体类
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OrderEntity {

    private Integer userId;

    private String name;

    private Integer number;
}


/**
 * Author:yang
 * Date:2024-09-26 10:55
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity {

    private int id;

    private String name;

    private String password;

    private String iphone;

    private String address;

    private int money;

}
2.3.3 ORM交互接口
/**
 * Author:yang
 * Date:2024-09-26 11:00
 */
@Mapper
public interface OrderMapper {

    int insertOrder(OrderEntity order);
}

/**
 * Author:yang
 * Date:2024-09-26 10:46
 */
@Mapper
public interface UserMapper {

    Integer insertUser(UserEntity userEntity);

}
2.3.4 mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xiaokai.tran.repository.UserMapper">

    <insert id="insertUser" parameterType="com.xiaokai.tran.entity.UserEntity" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (name, iphone, money, address, password)
        VALUES (#{name}, #{iphone}, #{money}, #{address}, #{password})
    </insert>
</mapper>


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xiaokai.tran.repository.OrderMapper">
    <insert id="insertOrder" parameterType="com.xiaokai.tran.entity.OrderEntity" useGeneratedKeys="true">
        insert into `order` (`user_id`, `name`, `number`)
        values (#{userId},
                #{name},
                #{number})
    </insert>
</mapper>
2.3.5 启动类
package com.xiaokai;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * Author:yang
 * Date:2024-09-25 9:55
 */
@SpringBootApplication
@EnableTransactionManagement
public class TransactionApplication {
    public static void main(String[] args) {
        SpringApplication.run(TransactionApplication.class, args);
    }
}
2.3.6 测试
package com;

import com.xiaokai.TransactionApplication;
import com.xiaokai.service.IOrderService;
import com.xiaokai.service.IUserService;
import com.xiaokai.service.impl.IOrderServiceImpl;
import com.xiaokai.tran.entity.OrderEntity;
import com.xiaokai.tran.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.support.TransactionTemplate;

/**
 * Author:yang
 * Date:2024-09-26 11:09
 */
@SpringBootTest(classes = {TransactionApplication.class})
@Slf4j
@RunWith(SpringRunner.class)
public class TransactionTest {


    @Autowired
    private IOrderService orderService;
    @Autowired
    private IUserService userService;
    
    // 注入编程式事务管理bean
    @Autowired
    private TransactionTemplate transactionTemplate;

    @Test
    public void test_program() {
        log.info("test_program_transaction");
        transactionTemplate.execute(status -> {
            try {
                // 添加用户
                UserEntity user = new UserEntity().builder()
                        .name("xiaokai")
                        .iphone("13812345678")
                        .money(1000)
                        .address("beijing")
                        .build();
                Integer countUser = userService.createUser(user);
                if (countUser != 1) {
                    log.error("createUser error count: {}", countUser);
                }else {
                    log.info("createUser count: {},user: {}", countUser, user.toString());
                }
                // 创建异常,事务回滚
                int i = 1 / 0;

                OrderEntity order = new OrderEntity().builder()
                        .userId(user.getId())
                        .name("xiaokai")
                        .number(10)
                        .build();
                Integer countOrder = orderService.createOrder(order);
                if (countOrder != 1) {
                    log.error("createOrder count: {}", countOrder);
                }else {
                    log.info("createOrder count: {},order: {}", countOrder, order.toString());
                }
            } catch (Exception e) {
                status.setRollbackOnly();
                log.error("transaction rollback", e);
            }
            return 1;
        });
        log.info("test_program_transaction end");
    }
}

结果1:

2024-09-26 15:18:20.122  INFO 26420 --- [           main] com.TransactionTest                      : test_program_transaction
2024-09-26 15:18:20.124  INFO 26420 --- [           main] com.zaxxer.hikari.HikariDataSource       : DatebaseHikariPool - Starting...
2024-09-26 15:18:20.493  INFO 26420 --- [           main] com.zaxxer.hikari.HikariDataSource       : DatebaseHikariPool - Start completed.
2024-09-26 15:18:20.635  INFO 26420 --- [           main] com.TransactionTest                      : createUser count: 1,user: UserEntity(id=6, name=xiaokai, password=null, iphone=13812345678, address=beijing, money=1000)
2024-09-26 15:18:20.637 ERROR 26420 --- [           main] com.TransactionTest                      : transaction rollback

java.lang.ArithmeticException: / by zero

        由于有异常终止了终止了后部分代码,在捕获异常后回滚事务。根据日志,user表中已经插入了一条数据id=6,查看表数据:

        没有找到该数据是由于事务回滚了。 

结果2:注释 int i = 1 / 0;

test_program_transaction
DatebaseHikariPool - Starting...
DatebaseHikariPool - Start completed.
createUser count: 1,user: UserEntity(id=7, name=xiaokai, password=null, iphone=13812345678, address=beijing, money=1000)
createOrder count: 1,order: OrderEntity(userId=7, name=xiaokai, number=10)
test_program_transaction end

        没有异常抛出,两条数据均插入成功,事务commit。 

2.4 声明式事务

2.4.1 添加注解

        在启动类上添加 @EnableTransactionManagement 注解以及在所需要执行事务的方法或者类上添加 @Transactional 注解

2.4.2 添加事务服务
package com.xiaokai.tran;

import com.xiaokai.service.IOrderService;
import com.xiaokai.service.IUserService;
import com.xiaokai.tran.entity.OrderEntity;
import com.xiaokai.tran.entity.UserEntity;
import com.xiaokai.tran.repository.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


/**
 * Author:yang
 * Date:2024-09-25 14:40
 */
@Service
@Transactional
@Slf4j
public class AnnotationService {

    @Autowired
    private IUserService userService;

    @Autowired
    private IOrderService orderService;

    public void statementTransaction() {
        // 添加用户
        UserEntity user = new UserEntity().builder()
                .name("xiaokai")
                .iphone("13812345678")
                .money(1000)
                .address("beijing")
                .build();
        Integer countUser = userService.createUser(user);
        if (countUser != 1) {
            log.error("createUser error count: {}", countUser);
        } else {
            log.info("createUser count: {},user: {}", countUser, user.toString());
        }
        // 创建异常
        //int i = 1 / 0;
        OrderEntity order = new OrderEntity().builder()
                .userId(user.getId())
                .name("xiaokai")
                .number(10)
                .build();
        Integer countOrder = orderService.createOrder(order);
        if (countOrder != 1) {
            log.error("createOrder count: {}", countOrder);
        } else {
            log.info("createOrder count: {},order: {}", countOrder, order.toString());
        }
    }

}
2.4.3 测试
@Test
public void test_statement() {
    log.info("test_statement_transaction");
    annotationService.statementTransaction();
    log.info("test_statement_transaction end");
}

        测试结果可以参考编程式事务结果。

注:在声明式事务管理中,@Transactional注解可以配置多种事务属性,如propagation(传播行为)、isolation(隔离级别)、timeout(超时时间)、readOnly(只读标志)等,以满足不同的业务需求。

3. 传播机制

参考另一篇文章:Spring事务传播机制

4. 事务失效

参考另一篇文章:Spring事务常见失效场景

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值