Chapter 44 of Spring’s Road to God: Detailed Explanation of Spring’s Declarative Transactions (@Transactional)

Chapter 44 of Spring’s Road to God: Detailed Explanation of Spring’s Declarative Transactions (@Transactional)

There are two usages of spring transactions: programmatic transactions and declarative transactions .

Programmatic transactions have been introduced in the previous article. If you are not familiar with it, it is recommended to look at the usage of programmatic transactions first.

This article mainly introduces the usage of declarative transactions. We basically use declarative transactions in our work, so this article is more important. I suggest you cheer up and start officially.

What are declarative transactions?

The so-called declarative transaction is to tell spring through configuration, such as configuration file (xml) or annotation, which methods need spring to help manage the transaction, and then developers only need to pay attention to the business code, and spring will automatically help with the transaction. We control.

For example, in the way of annotation@Transaction , you only need to add an annotation on the method, then spring will automatically start a transaction before the method is executed, and after the method is executed, it will automatically submit or roll back the transaction, and there is no transaction-related code inside the method, which is very special to use Methods.

@Transaction
public void insert(String userName){
    
    
    this.jdbcTemplate.update("insert into t_user (name) values (?)", userName);
}

Two implementations of declarative transactions

  1. The way of configuration files , that is, to perform unified configuration in the spring xml file, developers basically don’t need to pay attention to transaction matters, and there is no need to care about any transaction-related code in the code, and everything is handled by spring.
  2. For the way of annotation , you only need to add @Transaction annotation on the method that needs spring to help manage affairs. The way of annotation is relatively more concise, and developers need to configure it themselves. Some students may not understand spring. Too familiar, so there is a certain risk in configuring this, just do a good code review.

The way of configuring files is not mentioned here, and it is relatively seldom used. We mainly master how to use annotations, and that’s it.

5 steps for declarative transaction annotation

1. Enable Spring's annotation-driven transaction management function

@EnableTransactionManagementAdd annotations to the spring configuration class

@EnableTransactionManagement
public class MainConfig4 {
    
    
}

Briefly introduce the principle: when the spring container starts, it finds the @EnableTransactionManagement annotation. At this time, it will intercept the creation of all beans, and scan to see if there is a @Transaction annotation on the bean (class, or parent class, or interface, or method) This annotation is available), if there is this annotation, spring will generate a proxy object for the bean through aop, and an interceptor will be added to the proxy object, which will intercept the execution of the public method in the bean, and will start the transaction before the method is executed , Commit or rollback the transaction after the method is executed. There will be a dedicated article to show you the source code of this piece later.

If you are interested, you can read the source code first, mainly because the following method will

org.springframework.transaction.interceptor.TransactionInterceptor#invoke

Let's take a look at the source code of EnableTransactionManagement

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
    
    

 /**
  * spring是通过aop的方式对bean创建代理对象来实现事务管理的
  * 创建代理对象有2种方式,jdk动态代理和cglib代理
  * proxyTargetClass:为true的时候,就是强制使用cglib来创建代理
  */
 boolean proxyTargetClass() default false;

 /**
  * 用来指定事务拦截器的顺序
  * 我们知道一个方法上可以添加很多拦截器,拦截器是可以指定顺序的
  * 比如你可以自定义一些拦截器,放在事务拦截器之前或者之后执行,就可以通过order来控制
  */
 int order() default Ordered.LOWEST_PRECEDENCE;
}

2. Define the transaction manager

If the transaction is managed by spring, then you must create one or more transaction managers. These managers are responsible for managing specific transactions, such as starting a transaction, committing a transaction, and rolling back a transaction.

Spring uses the PlatformTransactionManager interface to represent the transaction manager.

Multiple implementation classes of PlatformTransactionManager to deal with different environments

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-NIJYdB5Q-1684550694441)(%E6%96%B0%E5%BB%BA%E6%96%87% E6%9C%AC%E6%96%87%E6%A1%A3/1369022-20211106113416897-1649199944.png)]

JpaTransactionManager : If you use jpa to operate db, you need to use this manager to help you control transactions.

DataSourceTransactionManager : If you use the method of specifying the data source, such as operating the database using: JdbcTemplate, mybatis, ibatis, then you need to use this manager to help you control the transaction.

HibernateTransactionManager : If you use hibernate to operate db, then you need to use this manager to help you control transactions.

JtaTransactionManager : If you use jta in java to operate db, this is usually a distributed transaction, and you need to use this manager to control the transaction.

For example: we use mybatis or jdbctemplate, then define a transaction manager in the following way.

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    
    
    return new DataSourceTransactionManager(dataSource);
}

3. Add the @Transaction annotation to the target that needs to use the transaction

  • @Transaction is placed on the interface , then all publics in the implementation class of the interface will be automatically added with transactions by spring
  • Put @Transaction on the class , then all publicc methods in the current class and its infinite subclasses will be automatically added with transactions by spring
  • @Transaction is placed on the public method, then the method will be automatically added to the transaction by spring
  • Note: @Transaction is only valid for public methods

Let's take a look at the @Transactional source code:

@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    
    

    /**
     * 指定事务管理器的bean名称,如果容器中有多事务管理器PlatformTransactionManager,
     * 那么你得告诉spring,当前配置需要使用哪个事务管理器
     */
    @AliasFor("transactionManager")
    String value() default "";

    /**
     * 同value,value和transactionManager选配一个就行,也可以为空,如果为空,默认会从容器中按照类型查找一个事务管理器bean
     */
    @AliasFor("value")
    String transactionManager() default "";

    /**
     * 事务的传播属性
     */
    Propagation propagation() default Propagation.REQUIRED;

    /**
     * 事务的隔离级别,就是制定数据库的隔离级别,数据库隔离级别大家知道么?不知道的可以去补一下
     */
    Isolation isolation() default Isolation.DEFAULT;

    /**
     * 事务执行的超时时间(秒),执行一个方法,比如有问题,那我不可能等你一天吧,可能最多我只能等你10秒
     * 10秒后,还没有执行完毕,就弹出一个超时异常吧
     */
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

    /**
     * 是否是只读事务,比如某个方法中只有查询操作,我们可以指定事务是只读的
     * 设置了这个参数,可能数据库会做一些性能优化,提升查询速度
     */
    boolean readOnly() default false;

    /**
     * 定义零(0)个或更多异常类,这些异常类必须是Throwable的子类,当方法抛出这些异常及其子类异常的时候,spring会让事务回滚
     * 如果不配做,那么默认会在 RuntimeException 或者 Error 情况下,事务才会回滚 
     */
    Class<? extends Throwable>[] rollbackFor() default {
    
    };

    /**
     * 和 rollbackFor 作用一样,只是这个地方使用的是类名
     */
    String[] rollbackForClassName() default {
    
    };

    /**
     * 定义零(0)个或更多异常类,这些异常类必须是Throwable的子类,当方法抛出这些异常的时候,事务不会回滚
     */
    Class<? extends Throwable>[] noRollbackFor() default {
    
    };

    /**
     * 和 noRollbackFor 作用一样,只是这个地方使用的是类名
     */
    String[] noRollbackForClassName() default {
    
    };

}

Parameter introduction

parameter describe
value Specify the bean name of the transaction manager. If there are multiple transaction managers PlatformTransactionManager in the container, then you have to tell spring which transaction manager the current configuration needs to use
transactionManager Just choose one of the same value, value and transactionManager, or it can be empty, if it is empty, it will search for a transaction manager bean from the container according to the type by default
propagation The propagation attribute of the transaction will be introduced in detail in the next article
isolation The isolation level of the transaction is to formulate the isolation level of the database. Do you know the isolation level of the database? If you don't know, you can make it up
timeout The timeout period (seconds) of transaction execution. Execute a method. For example, if there is a problem, I can’t wait for you for a day. Maybe I can only wait for you for 10 seconds and 10 seconds at most. If the execution is not completed, a timeout exception will pop up.
readOnly Whether it is a read-only transaction, for example, there is only query operation in a certain method, we can specify that the transaction is read-only If this parameter is set, the database may do some performance optimization to improve the query speed
rollbackFor Define zero (0) or more exception classes. These exception classes must be subclasses of Throwable. When methods throw these exceptions and their subclass exceptions, spring will roll back the transaction. In the case of RuntimeException or Error, the transaction will be rolled back
rollbackForClassName Same as rollbackFor, but this place uses the class name
noRollbackFor Define zero (0) or more exception classes. These exception classes must be subclasses of Throwable. When the method throws these exceptions, the transaction will not be rolled back
noRollbackForClassName Same as noRollbackFor, except that the class name is used here

4. Perform db business operations

Perform business operations on @Transaction annotation classes or target methods, and these methods will be automatically managed by spring for transactions.

For example, the following insertBatch operation deletes data first, and then inserts data in batches. The @Transactional annotation is added to the method. At this time, this method will be automatically controlled by the spring transaction, and either all succeed or all fail.

@Component
public class UserService {
    
    
    @Autowired
    private JdbcTemplate jdbcTemplate;

    //先清空表中数据,然后批量插入数据,要么都成功要么都失败
    @Transactional
    public void insertBatch(String... names) {
    
    
        jdbcTemplate.update("truncate table t_user");
        for (String name : names) {
    
    
            jdbcTemplate.update("INSERT INTO t_user(name) VALUES (?)", name);
        }
    }
}

5. Start the spring container and use beans to perform business operations

@Test
public void test1() {
    
    
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig4.class);
    context.refresh();

    UserService userService = context.getBean(UserService.class);
    userService.insertBatch("java高并发系列", "mysql系列", "maven系列", "mybatis系列");
}

Case 1

prepare database

DROP DATABASE IF EXISTS javacode2018;
CREATE DATABASE if NOT EXISTS javacode2018;

USE javacode2018;
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user(
  id int PRIMARY KEY AUTO_INCREMENT,
  name varchar(256) NOT NULL DEFAULT '' COMMENT '姓名'
);

spring configuration class

package com.javacode2018.tx.demo4;

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.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;

@EnableTransactionManagement //@1
@Configuration
@ComponentScan
public class MainConfig4 {
    
    
    //定义一个数据源
    @Bean
    public DataSource dataSource() {
    
    
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root123");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    //定义一个JdbcTemplate,用来执行db操作
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
    
    
        return new JdbcTemplate(dataSource);
    }

    //定义我一个事物管理器
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
    
     //@2
        return new DataSourceTransactionManager(dataSource);
    }
}

@1 : Use the @EnableTransactionManagement annotation to enable spring transaction management

@2 : Define transaction manager

come to business class

package com.javacode2018.tx.demo4;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;

@Component
public class UserService {
    
    
    @Autowired
    private JdbcTemplate jdbcTemplate;

    //先清空表中数据,然后批量插入数据,要么都成功要么都失败
    @Transactional //@1
    public int insertBatch(String... names) {
    
    
        int result = 0;
        jdbcTemplate.update("truncate table t_user");
        for (String name : names) {
    
    
            result += jdbcTemplate.update("INSERT INTO t_user(name) VALUES (?)", name);
        }
        return result;
    }

    //获取所有用户信息
    public List<Map<String, Object>> userList() {
    
    
        return jdbcTemplate.queryForList("SELECT * FROM t_user");
    }
}

@1 : The @Transactional annotation is added to the insertBatch method, allowing spring to automatically add transactions to this method

test class

package com.javacode2018.tx.demo4;

import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo4Test {
    
    
    @Test
    public void test1() {
    
    
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig4.class);
        context.refresh();

        UserService userService = context.getBean(UserService.class);
        //先执行插入操作
        int count = userService.insertBatch(
                "java高并发系列",
                "mysql系列",
                "maven系列",
                "mybatis系列");
        System.out.println("插入成功(条):" + count);
        //然后查询一下
        System.out.println(userService.userList());
    }
}

run output

插入成功(条):4
[{
    
    id=1, name=java高并发系列}, {
    
    id=2, name=mysql系列}, {
    
    id=3, name=maven系列}, {
    
    id=4, name=mybatis系列}]

Some friends may ask, how to know whether the called method uses transactions? Let's take a look below.

How to determine whether the method uses spring transactions

Method 1: Breakpoint debugging

The spring transaction is processed by the TransactionInterceptor interceptor, and finally the following method will be called, and the detailed process can be seen by setting a breakpoint.

org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-Z9sde61c-1684550694442) (%E6%96%B0%E5%BB%BA%E6%96%87% E6%9C%AC%E6%96%87%E6%A1%A3/1369022-20211106113623217-1389119179.png)]

Method 2: Look at the log

The process of spring processing transactions has detailed log output. When the log is turned on, the console can see the detailed process of the transaction.

Add maven configuration

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

src\main\resources new logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%d{MM-dd HH:mm:ss.SSS}][%thread{20}:${PID:- }][%X{trace_id}][%level][%logger{56}:%line:%method\(\)]:%msg%n##########**********##########%n</pattern>
        </encoder>
    </appender>

    <logger name="org.springframework" level="debug">
        <appender-ref ref="STDOUT" />
    </logger>

</configuration>

Let's run Case 1 again

[09-10 11:20:38.830][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:370:getTransaction()]:Creating new transaction with name [com.javacode2018.tx.demo4.UserService.insertBatch]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
##########**********##########
[09-10 11:20:39.120][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:265:doBegin()]:Acquired Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@65fe9e33]]] for JDBC transaction
##########**********##########
[09-10 11:20:39.125][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:283:doBegin()]:Switching JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@65fe9e33]]] to manual commit
##########**********##########
[09-10 11:20:39.139][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:502:update()]:Executing SQL update [truncate table t_user]
##########**********##########
[09-10 11:20:39.169][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:860:update()]:Executing prepared SQL update
##########**********##########
[09-10 11:20:39.169][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:609:execute()]:Executing prepared SQL statement [INSERT INTO t_user(name) VALUES (?)]
##########**********##########
[09-10 11:20:39.234][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:860:update()]:Executing prepared SQL update
##########**********##########
[09-10 11:20:39.235][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:609:execute()]:Executing prepared SQL statement [INSERT INTO t_user(name) VALUES (?)]
##########**********##########
[09-10 11:20:39.236][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:860:update()]:Executing prepared SQL update
##########**********##########
[09-10 11:20:39.237][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:609:execute()]:Executing prepared SQL statement [INSERT INTO t_user(name) VALUES (?)]
##########**********##########
[09-10 11:20:39.238][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:860:update()]:Executing prepared SQL update
##########**********##########
[09-10 11:20:39.239][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:609:execute()]:Executing prepared SQL statement [INSERT INTO t_user(name) VALUES (?)]
##########**********##########
[09-10 11:20:39.241][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:741:processCommit()]:Initiating transaction commit
##########**********##########
[09-10 11:20:39.241][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:328:doCommit()]:Committing JDBC transaction on Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@65fe9e33]]]
##########**********##########
[09-10 11:20:39.244][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:387:doCleanupAfterCompletion()]:Releasing JDBC Connection [ProxyConnection[PooledConnection[com.mysql.jdbc.JDBC4Connection@65fe9e33]]] after transaction
##########**********##########
插入成功(条):4
[09-10 11:20:39.246][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:427:query()]:Executing SQL query [SELECT * FROM t_user]
##########**********##########
[09-10 11:20:39.247][main: ][][DEBUG][org.springframework.jdbc.datasource.DataSourceUtils:115:doGetConnection()]:Fetching JDBC Connection from DataSource
##########**********##########
[{id=1, name=java高并发系列}, {id=2, name=mysql系列}, {id=3, name=maven系列}, {id=4, name=mybatis系列}]

Let's understand the log

The insertBatch method has the @Transaction annotation, so it will be intercepted by the interceptor. The following is a transaction created before the insertBatch method is called.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-rdB0lIZX-1684550694443) (%E6%96%B0%E5%BB%BA%E6%96%87% E6%9C%AC%E6%96%87%E6%A1%A3/1369022-20211106113718442-1842075940.png)]

The parameters of the @Transaction annotation on the insertBatch method are all default values. The @Transaction annotation can be used to value或者transactionManagerspecify the transaction manager, but it is not specified. At this time, spring will find a default one in the container according to the type of transaction manager. It happens that we are in the spring container One is defined in , so it is used directly. We use the transaction manager to new DataSourceTransactionManager(dataSource)obtain a database connection from the datasource of the transaction manager, and then set the transaction to be submitted manually through the connection, and then throw (datasource->this connection) into ThreadLocal. Specifically, you can see it an article.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-rZ3WZCnB-1684550694443)(%E6%96%B0%E5%BB%BA%E6%96%87% E6%9C%AC%E6%96%87%E6%A1%A3/1369022-20211106113729840-868259205.png)]

The following is the entry into the insertBatch method. Some db operations are performed through the jdbctemplate. The jdbctemplate will get the connection of the spring transaction through the datasource to the above threadlocal, and then perform the db operation.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-dd7nXKAG-1684550694444)(%E6%96%B0%E5%BB%BA%E6%96%87% E6%9C%AC%E6%96%87%E6%A1%A3/1369022-20211106113738775-208538673.png)]

Finally, after the insertBatch method is executed, there is no exception, then spring starts to submit the transaction through the database connection.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-eUgdvJFj-1684550694444)(%E6%96%B0%E5%BB%BA%E6%96%87% E6%9C%AC%E6%96%87%E6%A1%A3/1369022-20211106113749897-572088668.png)]

Summarize

This article explains the steps to use programmatic transactions in spring.

It mainly involves 2 annotations:

@EnableTransactionManagement: Enable spring transaction management function

@Transaction: Adding it to classes, methods, and interfaces that require spring to manage transactions will only be valid for public methods.

Let’s digest it again, if you have any questions, please leave a message to exchange.

The next article will introduce the propagation properties of transactions in detail, so stay tuned.

Case source code

git地址:
https://gitee.com/javacode2018/spring-series

本文案例对应源码:spring-series\lesson-002-tx\src\main\java\com\javacode2018\tx\demo4

Passerby A All java case codes will be put on this in the future, everyone watch it, you can continue to pay attention to the dynamics.

After that, there is no exception, then spring starts to commit the transaction through the database connection.

[External link image transfer...(img-eUgdvJFj-1684550694444)]

Summarize

This article explains the steps to use programmatic transactions in spring.

It mainly involves 2 annotations:

@EnableTransactionManagement: Enable spring transaction management function

@Transaction: Adding it to classes, methods, and interfaces that require spring to manage transactions will only be valid for public methods.

Let’s digest it again, if you have any questions, please leave a message to exchange.

The next article will introduce the propagation properties of transactions in detail, so stay tuned.

Case source code

git地址:
https://gitee.com/javacode2018/spring-series

本文案例对应源码:spring-series\lesson-002-tx\src\main\java\com\javacode2018\tx\demo4

Passerby A All java case codes will be put on this in the future, everyone watch it, you can continue to pay attention to the dynamics.

Source: https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648936892&idx=2&sn=473a156dc141a2efc0580f93567f0630&scene=21#wechat_redirect

Guess you like

Origin blog.csdn.net/china_coding/article/details/130778919