【Spring6】| Spring's support for transactions

Table of contents

One: Spring's support for transactions

1. Transaction overview

2. Introduce transaction scenarios

3. Spring's support for transactions

3.1 Two ways of implementing transactions in Spring

3.2 Spring transaction management API

3.3 Annotation implementation of declarative transactions

3.4 Transaction attributes

3.5 Transaction propagation behavior propagation

3.6 transaction isolation level isolation

3.7 Transaction timeout

3.8 Read-only transactions

3.9 Which abnormal rollback transactions to set

3.10 Set which exceptions do not roll back transactions

3.11 Full-annotation development of transactions

3.12 XML implementation of declarative transactions


One: Spring's support for transactions

1. Transaction overview

(1) What is a transaction?

①In a business process, it usually requires multiple DML (insert delete update) statements to be combined to complete. These multiple DML statements must succeed or fail at the same time, so as to ensure data security.

②Multiple DMLs either succeed or fail at the same time, this is called a transaction!

③ Transaction: Transaction (tx)

(2) Four processing processes of transactions:

①The first step: start the transaction ( start transaction ).

② Step 2: Execute the core business code.

③ Step 3: Commit the transaction (if there is no abnormality in the core business process) ( commit transaction ).

④ Step 4: Roll back the transaction (if an exception occurs during core business processing) ( rollback transaction ).

(3) Four characteristics of transactions:

A Atomicity: A transaction is the smallest unit of work and cannot be further divided.

C Consistency: Transactions require either success or failure at the same time. The pre-transaction and post-transaction totals are unchanged.

③I Isolation: Because of the isolation between transactions, transactions can be guaranteed not to interfere with each other.

D Persistence: Persistence is a sign of the end of a transaction (persistence to the hard disk).

2. Introduce transaction scenarios

(1) Take the bank account transfer as an example to learn the transaction; two accounts act-001 and act-002, the transfer of 10000 from the act-001 account to the act-002 account must succeed at the same time, or fail at the same time. (One minus succeeds, one plus succeeds, these two update statements must succeed at the same time, or fail at the same time)

(2) The technology of connecting to the database first adopts the JdbcTemplate of the Spring framework, and does not integrate the Mybatis framework.

(3) Use a three-tier architecture to build:

Introduce dependencies in pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bjpowernode</groupId>
    <artifactId>spring6-012-tx-bank</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <!--仓库-->
    <repositories>
        <!--spring里程碑版本的仓库-->
        <repository>
            <id>repository.spring.milestone</id>
            <name>Spring Milestone Repository</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

    <!--依赖-->
    <dependencies>
        <!--spring context-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
        <!--spring jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>6.0.0-M2</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <!--德鲁伊连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.13</version>
        </dependency>
        <!--@Resource注解-->
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>2.1.1</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

</project>

Step 1: Prepare the database table

Table Structure:

Table data:

Step Two: Create the Package Structure 

com.powernode.bank.pojo

com.powernode.bank.service

com.powernode.bank.service.impl

com.powernode.bank.dao

com.powernode.bank.dao.impl

Step 3: Prepare the POJO class

package com.powernode.bank.pojo;

public class Account {
    private String actno;
    private Double balance;

    public Account() {
    }
    public Account(String actno, Double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public Double getBalance() {
        return balance;
    }

    public void setBalance(Double balance) {
        this.balance = balance;
    }
}

Step 4: Write the persistence layer

Write interface AccountDao

package com.powernode.bank.dao;

import com.powernode.bank.pojo.Account;

public interface AccountDao {
    // 根据账号查询余额
    Account selectById(String actno);
    // 更新账户
    int update(Account act);
}

Write the interface implementation class AccountDaoImpl

A JdbcTemplate reference is needed to call the method to execute the sql statement!

package com.powernode.bank.dao.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    @Resource(name = "jdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account selectByActno(String actno) {
        String sql = "select actno, balance from t_act where actno = ?";
        Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);
        return account;
    }

    @Override
    public int update(Account act) {
        String sql = "update t_act set balance = ? where actno = ?";
        int count = jdbcTemplate.update(sql, act.getBalance(), act.getActno());
        return count;
    }
}

Step 5: Write the business layer

Write the interface AccountService

package com.powernode.bank.service;

public interface AccountService {
    // 转账的方法---逻辑业务
    void transfer(String fromActno,String toActno,double money);
}

Write the interface implementation class AccountServiceImpl

package com.powernode.bank.service.impl;

import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import jakarta.annotation.Resource;

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Resource(name = "accountDao")
    private AccountDao accountDao;

    @Override
    public void transfer(String fromActno, String toActno, double money) {
        // 查询账户余额是否充足
        Account fromAccount = accountDao.selectById(fromActno);
        if (fromAccount.getBalance()<money) {
            throw new RuntimeException("账户余额不足");
        }
        // 余额充足,开始转账
        Account toAccount = accountDao.selectById(toActno);
        // 修改内存中的数字
        fromAccount.setBalance(fromAccount.getBalance()-money);
        toAccount.setBalance(toAccount.getBalance()+money);
        // 更新数据库中的数据
        int count = accountDao.update(fromAccount);
        count += accountDao.update(toAccount);
        if (count != 2){
            throw new RuntimeException("转账失败,请联系银行");
        }
    }
}

Step 6: Write the Spring configuration file

①Because the @Repository annotation is used, the component scan component-scan is used;

②Because JdbcTemplate needs a data source, it is necessary to configure a Druid connection pool;

③ Configure JdbcTemplate, and import the data source configured above as an attribute;

<?xml version="1.0" encoding="UTF-8"?>
<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--组件扫描-->
    <context:component-scan base-package="com.powernode.bank"/>
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="123" />
    </bean>
    <!--配置jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

Step 7: Write the presentation layer (test program)

package com.powernode.bank.test;

import com.powernode.bank.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BankTest {
    @Test
    public void testTransfer(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        try {
            accountService.transfer("act-001","act-002",10000);
            System.out.println("转账成功");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

Results of the:

 Successful transfer:

 Simulate exception: Simulate an exception between two data updates

Execution result: account act-001 has less money, but account act-002 has more money

3. Spring's support for transactions

3.1 Two ways of implementing transactions in Spring

(1) Programmatic transactions (basically not used)

① Realize transaction management by writing code.

(2) Declarative transactions (commonly used)

① Based on annotation method

② XML-based configuration method

3.2 Spring transaction management API

(1) Spring's underlying implementation of transaction management is based on AOP, which is further encapsulated by AOP; therefore, Spring has developed a set of APIs specifically for transactions. The core interface of the API is as follows:

(2) PlatformTransactionManager interface: The core interface of the spring transaction manager . In Spring6 it has two implementations:

①DataSourceTransactionManager: Support JdbcTemplate, MyBatis, Hibernate and other transaction management.

②JtaTransactionManager: supports distributed transaction management.

ctl+h to view the inheritance structure as follows:

(3) If you want to use JdbcTemplate in Spring6, you need to use DataSourceTransactionManager to manage transactions . (Spring built-in written, can be used directly)

3.3 Annotation implementation of declarative transactions

Step 1: Configure the transaction manager in the spring configuration file

For transaction control, the Connection object is definitely needed; therefore, the data source is needed, so the attribute needs to be configured with the data source dataSource!

<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

Step 2: Introduce the tx namespace in the spring configuration file

The tx namespace is here to introduce transaction annotation drivers!

<?xml version="1.0" encoding="UTF-8"?>
<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

Step 3: Configure the "transaction annotation driver" in the spring configuration file to start the annotation to control the transaction

<!--开启事务注解驱动器,告诉spring采用注解的方式控制事务-->
<tx:annotation-driven transaction-manager="transactionManager"/>

Complete spring.xml configuration

<?xml version="1.0" encoding="UTF-8"?>
<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--组件扫描-->
    <context:component-scan base-package="com.powernode.bank"/>
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="123"/>
    </bean>
    <!--配置jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--配置事务注解驱动器-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

 Step 4: Add the @Transactional annotation to the service class or method

Note: Add the @Transactional annotation to the class , and all methods in the class have transactions; add this annotation to a method, indicating that only this method uses transactions!

 Simulate the exception again: It is found that the data of the two accounts in the database has not changed, and the transaction is perfectly controlled

3.4 Transaction attributes

Through Transactional annotations, you can view the source code to understand the attributes of the transaction; focus on the following attributes:

① Business communication behavior;

② Transaction isolation level;

③Transaction timeout, -1 means there is no limit to the timeout period, no time limit;

④ read-only transaction;

⑤ Set which abnormal rollback transactions occur;

⑥Set which exceptions do not roll back the transaction;

3.5 Transaction propagation behavior attribute propagation

(1) What is transaction propagation behavior?

There are a() method and b() method in the service class. There is a transaction on the a() method, and there is also a transaction on the b() method. When the b() method is called during the execution of the a() method, how does the transaction pass of? merged into one transaction? Or start a new transaction? This is transaction propagation behavior.

(2) The transaction propagation behavior is defined as an enumeration type in the spring framework:

 (3) There are seven communication behaviors:

REQUIRED (required) : Support the current transaction, use the original one if it exists, create a new one if it does not exist (default); the transaction must exist! [If you have it, you can join it directly, if you don’t, you can create it]

SUPPORTS (support) : support the current transaction, if there is no transaction at present, it will be executed in a non-transactional manner [join if there is one, and leave it if there is no]

MANDATORY (mandatory) : must run in a transaction, if no transaction is currently happening, an exception will be thrown [join if there is one, throw an exception if there is no]

REQUIRES_NEW : Open a new transaction. If a transaction already exists, suspend the existing transaction [whether there is or not, directly start a new transaction. There is no nesting relationship between the new transaction and the previous transaction. The previous transaction is suspended]

NOT_SUPPORTED : Run in non-transactional mode, if there is a transaction, suspend the current transaction [do not support transactions, if they exist, they will be suspended]

NEVER : Run in a non-transactional mode, if there is a transaction, throw an exception [does not support transactions, throws an exception if it exists]

NESTED (nested) : If there is currently a transaction in progress, the method should run in a nested transaction. Nested transactions can be committed or rolled back independently of the outer transaction. If the outer transaction does not exist, behave as REQUIRED. [If there is a transaction, nest a completely independent transaction in this transaction, and the nested transaction can be submitted and rolled back independently. No transaction is the same as REQUIRED ]

(4) How to use it, just use it directly in the Transactional annotation, for example:

@Transactional(propagation = Propagation.REQUIRED)

(5) Simple understanding of REQUIRED and REQUIRES_NEW

For example: there is a transaction on the a() method, and there is also a transaction on the b() method. When the b() method is called during the execution of the a() method:

①Assuming that it is REQUIRED propagation behavior, the final use is the transaction on the a() method; if there is an exception in the b() method (the a method calls the b method), we will capture it on the a() method; then a Both the () method and the b() method will be rolled back because they share the same transaction.

②Assuming that it is the REQUIRES_NEW propagation behavior, a new transaction will be created regardless of whether there is a transaction in the original, and the original transaction will be suspended; when an exception occurs in the b() method (the a method calls the b method), we in a () method to capture, can successfully capture the exception of the b() method, after the capture is successful, can continue to execute the next logical code of the a() method; then only the b() method will roll back, and the a() method will not will roll back.

3.6 transaction isolation level isolation

(1) The transaction isolation level is similar to the wall between classroom A and classroom B. The higher the isolation level, the thicker the wall and the better the sound insulation effect.

(2) Three major problems in reading data in the database: (Three major reading problems)

① Dirty read: Read data that has not been submitted to the database, called dirty read (data in the read cache).

②Non-repeatable read: In the same transaction, the data read for the first time and the second time are different.

③Phantom reading: The data read is false.

(3) The transaction isolation level includes four levels:

Read uncommitted: READ_UNCOMMITTED ; This isolation level has a dirty read problem. The so-called dirty read (dirty read) means that you can read uncommitted data from other transactions.

Read committed: READ_COMMITTED ; solves the problem of dirty reads, and can only be read after other transactions are committed, but there is a problem of non-repeatable reads . Orcal's default isolation level

③Repeatable read: REPEATABLE_READ ; solves non-repeatable read , can achieve repeatable read effect, as long as the current transaction does not end, the read data is always the same, but there is a phantom read problem. MySQL's default isolation level

④Serialization : SERIALIZABLE ; solves the problem of phantom reading , transactions are queued for execution, and concurrency is not supported .

isolation level

dirty read

non-repeatable read

Phantom reading

read uncommitted

have

have

have

read commit

none

have

have

repeatable read

none

none

have

Serialization

none

none

none

(4) How to set the isolation level in Spring code? The isolation level exists as an enumeration type in spring:

@Transactional(isolation = Isolation.READ_COMMITTED)

3.7 Transaction timeout

code show as below:

@Transactional(timeout = 10)

The code indicates that the timeout period for setting transactions is 10 seconds; the default value is -1, which means there is no time limit!

Indicates that if all DML statements in the transaction have not been executed for more than 10 seconds, the final result will be rolled back.

There is a pitfall here, which period of time does the transaction timeout refer to? (time when the last DML statement was executed)

In the current transaction, the time before the last DML statement was executed. If there is a lot of business logic behind the last DML statement, the execution time of these business codes will not be included in the timeout period.

① For the following exception-catching code, the timeout will not be included in the timeout period and will be submitted.

@Transactional(timeout = 10) // 设置事务超时时间为10秒。
public void save(Account act) {
    accountDao.insert(act);
    // 睡眠一会
    try {
        Thread.sleep(1000 * 15);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

②The following exception-catching code times out, and the time will be counted into the time-out period and will be rolled back.

@Transactional(timeout = 10) // 设置事务超时时间为10秒。
public void save(Account act) {
    // 睡眠一会
    try {
        Thread.sleep(1000 * 15);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    accountDao.insert(act);
}

Note: If you want all the codes of the entire method to be included in the timeout period, you can add a line of insignificant DML statements to the last line of the method!

3.8 Read-only transactions

code show as below:

@Transactional(readOnly = true)

①Set the current transaction as a read-only transaction . During the execution of the transaction, only the query select statement is allowed to be executed , and delete, insert, and update cannot be executed!

②Question: Since only the select statement is allowed to execute, and there are no security issues, there is no need for transaction issues at all! So why do you need to set up a read-only transaction? The function of this feature is to start the spring optimization strategy and improve the execution efficiency of the select statement.

③If there is no addition, deletion or modification in the transaction, it is recommended to set it as a read-only transaction!

3.9 Which abnormal rollback transactions to set

code show as below:

@Transactional(rollbackFor = RuntimeException.class)

Indicates that only a RuntimeException exception or a subclass exception of this exception will be rolled back.

3.10  Set which exceptions do not roll back transactions

code show as below:

@Transactional(noRollbackFor = NullPointerException.class)

Indicates that a NullPointerException or a subclass exception of this exception will not be rolled back, and other exceptions will be rolled back.

3.11 Full-annotation development of transactions

Let's take a look at the spring.xml configuration file again, and write these configurations as annotations:

<?xml version="1.0" encoding="UTF-8"?>
<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--组件扫描-->
    <context:component-scan base-package="com.powernode.bank"/>
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="123"/>
    </bean>
    <!--配置jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--开启事务注解驱动器,告诉spring采用注解的方式控制事务-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

 Write a class to replace the configuration file, the code is as follows:

Use Bean annotation : After the Spring framework sees the @Bean annotation, it will call the marked method. The return value of this method is a java object. This java object will be automatically included in the IoC container management. The returned object is in the Spring container. A Bean; the name of this bean is the value of the name attribute in the annotation!

package com.powernode.bank;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration // 代替spring.xml配置文件,在这个类当中完成配置
@ComponentScan("com.powernode.bank") // 组件扫描
@EnableTransactionManagement // 开始事务注解
public class Spring6Config {
    // ----------------现在就剩下三个bean的配置
    // 配置数据源
    @Bean(name = "dataSource")
    public DruidDataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring");
        dataSource.setUsername("root");
        dataSource.setPassword("123");
        return dataSource;
    }

    // 配置jdbcTemplate
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        // 这个dataSource可以直接调用getDataSource方法
        // 也可以使用getJdbcTemplate方法传参的方式DataSource dataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    // 配置事务管理器
    @Bean(name = "transactionManager")
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

The test procedure is as follows:

    @Test
    public void testNoXML(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        try {
            accountService.transfer("act-001","act-002",10000);
            System.out.println("转账成功");
        }catch (Exception e){
            e.printStackTrace();
        }
        
    }

Execution result: normal transfer

3.12 XML implementation of declarative transactions

Configuration steps: first remove the @Transactional of the original AccountServiceImpl class

The first step: configure the transaction manager (previously configured)

Step Two: Configure Notifications

Step 3: Configure the aspect

Add aspectj dependencies:

<!--aspectj依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>6.0.0-M2</version>
</dependency>

Configuration notification: To associate a transaction manager in the notification

    <!--配置通知,这里要引用上面的事务管理器-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--配置通知的相关属性-->
        <tx:attributes>
            <!--name是方法名,之前所有的事务属性都可以在这里进行配置-->
            <tx:method name="transfer" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
        </tx:attributes>
    </tx:advice>

Note: For methods that are rarely hard-coded, fuzzy matching is generally used

    <!--配置通知-->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
            <tx:method name="del*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
            <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
            <tx:method name="transfer*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
        </tx:attributes>
    </tx:advice>

Configuration aspect: notification + cut point

    <!--配置切面-->
    <aop:config>
        <!--切点-->
        <aop:pointcut id="txPointCut" expression="execution(* com.bjpowernode.bank.service..*(..))"/>
        <!--切面=通知+切点-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>

All Spring configuration files are as follows: remember to add the aop namespace

<?xml version="1.0" encoding="UTF-8"?>
<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 http://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 http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--组件扫描-->
    <context:component-scan base-package="com.bjpowernode.bank"/>
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="123"/>
    </bean>
    <!--配置jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置通知,这里要引用上面的事务管理器-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--配置通知的相关属性-->
        <tx:attributes>
            <!--name是方法名,之前所有的事务属性都可以在这里进行配置-->
            <tx:method name="transfer" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
        </tx:attributes>
    </tx:advice>
    <!--配置切面-->
    <aop:config>
        <!--切点:虽然这里配的是所有service包下的所有类,但上面配置的是只有transfer方法才走事务-->
        <aop:pointcut id="txPointCut" expression="execution(* com.bjpowernode.bank.service..*(..))"/>
        <!--切面=通知+切点-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>

</beans>

write tests

package com.bjpowernode.bank.test;

import com.bjpowernode.bank.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BankTxTest {
    @Test
    public void testNoAnnotation(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        try {
            accountService.transfer("act-001","act-002",1000);
            System.out.println("转账成功");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

 Execution result: successful transfer

Guess you like

Origin blog.csdn.net/m0_61933976/article/details/128757072