The foreword 0.
Thank you for taking time out of your busy schedule to read my notes. If there are any mistakes, please criticize and correct them. If there are places where you disagree with me, I’m happy to discuss them with you. Finally, if you find this note helpful, please give it a thumbs up, thanks ~
1. Introduction
Database transactions exist to ensure “atomicity” of “multiple database operations”. Take the simplest scenario of A bank transfer service, where A transfers 1000 yuan to B. There are two main remittance actions: ① 1000 yuan is deducted from A’s bank account, ② 2000 yuan is added to B’s bank account. If operation ① succeeds and operation ② fails, then A’s account will lose 1000 yuan for nothing, but B’s account will not add 1000 yuan. So we need technology to guarantee the atomicity of operations ① and ② as a whole (that is, operations ① and ② either succeed or fail at the same time), and database transactions are built for this.
When we use the Springboot framework to develop, Springboot has helped us to encapsulate the operation of the underlying database transactions, reducing the cost of learning and operating using database transactions. This note simply records how to configure the use of transactions in Springboot framework (Springboot version 2.3.1.RELEASE, integration of Mybatis, database using MySQL), and when using Springboot transactions encountered pits.
2.Springboot implements three technologies for transaction support
Springboot wants a method to use database transactions by simply adding @Transactional to the corresponding method. (For details on the parameters of the @Transactional annotation, check out the Web or the @Transactional annotations in the source code.)
Springboot has three technical ways to make @transactional methods work with database transactions: “dynamic proxy (runtime weaving)”,” compile-time weaving “, and “class-load weaving”. All of these three technologies are based on AOP(Aspect Oriented Programming) idea. (I read a lot of articles on the Internet, and most people refer to AOP as a technology. In fact, AOP is not a specific technology, but a programming paradigm based on AOP programming paradigm, different programming languages have their own implementation.)
Let’s see how Springboot can be configured to support @Transactional open database transactions based on “dynamic proxy” and “compile-time weaving (using AspectJ),” respectively. (The “dynamic proxy” based approach (which supports @Transactional) has some pitfalls to be aware of, as this article will point out.)
2.1. Support @Transactional based on dynamic proxy
2.1.1. The configuration
-
Add spring-TX dependencies to poM
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.7. RELEASE</version> </dependency> Copy the code
-
Enable transaction support in your SpringBoot application using annotations (you can also use XML or Java configuration classes, but not as fast as using annotations). Using the @ EnableTransactionManagement annotations (from the above introduction of spring – tx package)
@SpringBootApplication @EnableTransactionManagement public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code
Spring recommended way is will be @ @ EnableTransactionManagement added to the Configuration of annotation on the class, and @ SpringBootApplication @ SpringBootConfiguration annotations, @ SpringBootConfiguration is @ the Configuration, so here we @ EnableTransactionManagement annotations can be added to be @ SpringBootApplication annotation on the class.
2.1.2. Test
-
Create the TransactionController for the test
package com.huang.spring.practice.transaction; import com.huang.spring.practice.user.dao.UserMapper; import com.huang.spring.practice.user.dto.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * */ @RestController @RequestMapping("/api/transaction") public class TransactionController { @Autowired private UserMapper userMapper; / * * * * test Spring affairs after inserting a new user, intentionally a runtime exception is thrown, the Spring transaction is rolled back, insert the user failed * / @ Transactional @ PostMapping ("/testTransactionThrowExcep") public User testTransactionThrowExcep() { User user = new User(); user.setName("Xiao li"); user.setAge((short) 13); user.setCity("Beijing"); userMapper.insert(user); throw new RuntimeException("Deliberately throwing a runtime exception"); } /** * Insert user */ @transactional @postmapping ("/testTransactionNoExcep") public User testTransactionNoExcep() { User user = new User(); user.setName("Xiao li"); user.setAge((short) 13); user.setCity("Beijing"); userMapper.insert(user); returnuser; }}Copy the code
-
The first call/API/transaction/testTransactionThrowExcep interface
Because we in the final throws a RuntimeException testTransactionThrowExcep interface, so the interface back to 500.
Because there is exception is thrown, so testTransactionThrowExcep interface within the transaction is rolled back, we insert “xiao li” user information will not fall to the database, check the data in a database user, there is no “xiao li” data, explain the Spring transaction to take effect
mysql> select id, name, age,city from user order by id desc; + - + -- -- -- -- -- -- -- -- + + -- -- -- -- -- -- -- -- -- -- -- -- -- + | | id name | age | city | + - + -- -- -- -- -- -- -- -- + + -- -- -- -- -- -- -- -- -- -- -- -- -- + | 1 | xiaoming | | | shenzhen 18 + - + -- -- -- -- -- -- -- -- + + -- -- -- -- -- -- -- -- -- -- -- -- -- + 1 row in the set (0.00 SEC)Copy the code
- Then call/API/transaction/testTransactionNoExcep interface, the interface on success, return 200 HTTP status code and insert into the database of new users “xiao li” of information:
Mysql > select * from user where user = ‘user’;
mysql> select id, name, age,city from user order by id desc; + - + -- -- -- -- -- -- -- -- + + -- -- -- -- -- -- -- -- -- -- -- -- -- + | | id name | age | city | + - + -- -- -- -- -- -- -- -- + + -- -- -- -- -- -- -- -- -- -- -- -- -- + | | 4 xiao li 13 | | Beijing | | 1 | | the man 18 | | shenzhen + - + -- -- -- -- -- -- -- -- + + -- -- -- -- -- -- -- -- -- -- -- -- -- + 2 rows in the set (0.00 SEC)Copy the code
-
2.1.3. Pit of transaction failure
When implementing @Transactional with dynamic proxy support, you should be aware of situations where @Transactional does not work. It is best to write a unit test to see if the transaction works as expected when you add @Transactional methods.
For a specific @Transactional transaction failure scenario, see this article on 8 Reasons Spring Transactions fail! It’s quite detailed. In this article, I discuss scenarios in which methods annotated by @Transactional are not public and methods annotated by @Transactional are self-called by other methods in the same class. Transactions fail because of dynamic proxy. As mentioned earlier, the @Transactional annotation is the Spring framework’s AOP-based programming paradigm for implementing methods annotated by @Transactional through dynamic proxy techniques to implement database transactions. Suppose A method of class A is annotated by @Transactional, but method A has private access. The Spring framework injects instances of class A into the Spring container to become beans. When adding bean A with A “dynamic proxy”, the private methods are ignored because you cannot call the private methods directly from the instance object outside the instance, as in this example, TransactionService updateUserAgeByIdTransactional method is private, cannot be called directly in TransactionController:
A dynamic proxy cannot delegate to a private method, and the @Transactional annotation attached to the private method will no longer be valid.
Transactional transactions do not work when a method annotated by @Transactional is called automatically by other methods in the same class. This is also the reason for dynamic proxy. So when we call our own @Transactional method through a class internal method, we call our @Transactional method through an instance of that class (the real, original, undynamically propped bean of that class in Spring). Instead of being called by instance objects that Spring has enhanced with dynamic proxies (parsing supports the @Transactional annotation), the @Transactional annotation naturally does not work.
public void updateUserAgeById(long userId, short age) {
updateUserAgeByIdTransactional(userId, age); / / 1.
this.updateUserAgeByIdTransactional(userId, age); / / 2.
}
@Transactional
public void updateUserAgeByIdTransactional(long userId, short age) {
User user = new User();
user.setId(userId);
user.setAge(age);
userMapper.updateByPrimaryKeySelective(user);
throw new RuntimeException();
}
Copy the code
However, if we must make the @Transactional annotation available to private methods and make it work when methods within a class call themselves, we can use “compile-time weaving” or “class-load weaving” to enhance the methods of our target class before running the code. You don’t have to deal with the limitations of implementing a “dynamic proxy.” The rest of this article will show you how to use AspectJ to weave methods for the @Transactional annotation at compile time.
2.2. Support @Transactional based on AspectJ compile time weaving
The principle behind AspectJ’s compile-time weaving is the technique of dynamically generating class bytecodes, modifying the class file we were originally generating to add the desired function code to it.
Configuration of 2.2.1.
Sets the mode of @ EnableTransactionManagement to AdviceMode. ASPECTJ (default is AdviceMode PROXY, also is our dynamic PROXY ~)
package com.huang.spring.practice;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
@SpringBootApplication(scanBasePackages = {"com.huang.*"})
//@EnableTransactionManagement
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
@MapperScan({"com.huang.spring.practice"})
public class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code
Adding dependencies to poM files and configuring AspectJ’s Maven plugin (our project is managed through Maven) allows the project to modify and create class files that need to be woven in at compile time through AspectJ:
<! -- AspectJ code weaving -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7. RELEASE</version>
</dependency>
<! Add AspectJ plugin -->
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Copy the code
2.2.2. Test
Add the @Transactional annotation to a private method and call it from within the class to see if the transaction works.
To provide an interface to test in TransactionController/API/transaction/updateUserAgeById
package com.huang.spring.practice.transaction.controller;
import com.huang.spring.practice.transaction.service.TransactionService;
import com.huang.spring.practice.user.dao.UserMapper;
import com.huang.spring.practice.user.dto.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/transaction")
public class TransactionController {
@Autowired
private TransactionService transactionService;
@PostMapping("/updateUserAgeById/{userId}/{age}")
public void updateUserAgeById(@PathVariable("userId") long userId, @PathVariable("age") short age) { transactionService.updateUserAgeById(userId, age); }}Copy the code
TransactionService updateUserAgeById method is as follows, through internal self calls, call add @ the private methods of Transactional annotation updateUserAgeByIdTransactional, This method updates the age of the user with the specified ID and throws a RuntimeException at the end of the method. If we call/API/transaction/updateUserAgeById, after the user’s age has been updated, said people had no effect, and the transaction takes effect.
package com.huang.spring.practice.transaction.service;
import com.huang.spring.practice.user.dao.UserMapper;
import com.huang.spring.practice.user.dto.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
@Service
public class TransactionService {
@Autowired
private UserMapper userMapper;
public void updateUserAgeById(long userId, short age) {
this.updateUserAgeByIdTransactional(userId, age);
}
@Transactional
private void updateUserAgeByIdTransactional(long userId, short age) {
User user = new User();
user.setId(userId);
user.setAge(age);
userMapper.updateByPrimaryKeySelective(user);
throw newRuntimeException(); }}Copy the code
Select * from user where id=1 and age = 11;
Start the app:
The @Transactional annotated method transaction still doesn’t work when you test the application in IDEA. Presumably the “code” IDEA uses to launch the application wasn’t woven into AspectJ’s compile time.
When maven builds our project in compile phase, we will call aspectj-maven-plugin according to our configuration in POM file, and weave it into compile time:
maven package Copy the code
After the Maven build is complete, the jar package for our SpringBoot application will be generated in the target directory of the project:
To verify that AspectJ has woven transactions into methods that use @Transactional, we can decomcompile the jar package we just typed to see if transactionService.java has been woven into it. Decompiler decompiler decompiler decompiler decompiler decompiler decompiler decompiler decompiler decompiler decompiler
After decomcompiling the application package, you can see that transactionService.java is processed by the AspectJ plug-in and generates three class files
The code for viewing transactionService.class is as follows:
package com.huang.spring.practice.transaction.service; import com.huang.spring.practice.user.dao.UserMapper; import com.huang.spring.practice.user.dto.User; import java.io.PrintStream; import org.aspectj.lang.JoinPoint.StaticPart; import org.aspectj.runtime.internal.Conversions; import org.aspectj.runtime.reflect.Factory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.aspectj.AbstractTransactionAspect; import org.springframework.transaction.aspectj.AnnotationTransactionAspect; @Service public class TransactionService { @Autowired private UserMapper userMapper; private static final JoinPoint.StaticPart ajc$tjp_0; private static final JoinPoint.StaticPart ajc$tjp_1; private static void ajc$preClinit() { Factory localFactory = new Factory("TransactionService.java", TransactionService.class); ajc$tjp_0 = localFactory.makeSJP("method-execution", localFactory.makeMethodSig("1"."testTransactionModifyDto"."com.huang.spring.practice.transaction.service.TransactionService".""."".""."com.huang.spring.practice.user.dto.User"), 17); ajc$tjp_1 = localFactory.makeSJP("method-execution", localFactory.makeMethodSig("1"."updateUserAgeByIdTransactional"."com.huang.spring.practice.transaction.service.TransactionService"."long:short"."userId:age".""."void"), 38); } public void updateUserAgeById(long userId, short age) { updateUserAgeByIdTransactional(userId, age); updateUserAgeByIdTransactional(userId, age); } @Transactional public void updateUserAgeByIdTransactional(long userId, short age) { long l = userId; short s = age; Object[] arrayOfObject = new Object[3]; arrayOfObject[0] = this; arrayOfObject[1] = Conversions.longObject(l); arrayOfObject[2] = Conversions.shortObject(s); AnnotationTransactionAspect.aspectOf().ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96c(this.new TransactionService.AjcClosure3(arrayOfObject), ajc$tjp_1); } static final void updateUserAgeByIdTransactional_aroundBody2(TransactionService ajc$this.long userId, short age) { User user = new User(); user.setId(Long.valueOf(userId)); user.setAge(Short.valueOf(age)); ajc$this.userMapper.updateByPrimaryKeySelective(user); throw new RuntimeException(); } static{}}Copy the code
As you can see, the code in transactionService.class is already woven into AspectJ. As mentioned above, AspectJ uses the technique of “dynamically generating class bytecode” to help us automatically modify the generated class bytecode at the specified location in the code. “Enhance” the code as we expect. This does free up a lot of manpower and ease development that would have been exhausting if we had to write the code that AspectJ automatically generated above ourselves.
Ok, now let’s start our application with the typed JAR package. Instead of starting the project in IDEA, we directly start the application package we just built with Maven using the Java command on the local computer:
Java - jar spring. Practice - 0.0.1 - the SNAPSHOT. The jarCopy the code
After the application is started, the interface is called to update the age of Xiaoming to 66, and the interface returns 500. Because there is a RuntimeException thrown in the interface, the user table of the database is checked again to see whether the age of Xiaoming is updated. To see if our @Transactional annotation is working,
The @Transactional annotation has been implemented, and AspectJ’s compile-time injection of the @Transactional annotation has been implemented. If we add @transactiona to the method, we don’t have to worry about the method’s access rights (private) and whether the method is called internally.
2.3 Pay attention to pits
Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional Transactional
In A Spring transaction, you query some data from the database, return the Java object of the data, and change the A member variable of the Java object to the value OF A, but do not update the result to the database. Then, in the same transaction, query this data again, and the value of A member variable of the Java object of this data returned is actually the value of b after the modification. The specific test code is as follows:
2.3.1.1 Scenario Reappearance
-
Add an interface testTransactionModifyDto to the TransactionController mentioned above
package com.huang.spring.practice.transaction.controller; import com.huang.spring.practice.transaction.service.TransactionService; import com.huang.spring.practice.user.dao.UserMapper; import com.huang.spring.practice.user.dto.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/transaction") public class TransactionController { @Autowired private TransactionService transactionService; @PostMapping("/testTransactionModifyDto") public User testTransactionModifyDto(a) { transactionService.testTransactionModifyDto(); User user2 = userMapper.selectByPrimaryKey(4L); System.out.println("Outside the transaction, query DB for user id 4 and print his age:" + user2.getAge()); returnuser2; }}Copy the code
-
Create TransactionService and add testTransactionModifyDto() method annotated by transaction @Transactional
package com.huang.spring.practice.transaction.service; import com.huang.spring.practice.user.dao.UserMapper; import com.huang.spring.practice.user.dto.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class TransactionService { @Autowired private UserMapper userMapper; @Transactional public User testTransactionModifyDto(a) { User user = userMapper.selectByPrimaryKey(4L); System.out.println("In transaction, query DB for user id 4 and print his age:" + user.getAge()); user.setAge((short) 80); System.out.println("In a transaction, set the age of user 4 to 80 and update it to the database without executing the update command."); user = userMapper.selectByPrimaryKey(4L); System.out.println("In transaction, query DB for user 4 and print his age:" + user.getAge()); returnuser; }}Copy the code
-
Call/API/transaction/testTransactionModifyDto interface, in the process of the interface to invoke print log is as follows:
In a transaction, query the DB for user 4 and print his age: 13. In a transaction, set the age of user 4 to 80 and update it to the database without executing the update command. In the transaction, query DB for user 4 and print his age: 80. Outside the transaction, query DB for user 4 and print his age: 13Copy the code
-
2.3.2.2. Fault location
Say nothing, and debug is done.
Debug the line in the red box:
MyBatis’ baseexecutor. Java code query method (1) will try to obtain the current SQL execution result in localCache. If there is the current SQL execution result, return the current result (list) key (2). If list is null, the queryFromDatabase method at 3 in the figure executes the SQL query results in the database.
Let’s take a look at what’s in the CacheKey used for the query cache
As shown in the figure above, the CacheKey holds Mapper information for the SQL to be executed, SQL statements, and input arguments to the SQL. So take a wild guess:
Mybatis will use the “Mapper information, SQL statement and SQL input parameter” of this SQL as the cache key after an SQL query, and then cache the SQL execution result when the next execution, if there is the SQL execution result cache will be directly used.
SQL > select * from user where id=4 and id=4; SQL > select * from user where id=4 and id=4; The result would be the same every time, which would be confusing, so any execution result cache stored in localCache must be in effect in a limited scope.
User user = userMapper.selectByPrimaryKey(4L);
Copy the code
The next step is to figure out when the cache in localCache is added and removed.
We can see that localCache is an instance called PerpetualCache
Entering the PerpatualCache class, you can see that the cache is stored in a HashMap called Cache
Add breakpoints on putObject, removeObject, and Clear, the three methods used to manipulate cache variables in PerpatualCache, and then debug the SQL query again, as shown in the red box below.
PerpatualCache putObject = queryFromDatabase = queryFromDatabase = BaseExecutor; The result of the query from db is saved to localCache(PrepetualCache)
The PerpatualCache clear method is not called, indicating that the PerpatualCache cache is still stored in the PerpatualCache. We continue debugging into the second red box in the image below
The SQL query in the second red box, as expected, fetches the result of the first red box directly from localCache and returns,
This explains the phenomenon mentioned above:
In A Spring transaction, you query some data from the database, return the Java object of the data, and change the A member variable of the Java object to the value OF A, but do not update the result to the database. Then, in the same transaction, query this data again, and the value of A member variable of the Java object of this data returned is actually the value of b after the modification.
Let’s draw a picture to illustrate:
(1) Create a heap on the JVM to store the user (id=4) on the heap.
②③ : After the data is retrieved from the database, mybatis will cache the result in PrepetualCache(localCache), PrepetualCache has a pointer to the memory address where the user data resides.
The user variable in the testTransactionModifyDto method points to the memory address of the user data retrieved from the database and stored in the heap in step 1.
⑤ : Change the age of user data in heap to 80 using the user variable
6. 7: In the same transaction, the transaction has not been committed, so the current thread’s PrepetualCache has not been cleared, and the same SQL is executed to obtain the memory address of the user in the heap from PrepetualCache. The user variable in the testTransactionModifyDto method again points to this memory address
Then continue debugging, coming out of this method in the transaction
The clear method called PerpetualCache (which we cleverly set up ahead of time) clears all caches. If you look at the call stack of the method, you can see that many classes of commit methods are called because the transaction method is finished and Spring commits the SQL during the transaction to the database so that our data operations during the transaction end up in DB.
Mybatis: PrepetualCache (clear); Mybatis: PrepetualCache (clear); Mybatis: PrepetualCache (clear); Mybatis: PrepetualCache (clear); Mybatis: PrepetualCache (clear); Mybatis: PrepetualCache (clear); How to make Spring framework code call Mybatis framework code? (I specially debug this problem, Found by Mybatis SqlSessionUtils TransactionSynchronizationManager, TransactionSynchronizationUtils realization and Spring, The specific process to write words to express a little cumbersome. Guys are interested can look at the debug, can be found in the Spring of transaction in order to let the other persistence layer framework integration in, is to provide an interface TransactionSynchronization, third-party persistence layer framework implements this interface, And their implementation to registration to Spring the TransactionSynchronizationManager synchronizations, so the Spring can through a third party in the persistence layer framework to deal with affairs. If you pay attention, it is not hard to find that many project software will adopt similar practices. By providing an interface to decouple implementations from the framework code in various situations, and then registering the corresponding implementation in the framework as needed, this coding trick (idea) can be learned a lot and is very helpful in helping us build robust, highly maintainable projects.
2.3.2.3. Processing mode
If you want to avoid the influence of mybatis localCache, the SQL (statment) execution results in the same SqlSession will not be cached by localCache. You can set the localCacheScope of Mybatis to STATEMENT.
Setting Description Valid Values Default localCacheScope MyBatis uses local cache to prevent circular references and speed up repeated nested queries. By default (SESSION) all queries executed during a session are cached. If localCacheScope=STATEMENT local session will be used just for statement execution, no data will be shared between two different calls to the same SqlSession. SESSION | STATEMENT SESSION
The specific configuration is as follows (here the Springboot configuration file is used to configure mybatis configuration, you can also use Java code to configure) :
mybatis:
configuration:
local-cache-scope: statement
Copy the code
After this setting, localCache will not take effect.
If you do not want to set the scope of mybatis localCache to statement, and want to avoid the scenario described in section 2.3.1.1 of this article, you can use the object deep copy mode, the code is as follows:
@Transactional
public User testTransactionModifyDto(a) {
User user = userMapper.selectByPrimaryKey(4L);
System.out.println("In transaction, query DB for user id 4 and print his age:" + user.getAge());
/** * Use the object stream to make a deep copy of the object. ** * Use the object stream stream to make a deep copy of the object. ** * Use the object stream stream. https://www.hutool.cn/ * * objectutil. cloneByStream the source code is simple, Note that for deep copy using byte stream, the object to be copied must implement the Serializable interface. Otherwise, serialization and deserialization cannot be implemented. Copy will fail. * /
user = ObjectUtil.cloneByStream(user);
user.setAge((short) 80);
System.out.println("In a transaction, set the age of user 4 to 80 and update it to the database without executing the update command.");
user = userMapper.selectByPrimaryKey(4L);
System.out.println("In transaction, query DB for user 4 and print his age:" + user.getAge());
return user;
}
Copy the code
The output after modification is as follows:
In a transaction, query the DB for user 4 and print his age: 13. In a transaction, set the age of user 4 to 80 and update it to the database without executing the update command. In the transaction, query DB for user 4 and print age: 13. In the transaction, query DB for user 4 and print age: 13Copy the code
3. Project code address
Github.com/ambition080…