Spring @Transactional 注解

官方文档:docs.spring.io/spring-fram...).

推荐阅读:www.marcobehler.com/guides/spri...

Spring 事务的原理

@Transactional 注解的底层实现使用的是 JDBC 事务实现的,所以如果需要使用 @Transactional 的话那么就需要使用支持的数据库,比如 MySQL 的 innodb 就至此事务,下面是简化代码:

java 复制代码
import java.sql.Connection;

Connection connection = dataSource.getConnection(); // (1)

try (connection) {
    connection.setAutoCommit(false); // (2)
    // execute some SQL statements...
    connection.commit(); // (3)

} catch (SQLException e) {
    connection.rollback(); // (4)
}

(1)你需要连接到数据库才能启动事务。DriverManager.getConnection(url, user, password) 也可以,但在大多数企业应用程序中,你会配置数据源并从中获取连接。这是在 Java 中"启动"数据库事务的唯一方式,尽管名称听起来有点奇怪。

(2)setAutoCommit(true) 确保每个单独的 SQL 语句都自动包装在自己的事务中,而 setAutoCommit(false) 则相反:你是事务的管理者,需要开始调用 commit 和其他相关方法。请注意,autoCommit 标志在连接打开的整个时间段内有效,这意味着你只需调用该方法一次,而不是重复调用。

(3)让我们提交我们的事务...

(4)或者,如果出现异常,回滚我们的更改。

这段代码虽然简单,但是确实 Spring 的 @Transactional 帮我们实现事务的核心代码

同时对应的 @Transactional 对应的注解是相关的参数都有 JDBC 的阐述

比如

java 复制代码
@Transactional(propagation=TransactionDefinition.NESTED,
               isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)

对应的就是 JDBC 的 savePoint 和 isolate

java 复制代码
import java.sql.Connection;

// isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED

connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); // (1)

// propagation=TransactionDefinition.NESTED

Savepoint savePoint = connection.setSavepoint(); // (2)
...
connection.rollback(savePoint);
  1. Spring @Transactional 对应的 isolation 也是设置的 JDBC 实现的
  2. 嵌入式事务的实现是通过设置 savePoint

Spring 的事务管理如何工作

我们需要明白一点就是 Spring 的 @Transactional 不是洪水猛兽,我们不需要太过于害怕。反正就是 JDBC 事务的 start 、commit 、rollback 就是这些操作。

但是问题出在,使用普通 JDBC,只有一种方法 (setAutocommit(false)) 来管理事务,而 Spring 为您提供了许多不同的、更方便的方法来实现相同的目的。

如何实现的?我们后面会慢慢来说!

如何使用 Spring 编程式事务 ?

java 复制代码
@Service
public class UserService {

    @Autowired
    private TransactionTemplate template;

    public Long registerUser(User user) {
        Long id = template.execute(status ->  {
            // execute some SQL that e.g.
            // inserts the user into the db and returns the autogenerated id
            return id;
        });
    }
}

和普通 JDBC 事务进行对比:

  1. 不需要手动创建、关闭资源
  2. 不需要捕获 SQLExceptions ,Spring 会把 SQLExceptions 转换成 RuntimExceptions
  3. 更符合 Spring 生态,使用 @Autowired

使用编程式事务有一个好处就是,我们可以控制事务的最小粒度增加执行的效率!

使用 xml 配置 Spring 事务

之前这种配置很常见,但是现在注解式编程更加常见'

xml 复制代码
<!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- the transactional semantics... -->
        <tx:attributes>
            <!-- all methods starting with 'get' are read-only -->
            <tx:method name="get*" read-only="true"/>
            <!-- other methods use the default transaction settings (see below) -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

还需要指定对应的 AOP 切面

xml 复制代码
<aop:config>
    <aop:pointcut id="userServiceOperation" expression="execution(* x.y.service.UserService.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="userServiceOperation"/>
</aop:config>

<bean id="userService" class="x.y.service.UserService"/>

service 代码

java 复制代码
public class UserService {

    public Long registerUser(User user) {
        // execute some SQL that e.g.
        // inserts the user into the db and retrieves the autogenerated id
        return id;
    }
}

使用注解配置 Spring 事务

java 复制代码
@Service
public class UserService {

    @Transactional
    public Long registerUser(User user) {
       // execute some SQL that e.g.
        // inserts the user into the db and retrieves the autogenerated id
        // userDao.save(user);
        return id;
    }
}

没有一点 xml 的配置,但是我们需要注意两点:

  1. 需要确保 Spring 配置类需要 @EnableTransactionManagement 注解(SpringBoot 会自动帮我们配置)

    java 复制代码
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
    	.....
    }
  2. 确保您在 Spring 配置中指定了事务管理器(无论如何都需要这样做)

  3. 然后 Spring 非常方便,用 @Transactional 注释标识的任何 bean 的公共方法都将在数据库事务内执行

所以如果需要 @Transactional 生效的代码:

java 复制代码
@Configuration
@EnableTransactionManagement
public class MySpringConfig {

    @Bean
    public PlatformTransactionManager txManager() {
        return yourTxManager; // more on that later
    }

}

我们根据上文提到的 JDBC 方面的知识,我们可以猜到底层的实现:

java 复制代码
public class UserService {

    public Long registerUser(User user) {
        Connection connection = dataSource.getConnection(); // (1)
        try (connection) {
            connection.setAutoCommit(false); // (1)

            // execute some SQL that e.g.
            // inserts the user into the db and retrieves the autogenerated id
            // userDao.save(user); <(2)

            connection.commit(); // (1)
        } catch (SQLException e) {
            connection.rollback(); // (1)
        }
    }
}

CGlib & JDK Proxies

还是动态代理实现的 @Transactional 的底层,Spring 帮我们在底层实现了

当一个 bean 上使用 @Transactional 时,Spring 都会使用到动态代理。它不仅实例化 UserService,而且还实例化该 UserService 的事务代理。它通过使用 Cglib 库中的 proxy-through-subclassing 方法来实现

从上面的图片可以了解到 动态代理 帮我们做的工作

  • 打开和关闭数据库连接/事务。
  • 然后调用真正的 UserService
  • 其他的 bean 比如 UserRestController "永远" 不会知道 TA 调用的 UseService 不是真的而是动态代理的对象

为什么需要事务管理器(如 PlatformTransactionManager)?

UserService 会动态代理,而代理会为您管理事务。但实际上并非代理本身处理所有的事务状态(打开、提交、关闭),而是代理将这些工作委托给了事务管理器

Spring 提供 PlatformTransactionManager / TransactionManager 两个接口并且提供了实现

它完全做的就是你迄今为止管理事务所做的一切,但首先,让我们看一下所需的 Spring 配置:

java 复制代码
@Bean
public DataSource dataSource() {
    return new MysqlDataSource(); // (1)
}

@Bean
public PlatformTransactionManager txManager() {
    return new DataSourceTransactionManager(dataSource()); // (2)
}
  1. 创建一个特定于数据库或连接池的数据源。本示例中使用MySQL
  2. 创建你的事务管理器,它需要一个数据源来管理事务

下面就是一个简化版本的 transaction class doBegin 方法作用是开启事务, doCommit 方法就是提交事务

java 复制代码
public class DataSourceTransactionManager implements PlatformTransactionManager {

    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        Connection newCon = obtainDataSource().getConnection();
        // ...
        con.setAutoCommit(false);
        // 是的就这些,没其他的了!
    }

    @Override
    protected void doCommit(DefaultTransactionStatus status) {
        // ...
        Connection connection = status.getTransaction().getConnectionHolder().getConnection();
        try {
            con.commit();
        } catch (SQLException ex) {
            throw new TransactionSystemException("Could not commit JDBC transaction", ex);
        }
    }
}
  1. 如果 Spring 检测到 bean 上的 @Transactional 注释,它会创建该 bean 的动态代理。
  2. 代理可以访问事务管理器,并要求它打开和关闭事务/连接。
  3. 事务管理器本身将做在普通 Java 部分中所做的操作,creat、rollback、commit ...

物理事务和逻辑事务有什么区别?

java 复制代码
@Service
public class UserService {

    @Autowired
    private InvoiceService invoiceService;

    @Transactional
    public void invoice() {
        invoiceService.createPdf();
        // send invoice as email, etc.
    }
}

@Service
public class InvoiceService {

    @Transactional
    public void createPdf() {
        // ...
    }
}

UserService 有一个事务性 Invoice() 方法。它调用 InvoiceService 上的另一个事务方法 createPdf()。

现在数据库只有一个 数据库事务. (就是 getConnection(). setAutocommit(false). commit())Spring 称这个叫做物理事务

但是在对于 Spring 来说有发生了两个逻辑事务:第一个在 UserService 中,另一个在 InvoiceService 中。 Spring 非常智能,知道这两个 @Transactional 方法应该使用相同的物理事务之中

但是当我做出如下的修改:

java 复制代码
@Service
public class InvoiceService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createPdf() {
        // ...
    }
}

那么物理事务就是两个了( getConnection() x2. setAutocommit(false) x2. commit() x2),同时逻辑事务也是两个

@Transactional 传播级别

首先介绍一下 @Transactional 的传播级别

java 复制代码
  @Transactional(propagation = Propagation.REQUIRED)

  // or

  @Transactional(propagation = Propagation.REQUIRES_NEW)
  // etc

一共有一下几个传播级别:

  • REQUIRED
  • SUPPORTS
  • MANDATORY
  • REQUIRES_NEW
  • NOT_SUPPORTED
  • NEVER
  • NESTED

我们知道 JDBC 的事务能够做到的事情,但是 Spring 到底是如何通过 JDBC 的事务实现的这么多的级别?

  • REQUIRED(默认)方法需要事务,但是如果存在一个事务就使用存在的事务 → getConnection(). setAutoCommit(false). commit
  • SUPPORTS 无论有没有事务都会执行 → JDBC 什么也不会做
  • MANDATORY 如果没有事务就会抛出异常,有事务正常执行 → JDBC 什么也不会做
  • REQUIRES_NEW 我想要一个完全的属于我自己的事务 → getConnection(). setAutocommit(false). commit().
  • NOT_SUPPORTED 以非事务方式执行,如果存在当前事务,则暂停当前事务 → JDBC 什么也不做
  • NEVER 以非事务执行,如果有事务就报错 → JDBC 什么也不做
  • NESTED 如果有事务那么就用嵌入式的事务执行,好像有些复杂,但是其实这个是关于 savePoint → connection.setSavepoint()

@Transactional 隔离级别

当我们进行下面配置

java 复制代码
@Transactional(isolation = Isolation.REPEATABLE_READ)

底层其实是这样的:

java 复制代码
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);

还要注意,在事务过程中切换隔离级别时,必须确保 JDBC 驱动程序/数据库,了解支持的情况和不支持的情况

最常见的 @Transactional 坑

java 复制代码
@Service
public class UserService {

    @Transactional
    public void invoice() {
        createPdf();
        // send invoice as email, etc.
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createPdf() {
        // ...
    }
}

这个 UserService 里面有两个 @Transactional 标识的方法,而且其中的 creatPdf 方法的传播级别是 REQUIRES_NEW

那么现在有一个问题,这个类之中有几个物理事务

答案是 1 个事务而不是 2 个物理事务!!!

为什么?

Spring 为创建事务性 UserService 代理,但是一旦您进入 UserService 类并调用其他内部方法,就不再涉及代理了!这意味着不会再有新的事务

但是这个问题也是有对应的解法

1、可以自己注入自己然后调用 @Lazy @Resource private UserService userService; 2、可以使用 ((UserService) AopContext.currentProxy()).creatPdf() 3、使用 SpringUtil 获取 bean 调用 creatPdf() 方法

相关推荐
Ai 编码助手5 小时前
在 Go 语言中如何高效地处理集合
开发语言·后端·golang
小丁爱养花5 小时前
Spring MVC:HTTP 请求的参数传递2.0
java·后端·spring
Channing Lewis5 小时前
什么是 Flask 的蓝图(Blueprint)
后端·python·flask
轩辕烨瑾7 小时前
C#语言的区块链
开发语言·后端·golang
feilieren7 小时前
SpringBoot 搭建 SSE
java·spring boot·spring
栗豆包8 小时前
w175基于springboot的图书管理系统的设计与实现
java·spring boot·后端·spring·tomcat
萧若岚9 小时前
Elixir语言的Web开发
开发语言·后端·golang
Channing Lewis9 小时前
flask实现重启后需要重新输入用户名而避免浏览器使用之前已经记录的用户名
后端·python·flask
Channing Lewis9 小时前
如何在 Flask 中实现用户认证?
后端·python·flask
一只爱吃“兔子”的“胡萝卜”10 小时前
2.Spring-AOP
java·后端·spring