Spring Cloud Spring Boot mybatis Distributed Microservice Cloud Architecture (31) Transaction Management (1)

Keywords: JDBC Spring SQL Database

When we develop enterprise applications, an operation for business people is actually a combination of multi-step operation of data reading and writing. As data operations are executed sequentially, any step of operation may be abnormal, which will lead to subsequent operations can not be completed. At this time, because the business logic is not completed correctly, the previous successful operation of data is not reliable and needs to be retreated in this case.

The purpose of a transaction is to ensure that every operation of the user is reliable. Every operation in a transaction must be successfully executed, and if there is an exception, it will return to the state where the transaction has not been operated at the beginning.

Transaction management is one of the most commonly used functions in Spring framework. When we use Spring Boot to develop applications, we also need to use transactions in most cases.

quick get start

In Spring Book, when we use spring-boot-starter-jdbc or spring-boot-starter-data-jpa dependencies, the framework automatically defaults to injecting DataSource Transaction Manager or Jpa Transaction Manager, respectively. So we can use the @Transactional annotation for transactions without any additional configuration.

An example of using spring-data-jpa to access databases Chapter3-2-2 Common sense of doing business as a basic project.

In this sample project (if you don't know how to access the data, read this first) Article We introduced spring-data-jpa and created User entity and UserRepository, which is the data access object of User. In the ApplicationTest class, we implemented the unit test case of using UserRepository to read and write data.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class ApplicationTests {

	@Autowired
	private UserRepository userRepository;

	@Test
	public void test() throws Exception {

		// Create 10 records
		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));

		// Omit some subsequent validation operations
	}


}

As you can see, in this unit test case, 10 User entities are created in succession to the database using the UserRepository object. Let's manually create some exceptions to see what happens.

By defining the name attribute length of User to be 5, the exception can be triggered by the name attribute of the User entity being too long at the time of creation.

@Entity
public class User {

    @Id
    @GeneratedValue
    private Long id;

    @Column(nullable = false, length = 5)
    private String name;

    @Column(nullable = false)
    private Integer age;

    // Eliminate constructors, getter s, and setter s

}

Modify the statement that creates the record in the test case to make the name of a record longer than 5, as follows: The User object whose name is HHHHHHHHH HHHHHHH will throw an exception.


// Create 10 records
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));

Executing the test case, you can see that the following exception was thrown in the console. The name field is too long.

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

At this time, the records of name from AAA to GGG are created in the database. There are no records of HHHHHHHHHH HHHH, III, JJJ. If this is a case where integrity is guaranteed, AAA to GGG records are expected to be rolled back when an exception occurs, then transaction can be used to make it rollback. This is very simple. We just need to add it to the test function. @Transactional Annotations are enough.

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

    // Omitting test content

}

To execute the test case again, you can see that the rollback transaction for test context is output in the console.

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

Looking at the database, there is no AAA to GGG user data in the User table, and automatic rollback has been achieved successfully.

Here we demonstrate how to use it mainly through unit testing. @Transactional Annotations declare that a function needs to be transactionally managed. Usually, in order to ensure data independence between each test, we use them in unit tests. @Rollback Annotations allow each unit test to be rolled back at the end. When we really develop business logic, we usually use it in the service layer interface. @Transactional To configure transaction management for each business logic, for example:


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

Source of source code

Posted by yuraupt on Thu, 03 Jan 2019 09:36:10 -0800