Spring Transaction Management
After understanding the characteristics of transactions, we will find that transactions are actually a lot of knowledge, but the puzzle is that we usually use Springboot in the development project, we almost rarely write transaction related things, this is because Spring helps us to do transaction management. However, Spring’s transaction management can help us to do the most basic transaction management, if our business becomes more and more complex, multi-data source scenarios or the need for refined transaction management, we need to handle it ourselves.
In fact, Spring uses transaction propagation behavior to manage transactions while processing transactions. For an introduction to the transaction itself, refer to transaction isolation level and transaction concurrency issues. Let’s focus on spring’s transaction propagation behavior. Transaction propagation behavior is a spring concept for managing transactions, not database transactions, to be clear. Example of an explanation of transaction propagation behavior.
Spring transaction propagation behavior
Propagation behavior | describe |
---|---|
PROPAGATION_REQUIRED | If a transaction exists, join the transaction. If there is no transaction currently, a new transaction is created.The default value |
PROPAGATION_REQUIRES_NEW | Creates a new transaction and suspends the current transaction if one exists |
PROPAGATION_SUPPORTS | If a transaction exists, join the transaction. If there is no transaction currently, it continues in a non-transactional manner |
PROPAGATION_NOT_SUPPORTED | Runs nontransactionally and suspends the current transaction if one exists |
PROPAGATION_NEVER | Runs nontransactionally and throws an exception if a transaction currently exists |
PROPAGATION_MANDATORY | If a transaction exists, join the transaction. If there is no transaction currently, an exception is thrown |
PROPAGATION_NESTED | If a transaction exists, a transaction is created to run as a nested transaction of the current transaction; If no current affairs, the value of equivalent to the TransactionDefinition. PROPAGATION_REQUIRED |
The spring transaction
In the early use of SpringMVC, AOP is also used for transaction processing, but are based on XML way, this way requires too much configuration and workload, and after the application of SpringBoot, can better use annotations to deal with, so XML way is not used.XML configuration code
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="Http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<! Load the resource file, which contains variable information, must be loaded first in the Spring configuration file.
<context:property-placeholder location="classpath:mysql.properties" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan">
<list>
<value>com.troyqu.entity</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
</props>
</property>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.user}" />
<property name="password" value="${jdbc.pass}" />
</bean>
<! Configure Hibernate transaction manager -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<! TransactionManager -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="get*" propagation="REQUIRED" />
<tx:method name="*" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config expose-proxy="true">
<! -- Config pointcut -->
<aop:pointcut id="txPointcut" expression="execution(* com.troyqu.service.. *. * (..) )" />
<! -- Configure pointcuts and advice -->
<aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
</aop:config>
</beans>
Copy the code
Aop-based Spring transactions
It is also possible to implement transaction management in SpringBoot using custom annotations in conjunction with AOP, which is simpler and saves code than the XML approach. The AOP configuration here does not mean declarative annotations using @Transactional. This refers to the AOP approach combined with @aspect. We can take a look at the key code.
The database connection class MyConnection is used to manage database connections. Using ThreadLocal to manage database connections eliminates the need to create multiple database connections in the same thread.
package com.troyqu.aop;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
@Component
public class MyConnection {
@Autowired
DataSourceTransactionManager dataSourceTransactionManager;
ThreadLocal<Connection> connection = new NamedThreadLocal<>("MyConnection");
public Connection getConnection(a){
Connection conn = connection.get();
if(conn ! =null) {
return conn;
}
try {
conn = dataSourceTransactionManager.getDataSource().getConnection();
connection.set(conn);
return conn;
} catch (SQLException e) {
e.printStackTrace();
return null; }}public void closeConnection(a){
Connection conn = connection.get();
if(conn ! =null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}finally{ connection.remove(); } } connection.remove(); }}Copy the code
Custom annotations serve as transaction declaration pointcuts
public @interface MyTransactional {
}
Copy the code
Create the transaction processing AOP class TransactionAop to handle methods marked @MyTransactional
package com.troyqu.aop;
import lombok.val;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
@Aspect
@Component
public class TransactionAop {
@Autowired
MyConnection myConnection;
@Around("@annotation(com.anchnet.smartops.cmp.aop.MyTransactional)")
public Object txAround(ProceedingJoinPoint pjp) throws Throwable {
Connection conn = myConnection.getConnection();
Object output = null;
try {
beginTransaction(conn);
output = pjp.proceed();
commitTransaction(conn);
} catch (Throwable e) {
rollBackTransaction(conn);
System.out.println("Error happened inside TX");
e.printStackTrace();
}finally {
myConnection.closeConnection();
returnoutput; }}private void beginTransaction(Connection conn){
try {
conn.setAutoCommit(false);
} catch (SQLException e) {
System.out.println("Start TX failed"); e.printStackTrace(); }}private void commitTransaction(Connection conn){
try {
conn.commit();
} catch (SQLException e) {
System.out.println("Commit failed"); e.printStackTrace(); myConnection.closeConnection(); }}private void rollBackTransaction(Connection conn){
try {
conn.rollback();
} catch (SQLException e) {
System.out.println("Rollback failed"); e.printStackTrace(); myConnection.closeConnection(); }}}Copy the code
Declarative transaction
Now you can use more simple declarative transaction to transaction processing, as long as open the @ EnableTransactionManagement can use declarative transaction, spring – the boot – autoconfigure will introduce relevant Bean we can only be used, To enable Transactional support, simply add @Transactional to the corresponding method and class name. Propagation @transactional allows you to set many attributes, including transactionManager, transaction propagation, etc. The transaction manager can choose the default transaction manager when using a single data element, but if we use multiple data sources, we need to manage the corresponding transaction manager.
An exception occurred to rollback the transaction
Transactional using @Transactional, you can specify a rollback strategy, such as the following code to roll back when an Exception is thrown.
Multiple transaction manager Specifies the corresponding transaction manager
Specify different transaction managers for method1 and method2, respectively.
@Transactional(value="txManager1")
public void method1(Connection conn){
System.out.println("this is method1");
}
@Transactional(value="txManager2")
public void method2(Connection conn){
System.out.println("this is method2");
}
Copy the code
Here are a few examples to look at transaction processing in a specific scenario. For the sake of simplicity, we just output output to simulate real database operations. Database operations in method1 and method2 are rolled back because method1 and method2 are in a transaction with the same @Transactional declaration.
@Transactional
public void tx(a){
method1();
method2();
}
public void method1(a){
System.out.println("this is method1");
int i = 10 / 0;
}
public void method2(a){
System.out.println("this is method2");
}
Copy the code
Method1 and method2 use the @Transactional annotation in the same transaction as method1 and Method2. This is not possible because spring transaction default transmission mechanism is: TransactionDefinition. PROPAGATION_REQUIRED if a transaction exists, to join the transaction; If there is no transaction currently, a new transaction is created.
@Transactional
public void tx(a){
method1();
method2();
}
@Transactional
public void method1(a){
System.out.println("this is method1");
int i = 10 / 0;
}
@Transactional
public void method2(a){
System.out.println("this is method2");
}
Copy the code
Isn’t there any way to isolate transactions after using @transacal? Method1 and method2 require a Propagation mechanism for Propagation.REQUIRES_NEW. Even though the outermost methods have @transactional annotations, But Propagation.REQUIRES_NEW creates a new transaction for method1 and method2, in which case method1 rolls back and method2 handles normally.
@Transactional
public void tx(a){
method1();
method2();
}
@Transactional(propagation= Propagation.REQUIRES_NEW)
public void method1(a){
System.out.println("this is method1");
int i = 10 / 0;
}
@Transactional(propagation= Propagation.REQUIRES_NEW)
public void method2(a){
System.out.println("this is method2");
}
Copy the code
So far, we have had a general understanding of transaction control. The specific control of transaction must be flexibly used in combination with our business. If we encounter multiple data sources, we also need to take the transaction manager into account.
Transactional Transactional failure scenario
- There is an exception that needs to be thrown. If a catch exception is not followed by a throw then the transaction is not rolled back because the exception has already been caught so for @Transactional methods no exception is thrown;
@Transactional(rollbackFor = Exception.class)
@Override
public void unbind(Integer crmId) {
try{ checkIf(! UserUtils.isOpsUser(), AmsCode.USER_NOT_OPS); val crmCustomer = customerMapper.selectByPrimaryKey(crmId); checkIf(crmCustomer ==null, AmsCode.CRM_ACCOUNT_NOT_FOUND); checkIf(crmCustomer.getStatus() ! =null && !CrmCustomerStatus.bound.name().equalsIgnoreCase(crmCustomer.getStatus()), AmsCode.CRM_CUSTOMER_NOT_BIND_ACCOUNT);
val account = accountMapper.selectOne(new AmsAccount().setCrmId(crmId));
if(account ! =null) {
account.setCrmId(null);
accountMapper.updateByPrimaryKey(account);
}
customerMapper.updateByPrimaryKey(crmCustomer.setStatus(CrmCustomerStatus.unbound.name()));
} catch (Exception e) {
// No exception rollback occurs because the exception is caught and not thrown again
log.error("error happened {}", e); }}Copy the code
We can throw an exception in a catch if we want to log an error as well as handle a transaction.
- Methods must be public modifiers. If they are not public modifiers, no error is reported, but the annotations do not take effect (because the dynamic proxy JDK’s dynamic proxy handling interface does not contain non-public methods, and cglib handles public methods).
- Method annotations called through this do not take effect. Calls made inside the current class through this do not create a new proxy, so the annotations do not take effect.
conclusion
- Springboot provides dependencies on transaction management by means of auto-assembly. However, if our business scenarios are complex and encounter multi-data source scenarios, it needs to be flexibly used in combination with actual services.
- To implement Transactional transactions, you can configure transaction manager (Value) and transaction propagation properties to control the business scenarios that meet your needs.