1. API for transaction management in Spring
A transaction is a group of operations that either logically succeed or fail. For example, if user A transfers money to user B, the two operations of user A's account balance reduction and user B's account increase are a group of transactions, and all operations must be successfully or failed to withdraw the operation. There cannot be a situation where A account balance decreases and B increases fail. The transaction has the following characteristics:
- Atomicity: The operation of the transaction is inseparable
- Consistency: The decrease of account A and the increase of B occur together
- Isolation: When multiple transactions are operated, they do not interfere with each other
- Durability: permanent changes after the transaction is committed to the database
Spring provides three interfaces for transaction management. During transaction management, spring will first read transaction definition information such as isolation, propagation, and timeout in TransactionDefinition, and then PlatformTransactionManager will conduct transaction management based on these information, and then will generate Transaction status is saved in TransactionStatus
PlatformTransactionManager
Spring provides the corresponding PlatformTransactionManager interface implementation for different persistence layer frameworks. Spring JDBC and MyBatis correspond to DataSourceTransactionManager, Hibernate corresponds to HibernateTransactionManager, as well as JPA, Jdo, JTA, etc. Different persistence layers use different implementation classes
org.springframework.jdbc.datasource.DataSourceTransaction.Manager
|
Used by Spring JDBC or iBatis to persist data
|
org.springframework.orm.hibernate3.HibernateTransactionManager
|
Hibernate3.0 version is used when persisting data
|
org.springframework.orm.jpa.JpaTransactionManager
|
Used by JPA for persistence
|
org.springframework.jdo.JdoTransaction Manager
|
Used when the persistence mechanism is Jdo
|
org.springframework.transactionjta.JtaTransactionManager
|
JTA transaction management, in a transaction spans multiple
must use resources
|
TransactionDefinition
The constants at the beginning of ISOLATION in the transaction definition information interface are used to define the isolation level of the transaction, the transaction propagation behavior at the beginning of PROPAGATION, and the timeout period defined by TIMEOUT_DEFAULT.
The isolation level is used to resolve dirty reads, non-repeatable reads that may occur when multiple transactions are committed,
- Dirty read: a transaction reads data that has not been committed by another transaction
- Non-repeatable read: One transaction reads the data before and after the update of another transaction, resulting in inconsistency between the two read data before and after
- Phantom read: When one transaction reads, another transaction inserts (insert), resulting in reading records that were not previously available
Transaction isolation level do not have the following four types, if DEFAULT is used as the default isolation level back-end database, such as MySQL using repeatable_read, Oracle database using read_committed level
READ_UNCOMMITED | Allows you to read changed data that has not yet been submitted. May cause dirty, illusory, non-repeatable reads |
READ_COMMITTED | Allow reading after the concurrent event has been submitted. Dirty reads can be prevented, but magic reads and non-repeatable reads can still occur |
REPEATABLE_READ | Multiple reads of the same field are consistent unless the data is changed by the transaction itself. It can prevent dirty and non-repeatable reads, but phantom reads can still occur. |
SERIALIZABLE | Completely obey the isolation level of ACID, by completely locking the data tables involved in the transaction to ensure that no dirty, magic, and non-repeatable reads occur, but the execution efficiency is the lowest |
Propagation line transaction is used to solve the problem of how to pass the transaction when the service layer method calls to each other. For example, transaction a is used in both methods a and b, so does a create a new transaction T when it calls b or does it use the transaction T in b? There are the following seven transmission methods
PROPAGATION_REQUIRED | Support current transaction, create a new one if it does not exist |
PROPAGATION_SUPPORTS | Support the current transaction, if it does not exist, do not use the transaction |
PROPAGATION_MANDATORY | Support the current transaction, if it does not exist, throw an exception |
PROPAGATION_REQUIRES_NEW | If there is a transaction, suspend the current ministry and create a new transaction |
PROPAGATION_NOT_SUPPORTED | Run in non-transactional mode, if there is a transaction, suspend the current transaction |
PROPAGATION_NEVER | Run in non-transactional mode, if a transaction exists, throw an exception |
PROPAGATION_NESTED | If the current transaction exists, execute the nested transaction |
TransactionStatus
It is used to record whether the transaction is completed, whether a savepoint is generated, and whether it can be rolled back.
2. Programmatic transaction management
There are two ways to use transactions in Spring. The first is to implement a transaction by manually writing it. The following figure shows the data table of a user account. Through transaction management, the account money is transferred according to the id, so that the money of one account decreases while the other increases.
1. Introduce jar packages . The right picture above shows the directory structure of the project. The lib folder contains the jar packages that the project depends on, including the Spring basic packages commons-logging, spring-core, spring-beans, spring-context, spring-expression, spring-test , And spring-jdbc, mysql-connector to connect to the database, transaction management spring-tx, in addition to using c3p0 to manage the database connection pool, in addition to introducing the c3p0 package, you need to introduce the mchange-commons package to use.
2. Configure the data source connection and complete the configuration of the database connection in the jdbc.properties file
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=1234
Then introduce properties in the configuration file spring-transaction.xml and inject the properties into the configuration of c3p0 to complete the configuration of dataSource
<!-- 引入属性文件jdbc.properties -->
<context:property-placeholder location="jdbc.properties"/>
<!-- 配置c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
3. Implement the DAO class of the data object . In the AccountDao class, by inheriting the JdbcDaoSupport class of spring, you can use the update () method in the JdbcTemplate to complete the update operation of the database. Since JdbcDaoSupport requires a DataSource to obtain the connection to the database, the c3p0 data source dataSource is injected through the attribute in the Bean configuration of AccountDao
package com.transaction;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class AccountDao extends JdbcDaoSupport {
public void transferIn(int id,double money){
String sql="UPDATE customers SET money = money + ? WHERE id =?";
this.getJdbcTemplate().update(sql,money,id); //使用JdbDaoSupport类的方法操作数据库
}
public void transferOut(int id,double money){
String sql="UPDATE customers SET money = money - ? WHERE id =?";
this.getJdbcTemplate().update(sql,money,id);
}
}
<!-- DAO类 -->
<bean class="com.transaction.AccountDao" id="accountDao">
<!-- SpringJdbc需要注入连接池DataSource作为参数来初始化 -->
<property name="dataSource" ref="dataSource"/>
</bean>
4. Realize the transaction operation Service class . The transaction operation is completed in the AccountService class. Spring programmatic transaction management is implemented through Spring's TransactionTemplate class. A transaction operation is completed in its execute () method. Execute () needs to pass in a TransactionCallback class as a parameter. Anonymous inner class implementation. Perform transaction operations in the doIntransaction () method in the inner class, and complete specific database operations by calling methods in the DAO layer
package com.transaction;
import org.springframework.transaction.support.TransactionTemplate;
public class AccountService {
private AccountDao accountDao;
private TransactionTemplate transactionTemplate;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
public void transfer(int inAccount, int outAccount, double money) {
//使用spring的TransactionTemplate进行事务操作
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
accountDao.transferIn(inAccount, money);
int i = 1/0; //除数为0会抛出异常
accountDao.transferOut(outAccount, money);
return null;
}
});
}
}
The AccountDao and transactionTemplate properties are used in the AccountService class, so two bean objects are required when configuring the Bean. The accountDao class was created before, so where did the transactionTemplate class come from? The transactionTemplate is created by the DataSourceTransactionManager . As mentioned before, it is an implementation class of the PlatformTransactionManager interface for spring jdbc. So you need to create a transactionManager bean, and you need to inject DataSource as a property to create it.
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务管理模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
<!-- 业务层类 -->
<bean id="accountService" class="com.transaction.AccountService">
<property name="accountDao" ref="accountDao"/>
<property name="transactionTemplate" ref="transactionTemplate"/>
</bean>
The data flow of the project is as follows:
Finally, test the AccountService class and call the transfer () method of the accountService object to transfer 50 from id 2 to 1. Due to the division by 0 operation in the transaction, the transaction execution failed and rolled back, the database operation does not take effect
class AccountServiceTest {
@org.junit.jupiter.api.Test
void transfer() {
ApplicationContext appCtx=new ClassPathXmlApplicationContext("spring-transaction.xml");
AccountService accountService= appCtx.getBean("accountService",AccountService.class);
accountService.transfer(2,1,50);
}
}
3. Declarative transaction management
Spring's declarative transaction management is based on the idea of AOP, so it is necessary to introduce two jar packages, spring-aop and aopalliance. There are three ways to use SpringAOP, so there are three types of declarative transaction management: through Spring ’s TransactionProxy, using the <aop> tag, and using annotations
Based on TransactionProxy
Based on the accountService class, the TransactionProxyBeanFactory generates a proxy class serviceProxy to manage the transaction. Because it is introduced through AOP, the original accountService class does not need to increase the transactionTemplate
public class AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(int inAccount, int outAccount, double money) {
accountDao.transferIn(inAccount, money);
int i = 1/0; //除数为0会抛出异常
accountDao.transferOut(outAccount, money);
}
}
There is no need to configure the TransactionTemplate in the xml file, only need to retain the TransactionManager, but need to configure a new proxy serviceProxy, proxy configuration needs to inject properties-proxy target object, transaction manager and transaction attributes. The transaction attribute of the specific transfer () method is configured through the <prop> key-value pair tag in transactionAttributes, where PROPAGATION_ starts with the propagation behavior, ISOLATION_ starts with the isolation level, and + starts with the definition of what exceptions do not occur after the transaction ,-The beginning defines which exception rollback occurs.
<!-- 业务层代理 -->
<bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 代理目标 -->
<property name="target" ref="accountService"/>
<!-- 注入事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_REPEATABLE_READ,+ArithmeticException</prop>
</props>
</property>
</bean>
When in use, get the proxy bean through serviceProxy, and call transfer () through the enhanced proxy
@Test
void transfer() {
ApplicationContext appCtx=new ClassPathXmlApplicationContext("spring-transaction.xml");
AccountService accountService= appCtx.getBean("serviceProxy",AccountService.class);
accountService.transfer(1,2,50);
}
<Aop> tags based on AspectJ
Because the above method requires a separate proxy class for each transaction object, it is more cumbersome, so it is not commonly used in practice. Using AspectJ does not generate proxy classes, but directly weaves into accountService. Since aspectJ needs to be used for weaving, the aspectjweaver.jar package needs to be introduced. In addition, the <tx> and <aop> tags are used in the xml configuration file, which needs to be declared in <beans>. Then define the transaction notification through the <tx> tag, where the specific transaction method can be configured in <tx: method>, including isolation level isolation, propagation attribute propagation, and non-rollback exception. Configure the aspect in the <aop: config> tag value, including entry point and notification.
<?xml version="1.0" encoding="UTF-8"?>
<!-- 声明tx、aop等标签 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
......
<!-- 配置事务AOP的通知-->
<tx:advice id="serviceAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" no-rollback-for="ArithmeticException"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="transferPointcut" expression="execution(* com.transaction.AccountService.*(..))"/>
<!-- 配置通知 -->
<aop:advisor advice-ref="serviceAdvice" pointcut-ref="transferPointcut"/>
</aop:config>
</beans>
Since no proxy class is generated, the accountService object can be used directly
@Test
void transfer() {
ApplicationContext appCtx=new ClassPathXmlApplicationContext("spring-transaction.xml");
AccountService accountService= appCtx.getBean("accountService",AccountService.class);
accountService.transfer(1,2,50);
}
Annotation-based approach
Using annotations to use spring transaction management code is more concise, only need to configure the transaction manager in the xml configuration file and open the transaction annotation
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
Then add the @Transactional annotation to the class to be used in the transaction. In the annotations, you can specify attributes such as isolation level, propagation attributes, rollback / non-rollback exceptions, etc.
package com.transaction;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,
rollbackFor = java.lang.ArithmeticException.class)
public class AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(int inAccount, int outAccount, double money) {
accountDao.transferIn(inAccount, money);
int i = 1/0; //除数为0会抛出异常
accountDao.transferOut(outAccount, money);
}
}