Spring Cloud Spring Boot mybatis分布式微服务云架构(三十一)事务管理(1)

咱们在开发企业应用时,对于业务人员的一个操做实际是对数据读写的多步操做的结合。因为数据操做在顺序执行的过程当中,任何一步操做都有可能发生异常,异常会致使后续操做没法完成,此时因为业务逻辑并未正确的完成,以前成功操做数据的并不可靠,须要在这种状况下进行回退。html

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

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

快速入门

在Spring Boot中,当咱们使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依赖的时候,框架会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager。因此咱们不须要任何额外配置就能够用@Transactional注解进行事务的使用。数据库

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

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

@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属性超长就能够触发异常产生。spring-boot

@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对象将会抛出异常。单元测试

// 建立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字段超长测试

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注解便可。

@Test
@Transactional
public void test() throws Exception {

    // 省略测试内容

}

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

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 [DefaultTestContext@1d7a715 testClass = ApplicationTests, testInstance = com.didispace.ApplicationTests@95a785, testMethod = test@ApplicationTests, testException = org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement, mergedContextConfiguration = [MergedContextConfiguration@11f39f9 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来对各个业务逻辑进行事务管理的配置,例如:

public interface UserService {
    
    @Transactional
    User login(String name, String password);
    
}

源码来源

相关文章
相关标签/搜索