• 周六. 10 月 5th, 2024

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

Spring boot 2. X Basics: getting started with transaction management

King Wang

1 月 3, 2022

What is business ?

When we develop enterprise applications , Usually, an operation of business personnel is actually a combination of multi-step operations of database reading and writing . Because data operations are in the process of sequential execution , Any step of operation may cause an exception , Exceptions can cause subsequent operations to fail , At this time, because the business logic is not completed correctly , Data from previous successful operations are unreliable , If you want this business to be carried out correctly , There’s usually a way to do it :

  1. Record the location of the failure , After the problem is fixed , Continue to execute the following business logic from the location where the last execution failed
  2. When the execution fails , Back off all processes executed this time , Let the operation return to its original state , After fixing the problem , Re execute the original business logic

Business is about the above way 2 The implementation of the . Business , Generally speaking, it refers to the things to be done or done , It is an operation of the business personnel mentioned above ( For example, in the e-commerce system , An operation to create an order involves creating an order 、 There are two basic operations of goods inventory deduction . If the order is created successfully , Inventory deduction failed , Then there will be the problem of oversold , So the most basic and most important thing is to include these two operational transactions , Make sure that either of these operations are successful , Or they all failed ).

There are many scenarios like this in the actual development process , So let’s learn about it today Spring Boot How to use transaction management in !

Quick start

stay Spring Boot in , When we use it spring-boot-starter-jdbc or spring-boot-starter-data-jpa When we depend , The framework will automatically inject separately by default DataSourceTransactionManager or JpaTransactionManager. So we don’t need any extra configuration to use @Transactional Annotations are used for transaction .

We’ve done it before 《 Use Spring Data JPA visit MySQL》 As an example of basic engineering for the use of transaction learning . In this sample project ( If you don’t know how to access the data , Read the preceding paragraph first ), We introduced spring-data-jpa, And created User Entity and to User Data access object of UserRepository, In the unit test class to achieve the use of UserRepository Unit test cases for data reading and writing , as follows :

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {

@Autowired
private UserRepository userRepository;
@Test
public void test() throws Exception {

// establish 10 Bar record 
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 
}
}

You can see , In this unit test case , Use UserRepository Objects are created in succession 10 individual User Entity to database , Let’s make some exceptions artificially , See what happens .

adopt @Max(50) for User Of age Set the maximum value to 50, This is done by creating User The entity’s age Attribute more than 50 The exception can be triggered when the .

@Entity
@Data
@NoArgsConstructor
public class User {

@Id
@GeneratedValue
private Long id;
private String name;
@Max(50)
private Integer age;
public User(String name, Integer age) {

this.name = name;
this.age = age;
}
}

Execute test case , You can see the following exception thrown in the console , About age Field error :

2020-07-09 11:55:29.581 ERROR 24424 --- [ main] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [Validation failed for classes [com.didispace.chapter310.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{
interpolatedMessage=' Maximum not exceeding 50', propertyPath=age, rootBeanClass=class com.didispace.chapter310.User, messageTemplate='{javax.validation.constraints.Max.message}'}
]]

At this point, check the database User surface :

You can see , The test case is interrupted by an exception after half execution , front 5 Data is inserted correctly and then 5 Data was not inserted successfully , If this 10 Data needs to be all successful or all failed , Then you can use transactions to implement , It’s very simple , All we need to do is test Add… To the function @Transactional Annotations can be .

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

// Omit the test 
}

Then execute the test case , You can see that the rollback log is output in the console (Rolled back transaction for test context),

2020-07-09 12:48:23.831 INFO 24889 --- [ main] o.s.t.c.transaction.TransactionContext : Began transaction (1) for test context [DefaultTestContext@f6efaab testClass = Chapter310ApplicationTests, testInstance = com.didispace.chapter310.Chapter310ApplicationTests@60816371, testMethod = test@Chapter310ApplicationTests, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@3c19aaa5 testClass = Chapter310ApplicationTests, locations = '{}', classes = '{class com.didispace.chapter310.Chapter310Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@34cd072c, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@528931cf, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@2353b3e6, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@7ce6a65d], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@4b85edeb]; rollback [true]
2020-07-09 12:48:24.011 INFO 24889 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext@f6efaab testClass = Chapter310ApplicationTests, testInstance = com.didispace.chapter310.Chapter310ApplicationTests@60816371, testMethod = test@Chapter310ApplicationTests, testException = javax.validation.ConstraintViolationException: Validation failed for classes [com.didispace.chapter310.User] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{
interpolatedMessage=' Maximum not exceeding 50', propertyPath=age, rootBeanClass=class com.didispace.chapter310.User, messageTemplate='{javax.validation.constraints.Max.message}'}
], mergedContextConfiguration = [WebMergedContextConfiguration@3c19aaa5 testClass = Chapter310ApplicationTests, locations = '{}', classes = '{class com.didispace.chapter310.Chapter310Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@34cd072c, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@528931cf, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@2353b3e6, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@7ce6a65d], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]

Look at the database again ,User There is no watch AAA To EEE Of user data , Successful implementation of automatic rollback .

This article mainly demonstrates how to use @Transactional Annotation to declare that a function needs to be managed by a transaction , Usually we unit test to ensure that the data between each test is independent , Will use @Rollback Annotations allow each unit test to be rolled back at the end of the test . And when you’re really developing business logic , We usually service The layer interface uses @Transactional To configure the transaction management of each business logic , for example :

public interface UserService {

@Transactional
User update(String name, String password);
}

Business details

In the example above, we used the default transaction configuration , It can meet some basic transaction requirements , But when our project is large and complex ( such as , There are multiple data sources, etc ), In this case, when declaring a transaction , Specify a different transaction manager . For transaction management configuration of different data sources, see 《Spring Data JPA Multi data source configuration for 》 Settings in . When declaring a transaction , Just go through value Property to specify the name of the configured transaction manager , for example :@Transactional(value="transactionManagerPrimary").

Except after specifying a different transaction manager , It can also control the isolation level and propagation behavior of transactions , The following is a detailed explanation :

Isolation level

Isolation level refers to the isolation level between several concurrent transactions , The main scenarios related to our development include : Dirty read 、 Repeated reading 、 Fantasy reading .

We can see org.springframework.transaction.annotation.Isolation Five values representing the isolation level are defined in the enumeration class :

public enum Isolation {

DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
}
  • DEFAULT: This is the default , Indicates that the default isolation level of the underlying database is used . For most databases , Usually it’s worth :READ_COMMITTED.
  • READ_UNCOMMITTED: This isolation level indicates that one transaction can read the data modified by another transaction but not yet committed . This level does not prevent dirty and non repeatable reads , So this isolation level is rarely used .
  • READ_COMMITTED: This isolation level means that one transaction can only read the data that another transaction has committed . This level prevents dirty reads , This is also the recommended value in most cases .
  • REPEATABLE_READ: This isolation level indicates that a transaction can execute a query repeatedly in the whole process , And the records returned each time are the same . Even if there is new data to satisfy the query between multiple queries , These new records will also be ignored . This level can prevent dirty reading and non repeatable reading .
  • SERIALIZABLE: All transactions are executed one by one , In this way, there is no interference between transactions , in other words , This level prevents dirty reads 、 Unrepeatable reading and phantom reading . But this will seriously affect the performance of the program . This level is not usually used .

Specify the method : By using isolation Property settings , for example :

@Transactional(isolation = Isolation.DEFAULT)

Communication behavior

The so-called communication of affairs refers to , If before starting the current transaction , A transaction context already exists , There are several options to specify the execution behavior of a transactional method .

We can see org.springframework.transaction.annotation.Propagation Enumeration class defines 6 Enumeration values representing propagation behavior :

public enum Propagation {

REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
}
  • REQUIRED: If there are currently transactions , Then join the transaction ; If there is no current transaction , Create a new transaction .
  • SUPPORTS: If there are currently transactions , Then join the transaction ; If there is no current transaction , Continue to run in a non transactional manner .
  • MANDATORY: If there are currently transactions , Then join the transaction ; If there is no current transaction , Throw an exception .
  • REQUIRES_NEW: Create a new transaction , If there are currently transactions , Suspend the current transaction .
  • NOT_SUPPORTED: Run in a non transactional manner , If there are currently transactions , Suspend the current transaction .
  • NEVER: Run in a non transactional manner , If there are currently transactions , Throw an exception .
  • NESTED: If there are currently transactions , Create a transaction to run as a nested transaction of the current transaction ; If there is no current transaction , Then the value is equivalent to REQUIRED.

Specify the method : By using propagation Property settings , for example :

@Transactional(propagation = Propagation.REQUIRED)

Code example

For an example of this article, see the following in the warehouse chapter3-10 Catalog :

  • Github:https://github.com/dyc87112/SpringBoot-Learning/
  • Gitee:https://gitee.com/didispace/SpringBoot-Learning/

If you think this article is good , welcome Star Support , Your concern is the driving force of my persistence !

First article :Spring Boot 2.x Basic course : Introduction to transaction management , Reprint please indicate the source .
Welcome to my official account. : Program the ape DD, Get exclusive learning resources and daily dry goods push .
If you are interested in my other topics , Direct to my personal blog :didispace.com.

发表回复