springboot的事务管理

Spring Boot中的事务管理

原创

2016-05-27

翟永超

Spring Boot

被围观 29955 次

什么是事务?

我们在开发企业应用时,对于业务人员的一个操作实际是对数据读写的多步操作的结合。由于数据操作在顺序执行的过程中,任何一步操作都有可能发生异常,异常会导致后续操作无法完成,此时由于业务逻辑并未正确的完成,之前成功操作数据的并不可靠,需要在这种情况下进行回退。

事务的作用就是为了保证用户的每一个操作都是可靠的,事务中的每一步操作都必须成功执行,只要有发生异常就回退到事务开始未进行操作的状态。

事务管理是Spring框架中最为常用的功能之一,我们在使用Spring Boot开发应用时,大部分情况下也都需要使用事务。

快速入门

在Spring Boot中,当我们使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依赖的时候,框架会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager。所以我们不需要任何额外配置就可以用@Transactional注解进行事务的使用。

我们以之前实现的《用spring-data-jpa访问数据库》的示例Chapter3-2-2作为基础工程进行事务的使用常识。

在该样例工程中(若对该数据访问方式不了解,可先阅读该文章),我们引入了spring-data-jpa,并创建了User实体以及对User的数据访问对象UserRepository,在ApplicationTest类中实现了使用UserRepository进行数据读写的单元测试用例,如下:


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


@RunWith(SpringJUnit4ClassRunner.class)

@SpringApplicationConfiguration(Application.class)

public class ApplicationTests {

@Autowired

private UserRepository userRepository;

@Test

public void test() throws Exception {

// 创建10条记录

userRepository.save(new User("AAA", 10));

userRepository.save(new User("BBB", 20));

userRepository.save(new User("CCC", 30));

userRepository.save(new User("DDD", 40));

userRepository.save(new User("EEE", 50));

userRepository.save(new User("FFF", 60));

userRepository.save(new User("GGG", 70));

userRepository.save(new User("HHH", 80));

userRepository.save(new User("III", 90));

userRepository.save(new User("JJJ", 100));

// 省略后续的一些验证操作

}

}

可以看到,在这个单元测试用例中,使用UserRepository对象连续创建了10个User实体到数据库中,下面我们人为的来制造一些异常,看看会发生什么情况。

通过定义User的name属性长度为5,这样通过创建时User实体的name属性超长就可以触发异常产生。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16


@Entity

public class User {

@Id

@GeneratedValue

private Long id;

@Column(nullable = false, length = 5)

private String name;

@Column(nullable = false)

private Integer age;

// 省略构造函数、getter和setter

}

修改测试用例中创建记录的语句,将一条记录的name长度超过5,如下:name为HHHHHHHHH的User对象将会抛出异常。


1

2

3

4

5

6

7

8

9

10

11

12

// 创建10条记录

userRepository.save(new User("AAA", 10));

userRepository.save(new User("BBB", 20));

userRepository.save(new User("CCC", 30));

userRepository.save(new User("DDD", 40));

userRepository.save(new User("EEE", 50));

userRepository.save(new User("FFF", 60));

userRepository.save(new User("GGG", 70));

userRepository.save(new User("HHHHHHHHHH", 80));

userRepository.save(new User("III", 90));

userRepository.save(new User("JJJ", 100));

执行测试用例,可以看到控制台中抛出了如下异常,name字段超长:


1

2

3

4

5

6


2016-05-27 10:30:35.948 WARN 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1406, SQLState: 22001

2016-05-27 10:30:35.948 ERROR 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data truncation: Data too long for column ‘name‘ at row 1

2016-05-27 10:30:35.951 WARN 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Warning Code: 1406, SQLState: HY000

2016-05-27 10:30:35.951 WARN 2660 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data too long for column ‘name‘ at row 1

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement

此时查数据库中,创建了name从AAA到GGG的记录,没有HHHHHHHHHH、III、JJJ的记录。而若这是一个希望保证完整性操作的情况下,AAA到GGG的记录希望能在发生异常的时候被回退,这时候就可以使用事务让它实现回退,做法非常简单,我们只需要在test函数上添加@Transactional注解即可。


1

2

3

4

5

6

7


@Test

@Transactional

public void test() throws Exception {

// 省略测试内容

}

再来执行该测试用例,可以看到控制台中输出了回滚日志(Rolled back transaction for test context),


1

2

3

4

5

6

7


2016-05-27 10:35:32.210 WARN 5672 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1406, SQLState: 22001

2016-05-27 10:35:32.210 ERROR 5672 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data truncation: Data too long for column ‘name‘ at row 1

2016-05-27 10:35:32.213 WARN 5672 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Warning Code: 1406, SQLState: HY000

2016-05-27 10:35:32.213 WARN 5672 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data too long for column ‘name‘ at row 1

2016-05-27 10:35:32.221 INFO 5672 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test context [[email protected] testClass = ApplicationTests, testInstance = [email protected], testMethod = [email protected], testException = org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement, mergedContextConfiguration = [[email protected] testClass = ApplicationTests, locations = ‘{}‘, classes = ‘{class com.didispace.Application}‘, contextInitializerClasses = ‘[]‘, activeProfiles = ‘{}‘, propertySourceLocations = ‘{}‘, propertySourceProperties = ‘{}‘, contextLoader = ‘org.springframework.boot.test.SpringApplicationContextLoader‘, parent = [null]]].

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement

再看数据库中,User表就没有AAA到GGG的用户数据了,成功实现了自动回滚。

这里主要通过单元测试演示了如何使用@Transactional注解来声明一个函数需要被事务管理,通常我们单元测试为了保证每个测试之间的数据独立,会使用@Rollback注解让每个单元测试都能在结束时回滚。而真正在开发业务逻辑时,我们通常在service层接口中使用@Transactional来对各个业务逻辑进行事务管理的配置,例如:


1

2

3

4

5

6

7

public interface UserService {

@Transactional

User login(String name, String password);

}

事务详解

上面的例子中我们使用了默认的事务配置,可以满足一些基本的事务需求,但是当我们项目较大较复杂时(比如,有多个数据源等),这时候需要在声明事务时,指定不同的事务管理器。对于不同数据源的事务管理配置可以见《Spring Boot多数据源配置与使用》中的设置。在声明事务时,只需要通过value属性指定配置的事务管理器名即可,例如:@Transactional(value="transactionManagerPrimary")

除了指定不同的事务管理器之后,还能对事务进行隔离级别和传播行为的控制,下面分别详细解释:

#### 隔离级别

隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。

我们可以看org.springframework.transaction.annotation.Isolation枚举类中定义了五个表示隔离级别的值:


1

2

3

4

5

6

7


public enum Isolation {

DEFAULT(-1),

READ_UNCOMMITTED(1),

READ_COMMITTED(2),

REPEATABLE_READ(4),

SERIALIZABLE(8);

}

  • DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是:READ_COMMITTED
  • READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
  • READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  • REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
  • SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

指定方法:通过使用isolation属性设置,例如:


1

@Transactional(isolation = Isolation.DEFAULT)

传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。

我们可以看org.springframework.transaction.annotation.Propagation枚举类中定义了6个表示传播行为的枚举值:


1

2

3

4

5

6

7

8

9


public enum Propagation {

REQUIRED(0),

SUPPORTS(1),

MANDATORY(2),

REQUIRES_NEW(3),

NOT_SUPPORTED(4),

NEVER(5),

NESTED(6);

}

  • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED

指定方法:通过使用propagation属性设置,例如:


1

@Transactional(propagation = Propagation.REQUIRED)

完整示例Chapter3-3-1

时间: 2024-10-08 10:53:34

springboot的事务管理的相关文章

springboot mybatis 事务管理

本文主要讲述springboot提供的声明式的事务管理机制. 一.一些概念 声明式的事务管理是基于AOP的,在springboot中可以通过@Transactional注解的方式获得支持,这种方式的优点是: 1)非侵入式,业务逻辑不受事务管理代码的污染. 2)方法级别的事务回滚,合理划分方法的粒度可以做到符合各种业务场景的事务管理. 本文使用目前最常用的mybatis框架来配置springboot的事务管理机制.下面进入配置方法介绍. 二.springboot mybatis事务配置 1.看一下

SpringBoot之事务管理Transactional

以前学ssh ssm都有事务管理service层通过applicationContext.xml配置,所有service方法都加上事务操作: 用来保证一致性,即service方法里的多个dao操作,要么同时成功,要么同时失败: springboot下的话 一个@Transactional即可搞定: 我们这里搞一个实例,转账实例,A用户转账给B用户xx元 设计如下: Account类 1 package com.hik.entity; 2 3 import javax.persistence.Co

【Spring Boot学习之四】Spring Boot事务管理

环境 eclipse 4.7 jdk 1.8 Spring Boot 1.5.2 一.springboot整合事务事务分类:编程事务.声明事务(XML.注解),推荐使用注解方式,springboot默认集成事物,只主要在方法上加上@Transactional即可1.controller package com.wjy.controller; import org.springframework.beans.factory.annotation.Autowired; import org.spri

springboot多个事务管理

目录 导入依赖 在application.properties配置文件中对两个数据库的配置内容稍作修改. 在java文件夹下新建dbconfig包 在上篇文章中已经配置了数据源的两个配置类,这里需要修改一下. 启动类加上注解配置 @(springboot多个事务管理) 参考上篇文章配置多数据源 Springboot使用jta管理多个事务. 导入依赖 <!-- jta 管理多个数据源的事务--> <dependency> <groupId>org.springframew

企业分布式微服务云SpringCloud SpringBoot mybatis (十七)Spring Boot中的事务管理

快速入门 在Spring Boot中,当我们使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依赖的时候,框架会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager.所以我们不需要任何额外配置就可以用@Transactional注解进行事务的使用. 我们以之前实现的<用spring-data-jpa访问数据库>的示例Chapter3-2-2作为基础工程进行事务的使用常识

ShardingJdbc:Springboot集成ShardingSphere,单服务跨数据源时,简单实现事务管理

阅读下面实验之前:请先阅读官方的分布式事务支持内容:https://shardingsphere.apache.org/document/current/cn/features/transaction/ 经过试验,单服务跨数据源,使用shardingJdbc的事务,同普通jdbc时的做法一样.实验版本 shrading-jdbc-spring-boot-starter ver.3.1.0 1)启动类 (前提:pom引入spring-boot-starter-jdbc或者引入spring-boot

spring boot配置mybatis和事务管理

spring boot配置mybatis和事务管理 一.spring boot与mybatis的配置 1.首先,spring boot 配置mybatis需要的全部依赖如下: <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId>

Spring事务管理总结

事务定义 事务管理对于企业应用来说是至关重要的,主要作用是用来保证数据的一致性,比如转账问题.如下伪代码所示: transaction begin A账户扣100 B账户加100 transaction commit 要么这两部操作都完成,要么都不做(原子性),否则数据就不完整.因为在一个事务中的操作可以看成是一个统一整体,所以事务可以定义为一个不可分割的工作单元. 一个事务可以以两种方式结束:提交或者回滚.当事务提交时,所有对数据的修改都会保存.如果事务中的某一部操作失败了,则事务回滚,并且在

@Transactional(事务讲解)和springboot 整合事务

概述 事务在编程中分为两种:声明式事务处理和编程式事务处理 编程式事务处理:编码方式实现事务管理,常与模版类TransactionTemplate(推荐使用) 在业务代码中实现事务. 可知编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现. 声明式事务处理: 声明式事务实现方式主要有2种,一种为通过使用Spring的<tx:advice>定义事务通知与AOP相关配置实现,另为一种通过@Transac