One Spring transaction is widely used by transactions. One transaction method should not call another transaction method. Otherwise, two transactions will be generated. As a result, developers are tied up when designing transaction methods for fear of stepping on the mines accidentally.
In fact, this is a misunderstanding caused by lack of understanding of the Spring transaction propagation mechanism. Spring's support for transaction control is described in the TransactionDefinition class, which has the following important interface methods:
Int getPropagationBehavior (): Transaction Propagation Behavior
Int getIsolationLevel (): transaction isolation level
Int getTimeout (): The expiration time of the transaction.
Boolean isReadOnly (): read/write features of transactions
Obviously, apart from the propagation of transactions, other features of transactions are implemented by Spring with the help of underlying resources. Spring serves only as a proxy. However, the Propagation Behavior of transactions is the function provided by Spring based on its own framework. It is the most precious gift Spring provides to developers. The rumor has ruined the most beautiful halo of Spring's transaction framework.
The transaction Propagation Behavior refers to how transactions spread between these methods when multiple transaction methods are called each other. Spring supports the following seven transaction propagation behaviors.
PROPAGATION_REQUIRED: Creates a transaction if no transaction exists. If a transaction already exists, it is added to the transaction. This is the most common choice.
PROPAGATION_SUPPORTS: supports the current transaction. If no transaction exists, it is executed in non-transaction mode.
PROPAGATION_MANDATORY: use the current transaction. If no transaction exists, an exception is thrown.
PROPAGATION_REQUIRES_NEW: Creates a transaction. If a transaction exists, it is suspended.
PROPAGATION_NOT_SUPPORTED: executes operations in non-transaction mode. If a transaction exists, the current transaction is suspended.
PROPAGATION_NEVER: runs in non-transaction mode. If a transaction exists, an exception is thrown.
PROPAGATION_NESTED: if a transaction exists, it is executed within the nested transaction. If no transaction exists, perform a similar operation as PROPAGATION_REQUIRED.
The default transaction Propagation Behavior of Spring is PROPAGATION_REQUIRED, which is suitable for the vast majority of cases. If multiple ServiveX # methodX () tasks are performed in the transaction environment (that is, they are all enhanced by Spring transactions ), the program has the following call chains: Service1 # method1 ()-> Service2 # method2 ()-> Service3 # method3 (), the three methods of these three services all work in the same transaction through Spring's transaction propagation mechanism.
Nested service methods
Let's take a look at the instance. The UserService # logon () method internally calls the UserService # updateLastLogon Time () and ScoreService # addScore () methods, both of which inherit from BaseService. Shows the class structure between them:
The UserService # logon () method internally calls the ScoreService # addScore () method. Both of them use Spring AOP for transaction enhancement, and they work in the same transaction. Let's look at the specific code:
01 <SPAN style = "FONT-SIZE: 14px; COLOR: #006600; FONT-FAMILY: Microsoft YaHei" minmax_bound = "true"> package com. baobaotao. nestcall;
02...
03 @ Service ("userService ")
04 public class UserService extends BaseService {
05 @ Autowired
06 private JdbcTemplate jdbcTemplate;
07
08 @ Autowired
09 private ScoreService scoreService;
10
11 // ① this method nested calls other methods of this class and other service class methods
12 public void logon (String userName ){
13 System. out. println ("before userService. updateLastLogonTime ...");
14 updateLastLogonTime (userName); // ①-1 other methods of this service class
15 System. out. println ("after userService. updateLastLogonTime ...");
16
17 System. out. println ("before scoreService. addScore ...");
18 scoreService. addScore (userName, 20); // ①-2 other methods of other service classes
19 System. out. println ("after scoreService. addScore ...");
20
21}
22 public void updateLastLogonTime (String userName ){
23 String SQL = "UPDATE t_user u SET u. last_logon_time =? WHERE user_name =? ";
24 jdbcTemplate. update (SQL, System. currentTimeMillis (), userName );
25} </SPAN>
ScoreService Bean is injected into UserService, and ScoreService code is as follows:
01 <SPAN style = "FONT-SIZE: 14px; COLOR: #006600; FONT-FAMILY: Microsoft YaHei" minmax_bound = "true"> package com. baobaotao. nestcall;
02...
03 @ Service ("scoreUserService ")
04 public class ScoreService extends BaseService {
05
06 @ Autowired
07 private JdbcTemplate jdbcTemplate;
08
09 public void addScore (String userName, int toAdd ){
10 String SQL = "UPDATE t_user u SET u. score = u. score +? WHERE user_name =? ";
11 jdbcTemplate. update (SQL, toAdd, userName );
12}
13} </SPAN>
Add Spring AOP transaction enhancement for all public methods in ScoreService and UserService through Spring configuration, so that the UserService's logon (), updateLastLogonTime (), and ScoreService's addScore () methods All work in the transaction environment. The following is the key configuration code:
01 <SPAN style = "FONT-SIZE: 14px; COLOR: #006600; FONT-FAMILY: Microsoft YaHei" minmax_bound = "true"> <? Xml version = "1.0" encoding = "UTF-8"?>
02 <beans xmlns = "http://www.springframework.org/schema/beans"
03 xmlns: xsi = "http://www.w3.org/2001/XMLSchema-instance"
04 xmlns: context = "http://www.springframework.org/schema/context"
05 xmlns: p = "http://www.springframework.org/schema/p" xmlns: aop = "http://www.springframework.org/schema/aop"
06 xmlns: tx = "http://www.springframework.org/schema/tx"
07 xsi: schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
08 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd ">
09 <context: component-scan base-package = "com. baobaotao. nestcall"/>
10...
11 <bean id = "jdbcManager"
12 class = "org. springframework. jdbc. datasource. DataSourceTransactionManager"
13 p: dataSource-ref = "dataSource"/>
14
15 <! -- ① Use the following configuration to add transaction enhancement for all public methods that inherit all subclasses of the BaseService class -->
16 <aop: config proxy-target-class = "true">
17 <aop: pointcut id = "serviceJdbcMethod"
18 expression = "within (com. baobaotao. nestcall. BaseService +)"/>
19 <aop: advisor pointcut-ref = "serviceJdbcMethod" advice-ref = "jdbcAdvice" order = "0"/>
20 </aop: config>
21 <tx: advice id = "jdbcAdvice" transaction-manager = "jdbcManager">
22 <tx: attributes>
23 <tx: method name = "*"/>
24 </tx: attributes>
25 </tx: advice>
26 </beans> </SPAN>
Set the log level to DEBUG, start the Spring container, and execute the UserService # logon () method. Observe the following output logs carefully:
Reference
Before userService. logon method...
// ① Create a transaction
Creating new transaction with name [com. baobaotao. nestcall. UserService. logon]: PROPAGATION_REQUIRED, ISOLATION_DEFAULT
Acquired Connection [jdbc: mysql: // localhost: 3306/sampledb, UserName = root @ localhost, MySQL-AB JDBC Driver] for JDBC transaction
Switching JDBC Connection [jdbc: mysql: // localhost: 3306/sampledb, UserName = root @ localhost, MySQL-AB JDBC Driver] to manual commit
Before userService. updateLastLogonTime...
<! -- ② UpdateLastLogonTime () and logon () are in the same Bean and are not added to an existing transaction context.
Action, but work "naturally" in the same transaction context -->
Executing prepared SQL update
Executing prepared SQL statement [UPDATE t_user u SET u. last_logon_time =? WHERE user_name =?]
SQL update affected 1 rows
After userService. updateLastLogonTime...
Before scoreService. addScore...
// ③ ScoreService # Add the addScore Method to the transaction context started at ①
Participating in existing transaction
Executing prepared SQL update
Executing prepared SQL statement [UPDATE t_user u SET u. score = u. score +? WHERE user_name =?]
SQL update affected 1 rows
After scoreService. addScore...
Initiating transaction commit
Committing JDBC transaction on Connection [jdbc: mysql: // localhost: 3306/sampledb, UserName = root @ localhost, MySQL-AB JDBC Driver]
...
After userService. logon method...
From the above output log, we can clearly see that Spring started a new transaction for the UserService # logon () method, while UserSerive # updateLastLogonTime () and UserService # logon () in the same class, no transaction Propagation Behavior is observed, and its code block seems to be "directly merged" into UserService # logon.
However, when we execute the ScoreService # addScore () method, we observe a transaction Propagation Behavior: "participant in existing transaction", which indicates ScoreService # addScore () added to the transaction context of UserService # logon (), the two share the same transaction. Therefore, the final result is that logon (), updateLastLogonTime (), and addScore of ScoreService all work in the same transaction.