Two ways of transaction management in Spring

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);
    }
}

 

Published 124 original articles · Like 65 · Visit 130,000+

Guess you like

Origin blog.csdn.net/theVicTory/article/details/105455500