Spring Boot+Atomikos进行多数据源的分布式事务管理详解和实例
文章目录
- 0.前言
- 1.参考文档
- 2.基础介绍
- 3.步骤
- 1. 添加依赖到你的`pom.xml`文件:
- 2. 配置数据源及其对应的JPA实体管理器和事务管理器:
- 3. Spring Boot+MyBatis集成Atomikos
- 4. 在application.properties文件中配置数据源和JPA属性:
- 4.使用示例
- 5.底层原理

0.前言
背景: 一直零散的使用着Spring Boot 的各种组件和特性,从未系统性的学习和总结,本次借着这个机会搞一波。共同学习,一起进步。哈哈
Atomikos是一个易用、可靠、开放源码的事务管理器,它可以用于管理分布式事务,尤其在微服务架构中非常实用。它支持JTA(Java Transaction API)规范,能够与各种JTA兼容的资源管理器(如数据库和消息队列)配合使用。
-
分布式事务:当一个业务操作需要修改多个资源(如多个数据库或消息队列)时,我们需要保证这些修改操作的原子性,即它们要么全部成功,要么全部失败。这就是分布式事务。
-
JTA:Java Transaction API(JTA)是Java平台的一个事务规范,定义了用户和事务管理器以及事务管理器和资源管理器之间的接口。Atomikos作为一个事务管理器,就是遵循JTA规范的。
-
XA协议:XA协议是分布式事务的一个重要协议,它定义了全局事务ID、分支事务ID等概念,以及如何协调分支事务的接口。Atomikos支持XA协议。
Atomikos还提供了自动恢复、故障转移等高级特性,以进一步提高分布式事务的可靠性。
1.参考文档
Spring Boot 提供了一个用于整合 Atomikos 的 starter,名为 spring-boot-starter-jta-atomikos
。它是 Spring Boot 提供的一系列 “starter” 依赖之一
-
Spring Boot 官方文档
Spring Boot 官方文档的 “Spring Boot Features” 部分有一个 “Working with JTA” 的小节,其中提到了如何使用spring-boot-starter-jta-atomikos
。链接:https://docs.spring.io/spring-boot/docs/2.5.3/reference/htmlsingle/#boot-features-jta
-
Atomikos 官方文档
虽然 Atomikos 的官方文档并没有专门介绍spring-boot-starter-jta-atomikos
,但它提供了一些关于如何使用 Atomikos 的教程,你可以参考这些教程来理解spring-boot-starter-jta-atomikos
是如何工作的。链接:https://www.atomikos.com/Documentation/SpringBootIntegration
2.基础介绍
Atomikos 是一个提供分布式事务管理的开源事务管理器。将它们结合使用,可以在 Spring Boot 应用程序中实现分布式事务的管理。
在 Spring Boot 中使用 Atomikos,通常需要进行以下步骤:
-
引入 Atomikos 依赖:
首先,在 Maven 或 Gradle 构建文件中添加 Atomikos 的依赖项。可以添加atomikos-transactions-spring-boot-starter
依赖,它是 Atomikos 与 Spring Boot 集成的起点。 -
配置数据源:
在 Spring Boot 应用程序中,你需要配置多个数据源。可以使用 Spring Boot 的自动配置功能,根据配置文件或属性来配置数据源。你可以使用任何支持 Atomikos 的数据源,如 Atomikos 提供的AtomikosDataSourceBean
或其他第三方数据源。 -
配置 Atomikos 事务管理器:
在 Spring Boot 应用程序中,你需要配置 Atomikos 事务管理器。可以通过在配置类中创建JtaTransactionManager
实例,并将其与 Atomikos 的UserTransactionManager
和TransactionManager
关联起来。这样,Spring 将使用 Atomikos 事务管理器来管理分布式事务。 -
配置 JTA 事务管理器:
为了使 Spring Boot 应用程序能够使用 Atomikos 进行分布式事务管理,你需要配置 JTA 事务管理器。可以使用 Spring Boot 的自动配置功能,根据配置文件或属性来配置 JTA 事务管理器。 -
在方法上添加
@Transactional
注解:
在需要进行事务管理的方法上,添加 Spring 的@Transactional
注解。这将告诉 Spring 在方法执行期间启动和提交事务,并回滚事务(如果发生异常)。
使用 Spring Boot+Atomikos 的原理如下:
-
Spring Boot 提供了自动配置功能,可以根据配置文件或属性来自动配置数据源和事务管理器。
-
Atomikos 是一个独立的事务管理器,它提供了 JTA(Java Transaction API)的实现,可以处理分布式事务。
-
在 Spring Boot 中,你配置了多个数据源,并使用 Atomikos 的数据源实现(如
AtomikosDataSourceBean
)来实现分布式事务。 -
当你在方法上添加了
@Transactional
注解时,Spring Boot 会使用 Atomikos 的事务管理器来管理事务。 -
当方法执行时,事务管理器会协调各个数据源的事务,并在方法执行完成后根据事务的状态来提交或回滚事务。
总结来说,Spring Boot+Atomikos 的原理是利用 Spring Boot 的自动配置功能来配置多个数据源和事务管理器,并使用 Atomikos 的事务管理器实现分布式事务的管理。这样,你可以在 Spring Boot 应用程序中使用 @Transactional
注解来管理分布式事务。
3.步骤
确保你的数据库支持XA事务,否则无法使用Atomikos。
1. 添加依赖到你的pom.xml
文件:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
2. 配置数据源及其对应的JPA实体管理器和事务管理器:
在配置类中,我们需要分别为每个数据源创建DataSource、LocalContainerEntityManagerFactoryBean和JpaTransactionManager。这里假设有两个数据源db1和db2:
@Configuration
@EnableTransactionManagement
public class AtomikosConfig {@Bean@Primary@ConfigurationProperties(prefix = "spring.datasource.db1")public DataSource dataSource1() {return new AtomikosDataSourceBean();}@Bean@ConfigurationProperties(prefix = "spring.datasource.db2")public DataSource dataSource2() {return new AtomikosDataSourceBean();}@Bean@Primarypublic LocalContainerEntityManagerFactoryBean entityManagerFactory1() {HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();jpaVendorAdapter.setGenerateDdl(true);LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();factory.setJpaVendorAdapter(jpaVendorAdapter);factory.setPackagesToScan("com.example.package1");factory.setDataSource(dataSource1());factory.setPersistenceUnitName("persistenceUnit1");return factory;}@Beanpublic LocalContainerEntityManagerFactoryBean entityManagerFactory2() {HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();jpaVendorAdapter.setGenerateDdl(true);LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();factory.setJpaVendorAdapter(jpaVendorAdapter);factory.setPackagesToScan("com.example.package2");factory.setDataSource(dataSource2());factory.setPersistenceUnitName("persistenceUnit2");return factory;}
}
注意,这里使用了@Primary注解来标记主数据源和对应的实体管理器。
3. Spring Boot+MyBatis集成Atomikos
如果你的项目里同时使用了Spring Boot,MyBatis和Atomikos 。 两个数据源定义两个SqlSessionFactory和两个事务管理器,每个SqlSessionFactory和DataSourceTransactionManager都关联了一个特定的数据源。请注意,在我们定义SqlSessionFactory时,指定了mapper文件的路径,这是必需的,以便MyBatis知道如何将SQL语句映射到你的Java对象。你需要根据你的项目结构来修改这些路径。
@Configuration
@EnableTransactionManagement
public class AtomikosConfig {// 配置第一个数据源@Bean@Primary@ConfigurationProperties(prefix = "spring.datasource.db1")public DataSource dataSource1() {return new AtomikosDataSourceBean();}// 配置第二个数据源@Bean@ConfigurationProperties(prefix = "spring.datasource.db2")public DataSource dataSource2() {return new AtomikosDataSourceBean();}// 配置第一个SqlSessionFactory@Bean@Primarypublic SqlSessionFactory sqlSessionFactory1() throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSource1());sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/db1/*.xml"));return sqlSessionFactoryBean.getObject();}// 配置第二个SqlSessionFactory@Beanpublic SqlSessionFactory sqlSessionFactory2() throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSource2());sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/db2/*.xml"));return sqlSessionFactoryBean.getObject();}// 配置第一个事务管理器@Beanpublic DataSourceTransactionManager transactionManager1() {return new DataSourceTransactionManager(dataSource1());}// 配置第二个事务管理器@Beanpublic DataSourceTransactionManager transactionManager2() {return new DataSourceTransactionManager(dataSource2());}
}
4. 在application.properties文件中配置数据源和JPA属性:
spring.datasource.db1.unique-resource-name=datasource1
spring.datasource.db1.xa-data-source-class-name=com.mysql.cj.jdbc.MysqlXADataSource
spring.datasource.db1.xa-properties.databaseName=db1
spring.datasource.db1.xa-properties.url=jdbc:mysql://localhost:3306/db1
spring.datasource.db1.xa-properties.user=root
spring.datasource.db1.xa-properties.password=password
spring.datasource.db1.pool-size=5spring.datasource.db2.unique-resource-name=datasource2
spring.datasource.db2.xa-data-source-class-name=com.mysql.cj.jdbc.MysqlXADataSource
spring.datasource.db2.xa-properties.databaseName=db2
spring.datasource.db2.xa-properties.url=jdbc:mysql://localhost:3306/db2
spring.datasource.db2.xa-properties.user=root
spring.datasource.db2.xa-properties.password=password
spring.datasource.db2.pool-size=5
-
创建Atomikos的UserTransaction和TransactionManager实例。
-
创建要参与分布式事务的资源(如数据库连接)的XADataSource实例,并将它们注册到Atomikos。
-
调用UserTransaction的begin方法开始事务。
-
通过XADataSource获取资源连接,进行业务操作。
-
调用UserTransaction的commit方法提交事务,或调用rollback方法回滚事务。
4.使用示例
接下来,我们将创建一个简单的服务来演示如何使用在两个数据库上进行分布式事务。这个服务将在两个数据库上进行数据的添加操作。
首先创建两个实体类,分别对应两个数据库的表:
@Entity
@Table(name = "test1")
public class Test1 {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;//...
}@Entity
@Table(name = "test2")
public class Test2 {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;//...
}
然后创建对应的Repository:
public interface Test1Repository extends JpaRepository<Test1, Long> {}public interface Test2Repository extends JpaRepository<Test2, Long> {}
接下来创建处理这两个数据库操作的Service:
@Service
public class TestService {private final Test1Repository test1Repository;private final Test2Repository test2Repository;public TestService(Test1Repository test1Repository, Test2Repository test2Repository) {this.test1Repository = test1Repository;this.test2Repository = test2Repository;}@Transactionalpublic void addData() {Test1 test1 = new Test1();//...test1Repository.save(test1);Test2 test2 = new Test2();//...test2Repository.save(test2);}
}
在addData
方法中,我们在Test1
和Test2
两个表中分别添加数据。由于addData
方法添加了@Transactional
注解,所以这两个添加数据的操作会在同一个事务中执行。如果在添加数据到Test2
表时出现了错误,那么添加数据到Test1
表的操作也会被回滚。
在Controller或者其他层调用addData
方法,例如:
@RestController
public class TestController {private final TestService testService;public TestController(TestService testService) {this.testService = testService;}@PostMapping("/addData")public ResponseEntity<String> addData() {testService.addData();return ResponseEntity.ok().body("Data added successfully");}
}
5.底层原理
Atomikos 的底层原理是基于 JTA 规范,通过事务管理器协调和管理分布式事务,使用两阶段提交协议保证事务的一致性,提供日志和恢复机制用于持久化和恢复事务状态,以及使用资源适配器与各个资源进行交互。这些组件和机制共同提供了可靠的分布式事务管理功能。
-
JTA 实现:
Atomikos 实现了 JTA 规范,它提供了javax.transaction
包中定义的接口和类的实现。这些接口包括UserTransaction
、TransactionManager
和Transaction
等。Atomikos 利用这些接口和类来进行事务的管理和控制。 -
事务管理器(Transaction Manager):
Atomikos 提供了一个事务管理器,用于协调和管理分布式事务。事务管理器负责事务的创建、启动、提交、回滚和状态管理等。它是 Atomikos 的核心组件,负责处理多个资源参与的分布式事务。 -
本地事务管理:
Atomikos 还支持本地事务管理,即仅在单个数据库或资源上执行的事务。对于本地事务,Atomikos 利用底层资源的本地事务管理功能,例如 JDBC 的本地事务或 JMS 的本地事务。 -
分布式事务管理:
对于涉及多个资源的分布式事务,Atomikos 使用两阶段提交(Two-Phase Commit,2PC)协议来保证事务的一致性。在这个协议中,事务管理器与各个资源管理器进行通信,确保所有资源都准备好提交事务,并在所有资源都准备好后,进行事务的提交操作。 -
日志和恢复:
Atomikos 还提供了日志和恢复机制,用于处理事务的持久化和恢复。它通过将事务的状态和操作记录到日志中,以确保事务的持久性。在系统故障或崩溃后,Atomikos 可以使用日志进行事务的恢复,并保证事务的一致性。 -
资源适配器(Resource Adapter):
Atomikos 使用资源适配器来与各个资源进行交互,例如数据库、消息队列等。资源适配器负责管理资源的连接、事务的参与和操作的执行等。Atomikos 提供了一些内置的资源适配器,同时也支持自定义资源适配器。
相关文章:

Spring Boot+Atomikos进行多数据源的分布式事务管理详解和实例
文章目录 0.前言1.参考文档2.基础介绍3.步骤1. 添加依赖到你的pom.xml文件:2. 配置数据源及其对应的JPA实体管理器和事务管理器:3. Spring BootMyBatis集成Atomikos4. 在application.properties文件中配置数据源和JPA属性: 4.使用示例5.底层原理 0.前言 背景&#x…...

地上集装箱式村庄污水处理设备厂家价格
诸城市鑫淼环保小编带大家了解一下地上集装箱式村庄污水处理设备厂家价格 废水经水解酸化池后自流到生物接触氧化池,接触氧化法是一种好氧生物膜法工艺,微生物以生物膜形式及悬浮态生长于水中.它兼具活性污泥及生物滤池二者的特点.在生物接触氧化池中有机碳水化合物被分解成C0和…...

ASIC-WORLD Verilog(13)状态机FSM
写在前面 在自己准备写一些简单的verilog教程之前,参考了许多资料----Asic-World网站的这套verilog教程即是其一。这套教程写得极好,奈何没有中文,在下只好斗胆翻译过来(加点自己的理解)分享给大家。 这是网站原文&…...

设置微软Edge浏览器主页和新标签页,摆脱扰人和分散注意力的主页
默认情况下,Microsoft Edge会向您显示世界上最令人分心和讨厌的主页(也称为主屏幕)。微软不想只向你展示一个搜索框,也许还有一个漂亮的背景或一些你喜欢的网站的快捷方式,而是想在你面前扔一堆新闻标题和广告。 你可能会打开浏览器阅读电子邮件,结果被Microsoft Edge主…...

视频汇聚平台EasyCVR安防视频监控平台新增经纬度选取功能的详细介绍
视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同,支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强,视频能力丰富,具体可实现视频监控直播、视频轮播、视频录像、…...

HCIP-HCS华为私有云的使用
1、概述 HCS(HuaweiCoudStack)华为私有云:6.3 之前叫FusionSphere OpenStack,6.3.1 版本开始叫FusionCloud,6.5.1 版本开始叫HuaweiCloud Stack (HCS)华为私有云软件。 开源openstack,发放云主机的流程&am…...

深度学习1.卷积神经网络-CNN
目录 卷积神经网络 – CNN CNN 解决了什么问题? 需要处理的数据量太大 保留图像特征 人类的视觉原理 卷积神经网络-CNN 的基本原理 卷积——提取特征 池化层(下采样)——数据降维,避免过拟合 全连接层——输出结果 CNN …...

浏览器输入一个URL之后发生了什么?
URL解析DNS解析TCP连接TSL连接HTTP请求TCP挥手接收并解析响应 URL 解析 主要分为: 协议,eg http,https域名或者ip地址,eg www.baidu.com 域名相对于ip地址来说,更方便人们记忆,但是实际的网络传输中使用的是ip地址 端…...

uniapp 布局(自定义导航栏加固定高度的主要内容)
不想让整体页面出现滚动条 页面大致分为三部分,导航栏、主题内容、tabbar,不想让整个页面出现滚动条,只想让主要内容滚动。 我这里是直接用了uni.getSystemInfoSync(),整体分为两部分,自定义头部和滚动内容ÿ…...

android手机销售app(IDEA,SpringBoot,SSM,MySQL)+支付宝支付+全套视频教程
本项目亮点: 支付宝支付 eCharts柱状图图表数据统计 【项目功能介绍】 本系统包含后台管理和前端app双端系统,后台管理的功能包含: 登录, 退出, 修改管理员信息(基本信息与头像),资源管理,角色管理,资源权限分配,字典管理,用户管理,图书管理,订单管理,订单统计; a…...

深入探讨Java虚拟机(JVM)的工作原理与优化策略
摘要:本文对Java虚拟机(JVM)的工作原理进行深入探讨,包括其内存管理、垃圾回收以及代码执行等方面。同时,文章还通过具体的代码示例,阐述了JVM的优化策略,旨在提高Java程序的性能。 一、引言 …...

WPF数据绑定
数据绑定是一个很强大且优雅的技能,之前用过好多次,但有些地方总不是特别清晰,常常需要重新翻阅资料来回顾,于是这次用了几天时间好好梳理一下,记录一下。 首先数据绑定对数据对象的要求:需要是公有属性&a…...

Android学习之路(6) 其他UI控件
ImageView(图像视图) RadioButton(单选按钮)&Checkbox(复选框) 开关按钮ToggleButton和开关Switch ProgressBar(进度条) SeekBar(拖动条) RatingBar(星级评分条) ScrollView(滚动条)...

matlab实现牛顿迭代法求解非线性方程
非线性方程是指含有未知数的方程,且方程中至少有一个未知数的次数大于一或者含有非一次幂的函数(如指数、对数、三角函数等)。例如,$f(x) x^3 - 2x - 5 0$就是一个非线性方程。非线性方程通常没有显式的解析解,因此需…...

Cpp学习——编译链接
目录 编辑 一,两种环境 二,编译环境下四个部分的 1.预处理 2.编译 3.汇编 4.链接 三,执行环境 一,两种环境 在程序运行时会有两种环境。第一种便是编译环境,第二种则是执行环境。如下图: 在程序运…...

android - fragment 数据丢失?状态丢失?
最佳答案 一些状态丢失的例子: 1. 假设您有一个按钮和一个 TextView 。在代码中,你已经定义了初始值为 0 的整数 i,它通过单击按钮递增 1,并且它的值显示在 TextView 中。假设你已经按下按钮 5 次,那么 textview 将被设置为 0。也…...

Git基本操作
本地仓库 当我们初始化(git init)之后,会在当前目录下生成一个与项目并列的.git文件夹,当我们对项目作出更改之后使用git commit命令,一般是将修改提交到本地仓库,也就是该文件夹下面的文件会对应修改&…...

Nginx配置文件详解
Nginx配置文件详解 1、Nginx配置文件1.1主配置文件详解1.2子配置文件 2、全局配置部分2.1修改启动的工作进程数(worker process) 优化2.2cpu与worker process绑定2.3 PID 路径修改2.4 修改工作进程的优先级2.5调试工作进程打开的文件的个数2.6关闭master-worker工作…...

【0217】stats collector(统计信息收集器)进程启动原理(1)
文章目录 1. 启动 stats collector进程1.1 stats collector进程启动过程1.1.1 检查套接字 pgStatSock 是否存在1.1.2 重新启动失败的stats collector频率1.1.3 fork() 三种返回值处理1.2 detach所有共享内存段1.3 detach 共享内存段1.4 stats collecotr进程启动的主体相关阅读:…...

【应用层】网络基础 -- HTTPS协议
HTTPS 协议原理加密为什么要加密常见的加密方式对称加密非对称加密 数据摘要&&数据指纹 HTTPS 的工作过程探究方案1-只使用对称加密方案2-只使用非对称加密方案3-双方都使用非对称加密方案4-非对称加密对称加密中间人攻击-针对上面的场景 CA认证理解数据签名方案5-非对…...

实验篇—— 基因家族Motif 分析
实验篇—— 基因家族Motif 分析 文章目录 前言一、名词解释二、实操1. MEME工具箱2. Motif Discovery(基序发现)1. 结果网页2. 在TBtools中(额外) 2. Motif Enrichment(基序富集分析)3. Motif Search&#…...

Linux拓展之阻止或禁用普通用户登录
禁止指定用户登录 chsh -s /sbin/nologin 指定用户名示例 chsh -s /sbin/nologin testuser恢复指定用户登录 chsh -s /bin/bash 指定用户名示例 chsh -s /bin/bash testuser参考 https://blog.csdn.net/cnds123321/article/details/125232580 https://www.cnblogs.com/cai…...

Linux系统USB摄像头测试程序(四)_视频旋转及缩放
下面的程序实现了视频的旋转及缩放,窗口中点击鼠标左键视频向左旋转,点击鼠标右键视频向右旋转并且视频缩小了二分之一。程序中首先把yvyv422转换成了RGB24,然后利用opencv进行了旋转和缩放,其后用sdl2进行了渲染。使用了ffmpeg、…...

大模型+学习机,是概念游戏还是双向奔赴?
众所周知,2023年上半年大模型概念炙手可热。各大科技公司纷纷卷入,或宣称布局相关领域,或率先官宣自研大模型。而随着资本市场对大模型概念的热情有所消退,属于这片战场的新一轮角逐慢慢聚焦在了技术的落地应用上。 8月15日&#…...

linux怎么查看用户属于哪个组
查看当前用户所属组 shell> groups root查看指定用户所属组 shell> groups testuser testuser : testusershell> id testuser uid1000(testuser) gid1000(testuser) groups1000(testuser)查看组文件 shell> cat /etc/group...

邂逅JavaScript
前言:前端三大核心 前端开发最主要需要掌握的是三个知识点:HTML、CSS、JavaScript 一、认识编程语言 1.计算机语言 前面我们已经学习了HTML和CSS很多相关的知识: 在之前我们提到过, HTML是一种标记语言, CSS也是一种样式语言; 他们本身都是属于计算…...

Android 中 Fragment判空
1. 判断 Fragment 是否已经被添加到 Activity 中,可以通过 Fragment 的 isAdded() 方法来判断。 2. 判断 Fragment 的 View 是否已经被创建,可以通过 Fragment 的 getView() 方法来判断。 3. 判断 Fragment 是否已经被销毁,可以通过 Fragme…...

软考高级系统架构设计师系列论文八十八:财务数据仓库系统的设计与实现
软考高级系统架构设计师系列论文八十八:财务数据仓库系统的设计与实现 一、摘要二、正文三、总结一、摘要 近年来,数据仓库技术在信息系统的建设中得到了广泛应用,有效地为决策提供了支持。2020年6月,本人所在单位组织开发了财务管理决策系统,该系统主要是使高层领导掌握企…...

fastdeploy部署多线程/进程paddle ocr(python flask框架 )
部署参考:https://github.com/PaddlePaddle/FastDeploy/blob/develop/tutorials/multi_thread/python/pipeline/README_CN.md 安装 cpu: pip install fastdeploy-python gpu :pip install fastdeploy-gpu-python #下载部署示例代码 git cl…...

【图论】拓扑排序
一.定义 拓扑排序是一种对有向无环图(DAG)进行排序的算法,使得图中的每个顶点在排序中都位于其依赖的顶点之后。它通常用于表示一些任务之间的依赖关系,例如在一个项目中,某些任务必须在其他任务之前完成。 拓扑排序的…...