Spring Boot 事务的简单使用

Spring 事务

Spring 事务介绍

事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性。事务有四大特性(ACID):原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

Spring 既支持编程式事务管理(也称编码式事务),也支持声明式事务管理。编程式事务管理是指将事务管理代码嵌入业务方法中来控制事务的提交和回滚。在编程式事务中,必须在每个业务操作中包含额外的事务管理代码。声明式事务管理是指将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。在大多数情况下,声明式事务管理比编程式事务管理更好用。Spring 通过 Spring AOP 框架支持声明式事务管理。

Spring 声明式事务

Spring 配置文件中关于事务配置总是由三部分组成,分别是 DataSourceTransactionManager 和代理机制。无论哪种配置方式,一般变化的只是代理机制部分,DataSourceTransactionManager 这两部分只会根据数据访问方式有所变化,比如使用 Hibernate 进行数据访问时,DataSource 实现为 SessionFactoryTransactionManager 的实现为 HibernateTransactionManager

Spring 声明式事务配置提供 5 种方式,而基于 Annotation 的注解方式目前比较流行,所以这里只简单介绍基于注解方式配置 Spring 声明式事务。我们可以使用 @Transactional 注解在类或者方法中表明该类或者方法需要事务支持,被注解的类或者方法被调用时,Spring 开启一个新的事务,当方法正常运行时,Spring 会提交这个事务。

这里需要注意的是,@Transactional 注解来自 org.springframework.transaction.annotation。Spring 提供了 @EnableTransactionManagement 注解在配置类上来开启声明式事务的支持。使用 @EnableTransactionManagement 后,Spring 容器会自动扫描注解 @Transactional 的方法和类。

Spring 注解事务行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如,方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。事务的传播行为可以在 @Transactional 的属性中指定,Spring 定义了 7 种传播行为。
在这里插入图片描述

隔离级别定义了一个事务可能受其他并发事务影响的程度。在典型的应用程序中,多个事务并发运行经常会操作相同的数据来完成各自的任务。并发虽然是必需的,可是会导致许多问题,并发事务所导致的问题可以分为以下三类。

  1. 脏读(Dirty Read):脏读发生在一个事务(T1)读取了另一个事务(T2)改写但尚未提交的数据。如果改写在稍后被回滚了,那么第一个事务(T1)获取的数据就是无效的。
  2. 不可重复读(Nonrepeatable Read):不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。通常是因为另一个并发事务在两次查询期间更新了数据。
  3. 幻读(Phantom Read):幻读与不可重复读类似,发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。

针对这些问题,Spring 提供了 5 种事务的隔离级别。
在这里插入图片描述

@Transactional 可以通过 propagation 属性定义事务行为,属性值分别为 REQUIREDSUPPORTSMANDATORYREQUIRES_NEWNOT_SUPPORTEDNEVER 以及 NESTED。可以通过 isolation 属性定义隔离级别,属性值分别为 DEFAULTREAD_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READ 以及 SERIALIZABLE

还可以通过 timeout 属性设置事务过期时间,通过 readOnly 指定当前事务是否是只读事务,通过 RollbackFornoRollbackFor)指定哪个或者哪些异常可以引起(或不可以引起)事务回滚。

Spring Boot 事务的使用

Spring Boot 事务介绍

Spring Boot 开启事务很简单,只需要一个注解 @Transactional 就可以了,因为在 Spring Boot 中已经默认对 JPA、JDBC、Mybatis 开启了事务,引入它们依赖的时候,事物就默认开启。当然,如果你需要用其他的 ORM 框架,比如 BeatlSQL,就需要自己配置相关的事物管理器。

扫描二维码关注公众号,回复: 8801861 查看本文章

Spring Boot 用于配置事务的类为 TransactionAutoConfiguration,此配置类依赖于 JtaAutoConfigurationDataSourceTransactionManagerAutoConfiguration,具体查看源代码可知,而 DataSourceTransactionManagerAutoConfiguration 开启了对声明式事务的支持,所以在 Spring Boot 中,无须显式开启使用 @EnableTransactionManagement。

方法级别事务

@Transactional 除了可以注解在类上,还可以注解到方法上面。当注解在类上时,意味着此类的所有 public 方法都是开启事务的。如果类级别和方法级别同时使用了 @Transactional 注解,就使用方法级别注解覆盖类级别注解。

我们可以给 UserServiceImpl 类中的 addUser(User) 方法添加事务,同时在 insert(User) 执行完成之后抛出算术或空指针异常,然后查看数据是否可以回滚,具体代码如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.sxt.domain.User;
import com.sxt.mapper.UserMapper;
import com.sxt.service.UserService;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    
    @Transactional
    @Override
    public int addUser(User user) {
        int result = 0;
        result = userMapper.insert(user);
        result += userMapper.insert(user); // 故意写多一条
        int i = 1 / 0; // 算术异常
//        String error = null;
//        error.split("/"); // 空指针异常
        return result;
    }
}

测试

我们在测试类 SpringBoot23TransactionApplicationTests 中添加测试方法,具体代码如下:

import java.text.SimpleDateFormat;
import java.util.Date;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.sxt.domain.User;
import com.sxt.service.UserService;

@SpringBootTest
class SpringBoot23TransactionApplicationTests {
    private static final Logger logger = LoggerFactory.getLogger(SpringBoot23TransactionApplicationTests.class);
    
    @Autowired
    private UserService userService;
    
    @Test
    void contextLoads() {
        String dateString = new SimpleDateFormat("HHmmss").format(new Date());
        
        User user = new User();
        user.setUsername("admin");
        user.setPassword(dateString);
        
        int result = 0;
        try {
            result = userService.addUser(user);
        } catch (Exception e) {
            logger.error("异常信息:", e);
        }
        System.out.println("新增结果:" + result);
    }
}

执行单元测试,控制台输出:

22:34:51.505 DEBUG [main] com.mk.mapper.UserMapper.insert - ==>  Preparing: insert into t_user (id, username, password ) values (?, ?, ? ) 
22:34:51.801 DEBUG [main] com.mk.mapper.UserMapper.insert - ==> Parameters: null, admin(String), 223451(String)
22:34:51.807 DEBUG [main] com.mk.mapper.UserMapper.insert - <==    Updates: 1
22:34:51.809 DEBUG [main] com.mk.mapper.UserMapper.insert - ==>  Preparing: insert into t_user (id, username, password ) values (?, ?, ? ) 
22:34:51.809 DEBUG [main] com.mk.mapper.UserMapper.insert - ==> Parameters: null, admin(String), 223451(String)
22:34:51.810 DEBUG [main] com.mk.mapper.UserMapper.insert - <==    Updates: 1
22:34:52.104 ERROR [main] com.mk.SpringBoot23TransactionApplicationTests - 异常信息:
java.lang.ArithmeticException: / by zero
  ...
22:34:52.105 INFO  [main] com.mk.SpringBoot23TransactionApplicationTests - 新增结果:0

虽然从日志看,新增操作似乎成功(第 3、6 行)。

但是,当我们查询数据库中表的信息,可以发现,并没有新增任何记录。由此可知,在 UserServiceImpl.addUser(User) 方法发生异常之后,事务发生回滚。

mysql> select * from t_user;
Empty set (0.00 sec)

现在把 UserServiceImpl.addUser(User) 方法上的 @Transactional 注解注释掉,再次执行单元测试,控制台输出:

22:35:44.780 DEBUG [main] com.mk.mapper.UserMapper.insert - ==>  Preparing: insert into t_user (id, username, password ) values (?, ?, ? ) 
22:35:44.993 DEBUG [main] com.mk.mapper.UserMapper.insert - ==> Parameters: null, admin(String), 223544(String)
22:35:45.139 DEBUG [main] com.mk.mapper.UserMapper.insert - <==    Updates: 1
22:35:45.140 DEBUG [main] com.mk.mapper.UserMapper.insert - ==>  Preparing: insert into t_user (id, username, password ) values (?, ?, ? ) 
22:35:45.141 DEBUG [main] com.mk.mapper.UserMapper.insert - ==> Parameters: null, admin(String), 223544(String)
22:35:45.196 DEBUG [main] com.mk.mapper.UserMapper.insert - <==    Updates: 1
22:35:45.198 ERROR [main] com.mk.SpringBoot23TransactionApplicationTests - 异常信息:
java.lang.ArithmeticException: / by zero
  ...
22:35:45.199 INFO  [main] com.mk.SpringBoot23TransactionApplicationTests - 新增结果:0

虽然从日志看,新增操作似乎失败(第 10 行)。

但是,当我们查询数据库中表的信息,可以发现,多了两条记录。这是因为 UserServiceImpl.addUser(User) 方法没有事务。

mysql> select * from t_user;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  3 | admin    | 223544   |
|  4 | admin    | 223544   |
+----+----------+----------+
2 rows in set (0.00 sec)
发布了36 篇原创文章 · 获赞 0 · 访问量 1868

猜你喜欢

转载自blog.csdn.net/qq_29761395/article/details/103789388