Spring @Transactional 注解

news2024/12/27 14:01:36

官方文档:https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html#:~:text=The%20%40Transactional%20annotation%20is%20metadata,suspending%20any%20existing%20transaction%22).

推荐阅读:https://www.marcobehler.com/guides/spring-transaction-management-transactional-in-depth

Spring 事务的原理

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

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 的阐述

比如

@Transactional(propagation=TransactionDefinition.NESTED,
               isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)

对应的就是 JDBC 的 savePoint 和 isolate

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 编程式事务 ?

@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 事务

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

<!-- 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 切面

<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 代码

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 事务

@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 会自动帮我们配置)

    @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 生效的代码:

@Configuration
@EnableTransactionManagement
public class MySpringConfig {

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

}

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

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 方法来实现

image-20240318093220184

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

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

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

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

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

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

@Bean
public DataSource dataSource() {
    return new MysqlDataSource(); // (1)
}

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

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

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

image-20240318094417687

  1. 如果 Spring 检测到 bean 上的 @Transactional 注释,它会创建该 bean 的动态代理。
  2. 代理可以访问事务管理器,并要求它打开和关闭事务/连接。
  3. 事务管理器本身将做在普通 Java 部分中所做的操作,creat、rollback、commit …

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

@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 方法应该使用相同的物理事务之中

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

@Service
public class InvoiceService {

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

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

@Transactional 传播级别

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

  @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 隔离级别

当我们进行下面配置

@Transactional(isolation = Isolation.REPEATABLE_READ)

底层其实是这样的:

connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);

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

最常见的 @Transactional 坑

@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 类并调用其他内部方法,就不再涉及代理了!这意味着不会再有新的事务

image-20240318115454760

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1602152.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于STM32的智能垃圾分类识别系统设计(论文)_kaic

摘 要 智能垃圾分类技术逐渐受到了政府的重视和支持&#xff0c;越来越多的城市开始推行垃圾分类政策。因此设计一款能够对垃圾进行识别并分类的控制系统具有一定的现实意义。本设计采用STM32单片机作为整个系统的控制核心&#xff0c;利用K210开发板作为图像识别控制系统&…

RT-thread信号量与互斥量

1,信号量 信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。理解资源计数适合于线程间工作处理速度不匹配的场合;信号量在大于0时才能获取,在中断、线程中均可释放信号量。 为了体现使用信号量来达到线程间的同步,…

删除链表的倒数第n个节点【java版】

思路&#xff1a;要删除链表的倒数第n个节点&#xff0c;只需要找到倒数第n1个节点然后改变他的指针即可! 问题转换为&#xff1a;找到倒数第n1个节点? 假设要删除倒数第2个节点&#xff0c;只需要找到倒数第3个节点&#xff0c;问题是如何定位到这个节点 可见一个指针是不够…

【nodejs】使用express-generator快速搭建项目框架

文章目录 一、全局安装express-generator二、安装依赖三、启动项目四、修改文件便重启服务器1、全局安装nodemon2、修改 package.json 文件3、npm start 启动项目 一、全局安装express-generator npm install -g express-generator二、安装依赖 项目根目录打开终端&#xff0…

新手备战软考不要慌!这份软考全攻略请收下!

软考有哪些变化&#xff1f; 相信很多考生也关注到了&#xff0c;软考这两年进行了很多调整&#xff0c;软考这两年在多方面的形式上进行了一些调整。像2023年下半年开始&#xff0c;软考从以前的纸笔考试改成了机考。今年在考试科目的次数和时间安排上也进行了一些调整。 比…

久吾高科技股份有限将莅临2024第13届生物发酵展

参展企业介绍 江苏久吾高科技股份有限公司成立于1997年&#xff0c;是一家专注从事新材料研发与整体解决方案的高科技企业。2017年3月在深交所A股创业板上市。公司是首批认定的guojiaji高新技术企业、国家专精特新“小巨人”企业、国家制造业单项、中国膜行业陶瓷膜领域龙头企…

白帽工具箱:在windows上安装部署渗透测试演练系统DVWA-方法二

&#x1f31f;&#x1f30c; 欢迎来到知识与创意的殿堂 — 远见阁小民的世界&#xff01;&#x1f680; &#x1f31f;&#x1f9ed; 在这里&#xff0c;我们一起探索技术的奥秘&#xff0c;一起在知识的海洋中遨游。 &#x1f31f;&#x1f9ed; 在这里&#xff0c;每个错误都…

流程编排是如何实现解耦代码

为什么要使用流程流程编排 问题原因 在我们日常开发中&#xff0c;当我们接到一个复杂业务需求时&#xff0c;大部分开发同学都是通过面向过程编程代码&#xff0c;瀑布式编写代码风格&#xff0c;当新增新的需求时我们需要修改当前的方法&#xff0c;当需求变更很频繁代码的…

恶意不息上线时间/游戏价格/配置要求/加速器推荐

Moon Studios 联合创始人、技术总监 Gennadiy Korol 解释说&#xff1a;我们的目标是让战斗更有身临其境感一些、更加专注一些。而不是屏幕上的信息量多到爆炸&#xff0c;让人看不过来。我们要让玩家真正感受到角色的每一个动作。战斗是贴近的&#xff0c;是专注的。不是屏幕上…

Day:007(3) | Python爬虫:高效数据抓取的编程技术(scrapy框架使用)

Scrapy 保存数据案例-小说保存 spider import scrapyclass XiaoshuoSpiderSpider(scrapy.Spider):name xiaoshuo_spiderallowed_domains [zy200.com]url http://www.zy200.com/5/5943/start_urls [url 11667352.html]def parse(self, response):info response.xpath(&qu…

Linux第90步_异步通知实验

“异步通知”的核心就是信号&#xff0c;由“驱动设备”主动报告给“应用程序”的。 1、添加“EXTI3.c” #include "EXTI3.h" #include <linux/gpio.h> //使能gpio_request(),gpio_free(),gpio_direction_input(), //使能gpio_direction_output(),gpio_get_v…

浅谈Java IO流

Java中的IO流&#xff08;Input/Output streams&#xff09;是Java程序用来处理数据输入和输出的核心工具集。IO流抽象了数据流动的概念&#xff0c;允许Java程序与外部世界进行数据交换&#xff0c;无论是从文件、网络、键盘输入还是向屏幕、文件或网络发送数据。Java IO流按照…

RocketMQ:Windows下开发环境搭建

一、准备工作 从RockitMQ官网下载 | RocketMQ下载最新的release包。我这里下载的版本是v5.2.0 解压到本地目录&#xff0c;bin目录下存放可运行的脚本。 二、RocketMQ基本结构 在动手开发之前&#xff0c;我们需要了解一下RocketMQ的基本结构 如上图所示&#xff0c;一个正常…

【ROS2笔记七】ROS中的参数通信

7.ROS中的参数通信 文章目录 7.ROS中的参数通信7.1使用CLI工具调整参数7.2参数通信之rclcpp实现7.2.1创建节点7.2.2rclcpp参数API Reference ROS2中的参数是由键值对组成的&#xff0c;参数可以实现动态调整。 7.1使用CLI工具调整参数 启动turtlesim功能包的环境 ros2 run …

进程、线程和协程

进程、线程和协程 进程是程序的执行实例 线程是进程的执行路径 协程是基于线程之上但又比线程更加轻量级的存在 进程与线程的区别 线程是程序执行的最小单位&#xff0c;而进程是操作系统分配资源的最小单位 进程和程序的区别 程序&#xff1a;执行特定任务的一串代码&a…

Fastjson报autotype is not support

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 打开AutoType功能 …

快速删除node_modules依赖包的命令rimraf

1、安装rimraf npm install -g rimraf 2、使用命令删除node_modules rimraf node_modules *** window系统&#xff0c;使用命令很快就删除node_modules ***

python-获取config.ini中的属性值

获取配置文件中的数据 import configparser class ReadConfig(object):def __init__(self,config_file_path):self.config configparser.ConfigParser()self.config.read(config_file_path,encodingutf-8)def get_config(self,section,option):config_valueself.config.get(s…

Linux系统——Zookeeper集群

目录 一、Zookeeper概述 1.Zookeeper简介 2.Zookeeper工作机制 3.Zookeeper数据结构 4.Zookeeper应用场景 4.1统一命名服务 4.2统一配置管理 4.3统一集群管理 4.4服务器动态上下线 4.5软负载均衡 5.Zookeeper选举机制 5.1第一次启动选举机制 5.2非第一次启动选举机…

前端开发框架BootStrap

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl BootStrap概述 Bootstrap是一个开源的前端框架&#xff0c;它由Twitter的设计师和开发者创建并维护。Bootstrap提供了许多现成的Web组件&#xff0c;可帮助开发者快速设计和…