wenmo8 发布的文章

准备跑老师给定的Github上的多模态源码,但是用了这么久ubuntu还没有尝试过安装nvidia驱动,好在也是一次成功,于是记录下来。

借鉴的是
https://blog.csdn.net/Eric_xkk/article/details/131800365
这篇文章,按照流程来基本没有问题,不过个人觉得有些步骤比较冗余,所以记录下来

主要流程

关闭安全模式

这个在安装双系统的时候就在window中关闭过了,所以不设置

打开独显模型

这个没有设置,但是最后也可以成功识别到显卡

更新软件源

sudo apt-get update   #更新软件列表
 
sudo apt-get install g++
 
sudo apt-get install gcc
 
sudo apt-get install make

禁用nouveau

nouveau是系统自带的驱动程序,一般来讲要安装新的显卡驱动的时候需要禁用这个驱动

首先打开nouveau的配置文件:

sudo gedit /etc/modprobe.d/blacklist.conf

然后在最下面加上:

blacklist nouveau
options nouveau modeset=0

保存修改之后在终端执行:

sudo update-initramfs –u

重启电脑

终端执行:

sudo reboot

(lightdm那一步个人觉得可做可不做,好像和默认的gdm3的区别就是显示占用的内存大小,但是我看了一下lightdm占用的是6MB左右,gdm3占用的是9MB作用,区别不大。按照个人的喜好设置即可,其实就是一个显示器插件)

安装显卡驱动工具

sudo apt-get install nvidia-cuda-toolkit

查看系统推荐显卡驱动

这里要看好哪个是系统推荐的驱动(recommend),并且要记下来

sudo ubuntu-drivers devices

比如我的系统推荐的就是550(很奇怪,当时第一次下载的时候推荐的是550,现在是560...不过区别应该不大)
这里说一下三种驱动的大致区别:

  • nvidia-driver-xxx :这是适配大多数用户,一般情况下都可以安装这个
  • nvidia-driver-xxx-server :这个适配服务器,有多块GPU调度等情况
  • nvidia-driver-xxx-open :这个是开源版本,可以自己修改
== /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0 ==
modalias : pci:v000010DEd000028A0sv0000103Csd00008BBEbc03sc00i00
vendor   : NVIDIA Corporation
driver   : nvidia-driver-545 - distro non-free
driver   : nvidia-driver-545-open - distro non-free
driver   : nvidia-driver-550-open - third-party non-free
driver   : nvidia-driver-565 - third-party non-free
driver   : nvidia-driver-560-open - third-party non-free
driver   : nvidia-driver-550 - third-party non-free
driver   : nvidia-driver-560 - third-party non-free recommended
driver   : nvidia-driver-535-open - distro non-free
driver   : nvidia-driver-535-server-open - distro non-free
driver   : nvidia-driver-535-server - distro non-free
driver   : nvidia-driver-565-open - third-party non-free
driver   : nvidia-driver-535 - distro non-free
driver   : xserver-xorg-video-nouveau - distro free builtin

添加驱动源

sudo add-apt-repository ppa:graphics-drivers/ppa
sudo apt-get update

下载驱动

在ubuntu的
软件和更新
中的
附加驱动
中找到刚才系统recommend的显卡驱动型号,然后下载

测试

安装之后reboot重启电脑,输入
nvidia-smi
查看信息,如果出现下列信息,则说明安装成功:

Tue Dec 24 12:12:34 2024       
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.142                Driver Version: 550.142        CUDA Version: 12.4     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 4060 ...    Off |   00000000:01:00.0 Off |                  N/A |
| N/A   44C    P0            588W /   80W |       9MiB /   8188MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
                                                                                         
+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI        PID   Type   Process name                              GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|    0   N/A  N/A      1735      G   /usr/lib/xorg/Xorg                              4MiB |
+-----------------------------------------------------------------------------------------+

修改界面登陆器

如果前面下载了lightdm,觉得不好看想换回gdm3(比如我),输入
sudo dpkg-reconfigure lightdm
后选中gdm3即可,reboot重启之后即可奏效

cuda检查

在pycharm中输入
torch.cuda.is_availiable()
,输出为true,说明安装成功,大功告成!

从 $PGDATA 到文件组:深入解析 PostgreSQL 与 SQL Server 的存储策略

在数据库领域,数据存储和管理的效率与可靠性是决定系统性能、可扩展性和易于管理的关键因素。PostgreSQL 和  SQL Server 在数据存储方面采取了略有不同的方式。


PostgreSQL 中一个数据库管理员经常遇到的关键概念是
$PGDATA
文件夹。在这里,我们将探讨
$PGDATA
文件夹是什么,它的重要性,以及 PostgreSQL 的数据存储架构与  SQL Server 的比较。

什么是 $PGDATA?

$PGDATA
在 PostgreSQL 中指的是包含数据库集群所有配置和数据文件的目录。这是 PostgreSQL 数据存储架构的核心。简单来说,它是 PostgreSQL 存储所有文件的目录,这些文件包括实际数据、配置文件、事务日志以及其他重要信息,确保数据库集群能够正常运行。

$PGDATA 文件夹的关键组成部分


$PGDATA
文件夹中,你会发现许多子目录和文件,它们存储数据库的不同方面。

其中一些关键组成部分包括:

  • base 目录
    :存储集群中每个数据库的实际数据。每个数据库在 base 文件夹中都有一个单独的子目录。

  • PG_VERSION
    :这是一个小文件,用于存储该集群使用的 PostgreSQL 版本信息,这在升级或迁移时确保兼容性。

  • pg_wal(预写日志)
    :该文件夹包含预写日志(WAL),对于事务的持久性和恢复至关重要。WAL 文件记录了对数据所做的每一次更改,确保在崩溃的情况下数据库可以恢复到一致的状态。

  • pg_hba.conf
    :负责基于主机的身份验证配置文件,在此文件中,你可以定义允许哪些用户从哪些位置(主机)连接到哪些数据库。

  • postgresql.conf
    :主要的配置文件,包含 PostgreSQL 实例的所有操作设置,包括内存设置、日志参数和其他与性能相关的设置。

$PGDATA 的重要性

$PGDATA
文件夹至关重要,因为它本质上是 PostgreSQL 的支柱。如果该目录损坏或丢失,集群中的所有数据都会无法恢复。因此,定期保护和备份
$PGDATA
目录是每个数据库管理员的一项基本任务。

PostgreSQL 使用基于文件的存储系统,默认情况下将数据写入 8KB 的页面。页面大小可以在编译 C 源代码时更改。这些页面随后被分组为段并存储在
$PGDATA
目录中。

配置 $PGDATA

在 PostgreSQL 初始化(通常使用 initdb)期间,你可以指定
$PGDATA
目录的位置。你可以通过向 initdb 命令传递 -D 选项或设置 $PGDATA 环境变量来设置位置。例如:

initdb -D /var/lib/postgresql/data

初始化后,PostgreSQL 期望环境变量 $PGDATA 始终指向此位置,无论服务器是启动还是停止。

SQL Server 中的数据存储

虽然 PostgreSQL 依赖于
$PGDATA
文件夹进行核心操作,SQL Server 在数据存储和管理方面采取了略有不同的方式。Microsoft SQL Server 将其数据组织为数据库,每个数据库都有自己的一组数据文件、事务日志文件和备份选项。

在  SQL Server 中,数据库存储在不同类型的文件中:

  • 主数据文件(.mdf)
    :这是存放所有数据库对象(如表、索引、存储过程)的主要数据文件。每个数据库至少有一个 MDF 文件,mdf文件可以理解为数据库的共享表空间,默认所有表都存储在这个mdf文件里。
  • 次要数据文件(.ndf)
    :这些文件是可选的,用于将数据分布到多个磁盘上,从而提高大型系统的性能和可扩展性,ndf文件可以理解为独立表空间,当你把一个文件组附加在ndf文件上,然后指定数据表建立在这个文件组上,相当于把表建立在独立表空间上。
  • 事务日志文件(.ldf)
    :类似于 PostgreSQL 的 WAL,LDF 文件存储数据库中每个事务的日志记录。这对于确保在发生故障时可以回滚事务至关重要,也用于恢复过程。

与 PostgreSQL 不同,PostgreSQL将数据存储在
$PGDATA
文件夹中的子目录下,SQL Server 将数据库存储在这些 .mdf、.ndf 和 .ldf 文件中,通常位于管理员在安装或创建数据库时设置的特定目录或磁盘上。

PostgreSQL 与 SQL Server 存储的关键区别

现在我们已经了解了 PostgreSQL 和 SQL Server 如何处理数据存储,以下是它们之间的关键差异:

  • 存储位置

PostgreSQL 将所有数据存储在
$PGDATA
目录中,该目录包含每个数据库的子目录以及 WAL 日志和配置文件。SQL Server 使用单独的文件(.mdf、.ndf、.ldf)来存储每个数据库。管理员可以将这些文件放置在任何磁盘或目录中。

  • 事务日志

PostgreSQL 使用 pg_wal 目录来存储预写日志。SQL Server 使用 .ldf 文件进行事务日志记录,与 PostgreSQL 的 WAL 类似,但每个数据库都有自己的 .ldf 文件。

  • 文件组抽象

SQL Server 提供文件组,这是一层抽象,允许管理员将数据分布在多个存储设备上以提高性能和管理。PostgreSQL 没有类似的抽象层,文件管理完全依赖于操作系统,管理员只能将不同的表空间放置在不同的磁盘上。

  • 配置文件

PostgreSQL 的配置文件紧密集成在
$PGDATA
目录中(如 postgresql.conf、pg_hba.conf),而 SQL Server 的配置文件通常与数据库文件分开存储。

  • 临时数据存储

SQL Server 使用一个专门的系统数据库 TempDB 进行临时数据存储,而 PostgreSQL 则在现有的
$PGDATA
目录中使用临时表空间,与SQL Server类似,也是整个实例(集群)共享临时表空间。

总结

虽然 PostgreSQL 和 Microsoft SQL Server 都提供了强大的数据存储机制,但它们的方法却有着根本的不同。PostgreSQL 的
$PGDATA
文件夹是与数据库集群相关的所有内容的中央枢纽,

而 SQL Server 则依赖于更为精细化的 .mdf、.ndf 和 .ldf 文件系统来进行数据存储。每个系统都有其自身的优势,最佳选择通常取决于具体的使用场景、性能需求和系统架构偏好。

理解每个系统存储架构的细微差别对于数据库管理员来说至关重要,因为这将影响到备份策略、性能调优和灾难恢复计划的决策。


参考文章
https://www.javelin-tech.com/blog/2020/06/sizing-the-microsoft-sql-mdf-and-ldf/
https://docs.oracle.com/cd/E19787-01/820-3361/ciadcigh/index.html
https://docs.microfocus.com/UCMDB/11.0/ucmdb-docs/docs/eng/doc_lib/Content/database/PostgresConfig_c_logs.htm
https://www.postgresql.org/docs/current/storage-file-layout.html
https://www.quora.com/What-are-MDF-and-NDF-files-in-an-SQL-server


本文版权归作者所有,未经作者同意不得转载。

事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制

分类

主要分为编程式事务和声明式事务两种。

编程式事务

是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强,如下示例:

try {
    //TODO something
    transactionManager.commit(status);
} catch (Exception e) {
    transactionManager.rollback(status);
    throw new InvoiceApplyException("异常失败");
}

声明式事务

基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于TX和AOP的xml配置文件方式,第二种就是基于@Transactional注解了。

@Transactional
@GetMapping("/test")
public String test() {
int insert = cityInfoDictMapper.insert(cityInfoDict);
}

事务传播方式propagation

Spring的传播特性(行为)指的是:当一个事务方法调用另一个事务方法时,事务该如何进行。Spring的传播行为有七种,可以分为三大类类型。

场景:以下都会使用这个例子,方便理解;有A类内部有个a方法、B类内部有个b方法

支持当前事务

  1. REQUIRED:如果当前方法存在一个事务,则加入这个事务。若当前方法不存在事务,则新建一个事务。
    例:b方法的传播特性是REQUIRED,当a方法调用到b方法时,若a方法存在事务,则b方法加入这个事务,与a共用一个事务。若a方法不存在事务,则b方法新建一个事务。回滚怎么判断:前者两个方法使用同一个事务,当a或者b任意一个方法出现异常,a和b都会回滚;后者a没有事务不会回滚,b方法有事务,会回滚。

  2. SUPPORTS:如果当前方法存在事务,则加入事务。反之,则以非事务方式运行。
    例:b方法的传播特性是SUPPORTS,当a方法调用到b方法时,若a方法存在事务,则b方法加入这个事务。若a方法不存在事务,则b方法以非事务进行。

  3. MANDATORY:如果当前方法存在事务则加入事务,若不存在则抛出异常。
    例:b方法的传播特性是MANDATORY,当a方法调用到b方法时,若a方法存在事务,则b方法加入这个事务。若a方法不存在事务,则抛出异常。

不支持当前事务

  1. REQUIRES_NEW:新建一个事务,如果当前方法存在事务,则把当前事务挂起。
    例:b方法的传播特性是REQUIRES_NEW,当a方法调用到b方法时,b方法新建一个事务,如果a方法存在事务,则把a方法事务挂起。回滚怎么判断:要明白一点就是a和b的事务没有关系。当a没有事务时,只有b有事务,a方法不会回滚,b方法会回滚。后者a和b有不同事务,当b方法出现异常时b会回滚。而a方法会不会回滚需要分为两种情况:若b中的异常自己捕获则a不会回滚,若b中是是抛出异常,则a也会回滚。

  2. NOT_SUPPORTED:以非事务方式执行,若存在当前事务,则把当前事务挂起。
    例:b方法的传播特性是NOT_SUPPORTED,当a方法调用到b方法时,b方法以非事务方式执行,若a方法存在事务,则挂起a方法事务。

  3. NEVER:以非事务方式执行,若存在当前事务,则会抛出异常。
    例:b方法的传播特性是NEVER,当a方法调用到b方法时,b方法以非事务方式执行,若a方法存在事务,则抛出异常。

嵌套事务

NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作。

例:b方法的传播特性是NESTED,当a方法内调用到b方法时。若a方法存在事务,则在a方法内嵌套b方法的事务,两者是有联系的,b事务相当于a事务的子事务。若a方法不存在事务,则b方法新建一个事务。回滚怎么判断:前者a和b都有事务,并且是相关联的事务(a的事务相当于父事务、b的事务相当于子事务),当a方法出现异常时,a和b都会回滚。当b方法出现异常时则要分两种情况分析:若b中的异常自己捕获则a不会回滚,若b中是是抛出异常,则a也会回滚。

其他属性

isolation 属性:隔离级别

isolation :事务的隔离级别,默认值为 Isolation.DEFAULT。开发中基本都是 default 级别

  • Isolation.DEFAULT:使用底层数据库默认的隔离级别

  • Isolation.READ_COMMITTED:读已提交

  • Isolation.READ_UNCOMMITTED:读未提交

  • Isolation.REPEATABLE_READ:可重复读

  • Isolation.SERIALIZABLE:串行化

timeout 属性

timeout :事务的超时时间,。如果超过该时间限制但事务还没有完成,则自动回滚事务。默认值为 -1,默认不限制时间

readOnly 属性

readOnly :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

rollbackFor 属性

rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor属性

noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

@Transactional

作用范围

@Transactional 可以作用在接口、类、类方法。

  • 作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。

  • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。

  • 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效

错误使用场景

无需事务的业务

在没有事务操作的业务方法上使用 @Transactional 注解;

例如,在只有查询的操作上,或者在只操作单表的情况下 使用 @Transactional 注解。

  • 虽然对业务功能无影响,但从编码角度看是不规范的。其他开发者可能会认为该方法实际需要事务支持,从而增加理解代码的复杂性。
  • @Transactional是通过动态代理实现的,每次调用带有
    @Transactional
    注解的方法时,事务管理器都会检查是否需要启动一个新事务,造成性能开销。虽然事务管理的开销在大多数现代数据库和应用服务器中相对较小,但仍然存在。
@Transactional
public String testQuery() {
    standardBak2Service.getById(1L);
    return "testB";
}

事务范围过大

有些同学为了省事直接将 @Transactional 注解加在了类上或者抽象类上,这样做导致的问题就是
类内的方法或抽象类的实现类中所有方法全部都被事务管理
。增加了不必要的性能开销或复杂性,建议按需使用,只在有事务逻辑的方法上加@Transactional。

以下是事务范围过大可能引发的问题及其相关处理建议:

  • 锁竞争:事务范围过大可能会导致较长时间持有数据库锁,从而增加锁竞争的可能性。其他事务在等待锁释放期间,可能会导致响应时间变长或出现超时。
  • 死锁
    :长时间持有锁的事务增加了死锁的风险。当多个事务相互持有对方需要的资源时,可能会陷入死锁状态。

失效场景

应用在非 public 修饰的方法上

之所以会失效是因为@Transactional 注解依赖于Spring AOP切面来增强事务行为,这个 AOP 是通过代理来实现的

而无论是JDK动态代理还是CGLIB代理,Spring AOP的默认行为都是只代理
public
方法。

被用 final 、static 修饰方法

和上边的原因类似,被用
final

static
修饰的方法上加 @Transactional 也不会生效。

  • static 静态方法属于类本身的而非实例,因此代理机制是无法对静态方法进行代理或拦截的
  • final 修饰的方法不能被子类重写,事务相关的逻辑无法插入到 final 方法中,代理机制无法对 final 方法进行拦截或增强。

同类中非事务方法调用事务方法

比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。但是如果是A声明了事务,A的事务是会生效的。

失效原因:事务是基于动态代理实现的,但本类中调用另一个方法默认是this调用关系,并非动态代理,故失效
解决方案:要么将操作移动到事务中,要么调用另一个Service中的事务方法

Bean 未被 spring 管理

上边我们知道 @Transactional 注解通过 AOP 来管理事务,而 AOP 依赖于代理机制。因此,
Bean 必须由Spring管理实例!
要确保为类加上如
@Controller

@Service

@Component
注解,让其被Spring所管理,这很容易忽视。

异步线程调用

  • 例1:

如果我们在 testMerge() 方法中使用异步线程执行事务操作,通常也是无法成功回滚的,来个具体的例子。

假设testMerge() 方法在事务中调用了 testA(),testA() 方法中开启了事务。接着,在 testMerge() 方法中,我们通过一个新线程调用了 testB(),testB() 中也开启了事务,并且在 testB() 中抛出了异常。此时,testA() 不会回滚 和 testB() 回滚。

testA() 无法回滚是因为没有捕获到新线程中 testB()抛出的异常;testB()方法正常回滚。

在多线程环境下,Spring 的事务管理器不会跨线程传播事务,事务的状态(如事务是否已开启)是存储在线程本地的
ThreadLocal
来存储和管理事务上下文信息。这意味着每个线程都有一个独立的事务上下文,事务信息在不同线程之间不会共享。

  • 例2:
@Transactional
public void transactionalMethod() {
    new Thread(() ->{
        Valuation v  = new Valuation();
        v.setUserName("张三");
        valuationMapper.insert(v);
    }).start();
}

Spring的事务是通过数据库连接来实现不同线程使用不同的数据库连接,且放在ThreadLocal中,基于同一个数据库连接的事务才能同时提交或回滚,多线程场景下,拿到的数据库连接不是同一个

解决方案:

  1. 采用分布式事务保证
  2. 自己实现事务回滚

数据库引擎不支持事务

事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。

propagation 设置错误

若是错误的配置以下三种 propagation,事务将不会发生回滚。

  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

rollbackFor 设置错误

rollbackFor 可以指定能够触发事务回滚的异常类型。Spring 默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常(如IOException)不会触发回滚事务。如果在事务中抛出其他类型的异常,例如
checked exceptions
(检查型异常),但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。

失效原因:@Transactional 注解默认处理RuntimeException,即只有抛出运行时异常,才会触发事务回滚
解决方案:@Transactional 设置为 @Transactional(rollbackFor =Exception.class) 或者直接抛出运行时异常

异常被 catch了

spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。

在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。

嵌套事务问题

还有一种场景就是嵌套事务问题,比如,我们在 testMerge() 方法中调用了事务方法 testA() 和事务方法 testB(),此时不希望 testB() 抛出异常让整个 testMerge() 都跟着回滚;这就需要单独 try catch 处理 testB() 的异常,不让异常在向上抛。

@RequiredArgsConstructor
@Slf4j
@Service
public class TestMergeService {

    private final TestBService testBService;

    private final TestAService testAService;
    @Transactional
    public String testMerge() {
    
        testAService.testA();

        try {
            testBService.testB();
        } catch (Exception e) {
            log.error("testMerge error:{}", e);
        }
        return "ok";
    }
}

@Service
public class TestAService {

    @Transactional
    public String testA() {
        standardBakService.save(entity);
        return "ok";
    }
}

@Service
public class TestBService {

    @Transactional
    public String testB() {
        standardBakService.save(entity2);
        
        throw new RuntimeException("test2");
    }
}

源码分析

以下源码均基于Spring4.3.12版本。主要从 创建事务、开启事务、提交事务、事务回滚 的维度来详细分析声明式事务。

事务简易流程图

代理类生成

在Spring框架中,当配置了事务管理器并声明了@Transactional注解时,Spring会在实例化bean时生成事务增强的代理类。创建代理类参考源码路径如下:

AbstractAutowireCapableBeanFactory.createBean=>
            doCreateBean()=>
            initializeBean()=>
            applyBeanPostProcessorsAfterInitialization()=>
            postProcessAfterInitialization()(BeanPostProcessor内接口)=> 
      AbstractAutoProxyCreator.postProcessAfterInitialization()=>
            wrapIfNecessary()=>
            createProxy() 中  proxyFactory.setProxyTargetClass(true); //是否对类进行代理的设置,true为cglib代理

代理类中方法执行入口


TransactionInterceptor.invoke()
方法开始分析 (获取代理类,调用父类
TransactionAspectSupport.invokeWithinTransaction()
方法,该方法会将代理类的方法纳入事务中)。

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {

    public Object invoke(final MethodInvocation invocation) throws Throwable {
        // 返回代理类的目标类
        Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;
        //事务中执行被代理的方法
        return this.invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
            public Object proceedWithInvocation() throws Throwable {
                return invocation.proceed();
            }
        });
    }
}

主要核心逻辑

TransactionAspectSupport.invokeWithinTransaction()
方法负责获取事务属性和事务管理器,然后针对声明式事务和编程式事务区分处理流程(此处源码忽略编程式事务)。

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final TransactionAspectSupport.InvocationCallback invocation) throws Throwable {
        // 获取事务属性 TransactionDefinition对象(回顾规则,隔离级别,只读等)
        final TransactionAttribute txAttr = this.getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
        // 根据事务属性和方法,获取对应的事务管理器,(后续用于做事务的提交,回滚等操作),数据库的一些信息,
        final PlatformTransactionManager tm = this.determineTransactionManager(txAttr);
        // 获取事务方法全路径,
        final String joinpointIdentification = this.methodIdentification(method, targetClass, txAttr);
        //响应式编程事务,大多数情况下都会执行到 else中的语句;
        // CallbackPreferringPlatformTransactionManager 可以通过回掉函数来处理事务的提交和回滚操作, 此处不考虑,此处源码可以忽略
        if (txAttr != null && tm instanceof CallbackPreferringPlatformTransactionManager) {
        // 此处省略,此处为编程式事务 处理逻辑
        } else {
            //创建事务,事务属性等信息会被保存进 TransactionInfo,便于后续流程中的提交和回滚操作,详情见下文
            TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
            Object retVal = null;
            try {
                // 执行目标的方法 (执行具体的业务逻辑)
                retVal = invocation.proceedWithInvocation();
            } catch (Throwable var15) {
                //异常处理
                this.completeTransactionAfterThrowing(txInfo, var15);
                throw var15;
            } finally {
                //清除当前节点的事务消息,将旧事务节点消息通过ThreadLoacl更新到当前线程(事务的挂起操作就是在这执行)
                this.cleanupTransactionInfo(txInfo);
            }
            //提交事务
            this.commitTransactionAfterReturning(txInfo);
            return retVal;
        }
    }
    

开启事务

TransactionAspectSupport.createTransactionIfNecessary()
方法作用是检查当前是否存在事务,如果存在,则根据一定的规则创建一个新的事务。

protected TransactionAspectSupport.TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
        //如果事务名称不为空,则使用方法唯一标识。并使用 DelegatingTransactionAttribute 封装 txAttr 
        if (txAttr != null && ((TransactionAttributerollbackOn)txAttr).getName() == null) {
            txAttr = new DelegatingTransactionAttribute((TransactionAttribute)txAttr) {
                public String getName() {
                    return joinpointIdentification;
                }
            };
        }
        TransactionStatus status = null;
        if (txAttr != null) {
            if (tm != null) {
                // 获取事务状态。内部判断是否开启事务绑定线程与数据库连接。详情见下文
                status = tm.getTransaction((TransactionDefinition)txAttr);
            } else if (this.logger.isDebugEnabled()) {
                this.logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "] because no transaction manager has been configured");
            }
        }
        //构建事务消息,根据指定的属性与状态status 构建一个 TransactionInfo。将已经建立连接的事务所有信息,都记录在ThreadLocal下的TransactionInfo 实例中,包括目标方法的所有状态信息,如果事务执行失败,spring会根据TransactionInfo中的信息来进行回滚等后续操作
        return this.prepareTransactionInfo(tm, (TransactionAttribute)txAttr, joinpointIdentification, status);
    }

获取当前事务对象

AbstractPlatformTransactionManager.getTransaction()
获取当前事务对象。通过这个方法,可以获取到关于事务的详细信息,如事务的状态、相关属性等。

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
   throws TransactionException {
            
  //definition 中存储的事务的注解信息,超时时间和隔离级别等
  TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
  // 获取当前事务
  Object transaction = doGetTransaction();
  boolean debugEnabled = logger.isDebugEnabled();
  // 判断当前线程是否存在事务 
  if (isExistingTransaction(transaction)) {
   // 处理已经存在的事务
   return handleExistingTransaction(def, transaction, debugEnabled);
  }

  // 事务超时设置验证,超时时间小于-1 抛异常
  if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
   throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
  }

  // 如果当前线程不存在事务且 事务传播行为是 MANDATORY(用当前事务,如果当前没有事务,则抛出异常) 抛异常
  if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
   throw new IllegalTransactionStateException(
     "No existing transaction found for transaction marked with propagation 'mandatory'");
  }
  //以下三种事务传播行为 需要开启新事务
  else if (def.getPropagationBehavior() == TransactionDefinition.propagation_required ||
    def.getPropagationBehavior() == TransactionDefinition.propagation_requires_new ||
    def.getPropagationBehavior() == TransactionDefinition.propagation_nested) {
    //挂起原事务,因为这里不存在原事务 故设置为null。
    //当一个事务方法内部调用了另一个事务方法时,如果第二个事务方法需要独立于第一个事务方法,那么可以使用 suspend 方法来挂起当前事务,然后再开始一个新的事务
    AbstractPlatformTransactionManager.SuspendedResourcesHolder suspendedResources = this.suspend((Object)null);
            try {
                boolean newSynchronization = this.getTransactionSynchronization() != 2;
                DefaultTransactionStatus status = this.newTransactionStatus((TransactionDefinition)definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
                //开启事务
                this.doBegin(transaction, (TransactionDefinition)definition);
                //同步事务状态及书屋属性
                this.prepareSynchronization(status, (TransactionDefinition)definition);
                return status;
            } catch (RuntimeException var7) {
                this.resume((Object)null, suspendedResources);
                throw var7;
            }
  }
  else {
   boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);//0
    //创建默认状态 详情见 下文
   return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
  }
 }

执行获取事务的具体操作

AbstractPlatformTransactionManager.doGetTransaction()
方法用于执行获取事务的具体操作。它可能会根据一些条件或规则,去查找和获取当前的事务对象,并进行相应的处理。

 protected Object doGetTransaction() {
        DataSourceTransactionManager.DataSourceTransactionObject txObject = new DataSourceTransactionManager.DataSourceTransactionObject();
        //是否允许在一个事务内部开启另一个事务。
        txObject.setSavepointAllowed(this.isNestedTransactionAllowed());
        // this.dataSource数据源 配置
        //判断当前线程如果已经记录数据库连接则使用原连接
        ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource);
        //false 表示不是新创建连接
        txObject.setConnectionHolder(conHolder, false);
        return txObject;
    }
  • this.dataSource()
    是我们配置DataSourceTransactionManager时传入的。
 <bean id="valuationTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="valuationDataSource"/>
    </bean>
  • TransactionSynchronizationManager.getResource()
    方法的作用主要是获取与当前事务相关联的资源。TransactionSynchronizationManager 持有一个ThreadLocal的实例,存在一个key为dataSource ,value为ConnectionHolder 的Map信息。
//ThreadLocal 存放 ConnectionHolder 信息,ConnectionHolder 可以理解为Connection(数据库连接)的包装类,其中最主要属性为  Connection
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");

 // 获取ConnectionHolder
 public static Object getResource(Object key) {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        //获取连接信息
        Object value = doGetResource(actualKey);
        return value;
    }
 //具体执行获取连接信息操作
 private static Object doGetResource(Object actualKey) {
        //从 ThreadLoacl中获取
        Map<Object, Object> map = (Map)resources.get();
        if (map == null) {
            return null;
        } else {
            Object value = map.get(actualKey);
            if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
                map.remove(actualKey);
                if (map.isEmpty()) {
                    resources.remove();
                }
                value = null;
            }
            return value;
        }
    }

判断是否存在正在进行的事务

AbstractPlatformTransactionManager.isExistingTransaction()
方法用于判断是否存在正在进行的事务。它可以帮助我们确定当前的执行环境是否处于事务中,以便进行相应的处理。

protected boolean isExistingTransaction(Object transaction) {
        DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;
        return txObject.getConnectionHolder() != null && txObject.getConnectionHolder().isTransactionActive();
    }

挂起事务

AbstractPlatformTransactionManager.suspend()
挂起事务,对有无同步的事务采取不同方案,
doSuspend()
执行挂起具体操作。

protected final AbstractPlatformTransactionManager.SuspendedResourcesHolder suspend(Object transaction) throws TransactionException {
        //如果有同步的事务,则优先挂起同步事务
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            List suspendedSynchronizations = this.doSuspendSynchronization();
            try {
                Object suspendedResources = null;
                if (transaction != null) {
                    //执行挂起操作
                    suspendedResources = this.doSuspend(transaction);
                }
                //重置事务名称
                String name = TransactionSynchronizationManager.getCurrentTransactionName();
                TransactionSynchronizationManager.setCurrentTransactionName((String)null);
                //重置只读状态
                boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
                TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
                //重置隔离级别
                Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
                TransactionSynchronizationManager.setCurrentTransactionIsolationLevel((Integer)null);
                //重置事务激活状态
                boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
                TransactionSynchronizationManager.setActualTransactionActive(false);
                //返回挂起的事务
                return new AbstractPlatformTransactionManager.SuspendedResourcesHolder(suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
            } catch (RuntimeException var8) {
                this.doResumeSynchronization(suspendedSynchronizations);
                throw var8;
            } 
        } else if (transaction != null) {
            Object suspendedResources = this.doSuspend(transaction);
            return new AbstractPlatformTransactionManager.SuspendedResourcesHolder(suspendedResources);
        } else {
            return null;
        }
    }
  • AbstractPlatformTransactionManager.doSuspend()
    执行挂起操作只是将当前ConnectionHolder设置为null,返回原有事务消息,方便后续恢复原有事务消息,并将当前正在进行的事务信息进行重置。
protected Object doSuspend(Object transaction) {
        DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;
        txObject.setConnectionHolder((ConnectionHolder)null);
        //接触绑定 
        return TransactionSynchronizationManager.unbindResource(this.dataSource);
    }

//解除绑定操作,将现有的事务消息remove并返回上一级
 public static Object unbindResource(Object key) throws IllegalStateException {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        //解绑操作,移除资源
        Object value = doUnbindResource(actualKey);
        if (value == null) {
            throw new IllegalStateException("No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
        } else {
            return value;
        }
    }
  • AbstractPlatformTransactionManager.doBegin()
    数据库连接获取,当新事务时,则获取新的数据库连接,并为其设置隔离级别,是否只读等属性。
protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;
        Connection con = null;

        try {
            //新事务开启时将 ConnectionHolder 设置为null
            if (txObject.getConnectionHolder() == null || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                //获取新的数据库连接
                Connection newCon = this.dataSource.getConnection();
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
            }
            txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
            con = txObject.getConnectionHolder().getConnection();
            //设置事务隔离级别 和readOnly属性
            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
            txObject.setPreviousIsolationLevel(previousIsolationLevel);
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                // 交给Spring控制事务提交
                con.setAutoCommit(false);
            }
            this.prepareTransactionalConnection(con, definition);
            //设置当前线程的事务激活状态
            txObject.getConnectionHolder().setTransactionActive(true);
            int timeout = this.determineTimeout(definition);
            if (timeout != -1) {
                // 设置超时时间
                txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
            }
            if (txObject.isNewConnectionHolder()) {
                TransactionSynchronizationManager.bindResource(this.getDataSource(), txObject.getConnectionHolder());
            }

        } catch (Throwable var7) {
            if (txObject.isNewConnectionHolder()) {
                DataSourceUtils.releaseConnection(con, this.dataSource);
                txObject.setConnectionHolder((ConnectionHolder)null, false);
            }

            throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", var7);
        }
    }
  • AbstractPlatformTransactionManager.prepareTransactionStatus()
    创建默认Status,如果不需要开始事务 (比如SUPPORTS),则返回一个默认的状态。
protected final DefaultTransactionStatus prepareTransactionStatus(TransactionDefinition definition, Object transaction, boolean newTransaction, boolean newSynchronization, boolean debug, Object suspendedResources) {
        DefaultTransactionStatus status = this.newTransactionStatus(definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
        this.prepareSynchronization(status, definition);
        return status;
    }
    
protected DefaultTransactionStatus newTransactionStatus(TransactionDefinition definition, Object transaction, boolean newTransaction, boolean newSynchronization, boolean debug, Object suspendedResources) {
        boolean actualNewSynchronization = newSynchronization && !TransactionSynchronizationManager.isSynchronizationActive();
        //创建 DefaultTransactionStatus 对象
        return new DefaultTransactionStatus(transaction, newTransaction, actualNewSynchronization, definition.isReadOnly(), debug, suspendedResources);
    }
  • AbstractPlatformTransactionManager.handleExistingTransaction()
    针对不同的传播行为做不同的处理方法,比如挂起原事务开启新事务等等。
 private TransactionStatus handleExistingTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException {
        //当传播行为是 NEVER 时抛出异常
        if (definition.getPropagationBehavior() == 5) {
            throw new IllegalTransactionStateException("Existing transaction found for transaction marked with propagation 'never'");
        } else {
            AbstractPlatformTransactionManager.SuspendedResourcesHolder suspendedResources;
            boolean newSynchronization;
            //当传播方式为NOT_SUPPORTED 时挂起当前事务,然后在无事务的状态下运行
            if (definition.getPropagationBehavior() == 4) {
                //挂起事务
                suspendedResources = this.suspend(transaction);
                newSynchronization = this.getTransactionSynchronization() == 0;
                //返回默认status
                return this.prepareTransactionStatus(definition, (Object)null, false, newSynchronization, debugEnabled, suspendedResources);
                //当传播方式为REQUIRES_NEW时,挂起当前事务,然后启动新事务
            } else if (definition.getPropagationBehavior() == 3) {
                //挂起原事务
                suspendedResources = this.suspend(transaction);
                try {
                    newSynchronization = this.getTransactionSynchronization() != 2;
                    DefaultTransactionStatus status = this.newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
                    //启动新的事务
                    this.doBegin(transaction, definition);
                    this.prepareSynchronization(status, definition);
                    return status;
                } catch (Error|RuntimeException var7) {
                    this.resumeAfterBeginException(transaction, suspendedResources, var7);
                    throw var7;
                } 
            } else {
                boolean newSynchronization;
                //当传播方式为NESTED时,设置事务的保存点
                //存在事务,将该事务标注保存点,形成嵌套事务
                //嵌套事务中的子事务出现异常不会影响到父事务保存点之前的操作
                if (definition.getPropagationBehavior() == 6) {
                    if (!this.isNestedTransactionAllowed()) {
                        throw new NestedTransactionNotSupportedException("Transaction manager does not allow nested transactions by default - specify 'nestedTransactionAllowed' property with value 'true'");
                    } else {
                        if (this.useSavepointForNestedTransaction()) {
                            DefaultTransactionStatus status = this.prepareTransactionStatus(definition, transaction, false, false, debugEnabled, (Object)null);
                            //创建保存点,回滚时,只回滚到该保存点
                            status.createAndHoldSavepoint();
                            return status;
                        } else {
                            newSynchronization = this.getTransactionSynchronization() != 2;
                            DefaultTransactionStatus status = this.newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, (Object)null);
                            //如果不支持保存点,就启动新的事务
                            this.doBegin(transaction, definition);
                            this.prepareSynchronization(status, definition);
                            return status;
                        }
                    }
                } else {
                    newSynchronization = this.getTransactionSynchronization() != 2;
                    return this.prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, (Object)null);
                }
            }
        }
    }

回滚事务

TransactionAspectSupport.completeTransactionAfterThrowing()
判断事务是否存在,如不存在就不需要回滚,如果存在则在判断是否满足回滚条件。

protected void completeTransactionAfterThrowing(TransactionAspectSupport.TransactionInfo txInfo, Throwable ex) {
        //判断是否存在事务
        if (txInfo != null && txInfo.hasTransaction()) {
            // 判断是否满足回滚条件。抛出的异常类型,和定义的回滚规则进行匹配
            if (txInfo.transactionAttribute.rollbackOn(ex)) {
                try {
                    // 回滚处理
                    txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
                } 
            //省略代码
            } else {
                try {
                    //不满足回滚条件 出现异常 
                    txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
                } 
            //省略代码
            }
        }

    }

AbstractPlatformTransactionManager.rollback()
当在事务执行过程中出现异常或其他需要回滚的情况时,就会调用这个方法,将事务进行回滚操作,撤销之前所做的数据库操作,以保证数据的一致性。

 public final void rollback(TransactionStatus status) throws TransactionException {
        //判断事务是否已经完成,回滚时抛出异常
        if (status.isCompleted()) {
            throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");
        } else {
            DefaultTransactionStatus defStatus = (DefaultTransactionStatus)status;
            // 执行回滚操作。
            this.processRollback(defStatus);
        }
    }

AbstractPlatformTransactionManager.processRollback()
方法主要用于处理事务的回滚操作。通过这个方法,可以确保事务在需要回滚时能够正确地执行回滚操作,保持数据的完整性。

private void processRollback(DefaultTransactionStatus status) {
        try {
            try {
                //解绑线程和会话绑定关系
                this.triggerBeforeCompletion(status);
                if (status.hasSavepoint()) {
                    //如果有保存点(当前事务为单独的线程则会退到保存点)
                    status.rollbackToHeldSavepoint();
                } else if (status.isNewTransaction()) {
                    //如果是新事务直接回滚。调用数据库连接并调用rollback方法进行回滚。使用底层数据库连接提供的API
                    this.doRollback(status);
                } else if (status.hasTransaction()) {
                    if (status.isLocalRollbackOnly() || !this.isGlobalRollbackOnParticipationFailure()) {
                        //如果当前事务不是独立的事务,则只能等待事务链执行完成后再做回滚操作
                        this.doSetRollbackOnly(status);
                    } 
                } 
            } 
            //catch 等代码
            // 关闭会话,重置属性
            this.triggerAfterCompletion(status, 1);
        } finally {
            //清理并恢复挂起的事务
            this.cleanupAfterCompletion(status);
        }

    }

提交事务

TransactionAspectSupport.commitTransactionAfterReturning()
基本上和回滚一样,都是先判断是否有事务,在操作提交。

  protected void commitTransactionAfterReturning(TransactionAspectSupport.TransactionInfo txInfo) {
        if (txInfo != null && txInfo.hasTransaction()) {
            //提交事务
            txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
        }
    }

AbstractPlatformTransactionManager.commit()
创建默认Status prepareTransactionStatu,发现是否有回滚标记,然后进行回滚。如果判断无需回滚就可以直接提交。

public final void commit(TransactionStatus status) throws TransactionException {
        // 事务状态已完成则抛异常
        if (status.isCompleted()) {
            throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");
        } else {
            DefaultTransactionStatus defStatus = (DefaultTransactionStatus)status;
            //发现回滚标记
            if (defStatus.isLocalRollbackOnly()) {
                //回滚
                this.processRollback(defStatus);
            } else if (!this.shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
                //回滚
                this.processRollback(defStatus);
                if (status.isNewTransaction() || this.isFailEarlyOnGlobalRollbackOnly()) {
                    throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");
                }
            } else {
                // 提交操作
                this.processCommit(defStatus);
            }
        }
    }

AbstractPlatformTransactionManager.processCommit()
处理事务的提交操作

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
        try {
            boolean beforeCompletionInvoked = false;

            try {
                this.prepareForCommit(status);
                this.triggerBeforeCommit(status);
                this.triggerBeforeCompletion(status);
                beforeCompletionInvoked = true;
                boolean globalRollbackOnly = false;
                if (status.isNewTransaction() || this.isFailEarlyOnGlobalRollbackOnly()) {
                    globalRollbackOnly = status.isGlobalRollbackOnly();
                }
                if (status.hasSavepoint()) {
                    //释放保存点信息
                    status.releaseHeldSavepoint();
                } else if (status.isNewTransaction()) {
                    // 是一个新的事务 则提交。 获取数据库连接后使用数据库API进行提交事务
                    this.doCommit(status);
                }

                if (globalRollbackOnly) {
                    throw new UnexpectedRollbackException("Transaction silently rolled back because it has been marked as rollback-only");
                }
            } catch (TransactionException var20) {
                if (this.isRollbackOnCommitFailure()) {
                    //提交异常回滚
                    this.doRollbackOnCommitException(status, var20);
                } else {
                    this.triggerAfterCompletion(status, 2);
                }

                throw var20;
            } 
            //省略其它异常拦截
            try {
                this.triggerAfterCommit(status);
            } finally {
                this.triggerAfterCompletion(status, 0);
            }
        } finally {
            // 清理事务消息
            this.cleanupAfterCompletion(status);
        }

    }

清除事务信息

AbstractPlatformTransactionManager.cleanupAfterCompletion()
这个方法主要用于在事务完成后进行清理工作。它会负责释放资源、清理临时数据等,以确保系统处于良好的状态。

 private void cleanupAfterCompletion(DefaultTransactionStatus status) {
        //将当前事务设置为完成状态
        status.setCompleted();
        if (status.isNewSynchronization()) {
            // 清空当前事务消息
            TransactionSynchronizationManager.clear();
        }

        if (status.isNewTransaction()) {
            //如果是新事务 则在事务完成之后做清理操作
            this.doCleanupAfterCompletion(status.getTransaction());
        }
        if (status.getSuspendedResources() != null) {
            // 将原事务从挂起状态恢复
            this.resume(status.getTransaction(), (AbstractPlatformTransactionManager.SuspendedResourcesHolder)status.getSuspendedResources());
        }
    }

AbstractPlatformTransactionManager.doCleanupAfterCompletion()
在新事务完成后会调用resetConnectionAfterTransaction方法重置数据库连接信息,并判断如果是新的数据库连接则将其放回连接池。

protected void doCleanupAfterCompletion(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    if (txObject.isNewConnectionHolder()) {
        // 将数据库连接从当前线程中解除绑定
        TransactionSynchronizationManager.unbindResource(this.dataSource);
    }
    Connection con = txObject.getConnectionHolder().getConnection();
    try {
        // 恢复数据库连接的autoCommit状态
        if (txObject.isMustRestoreAutoCommit()) {
            con.setAutoCommit(true); 
        }
        // 负责重置数据库连接信息,包括隔离级别、readOnly属性等
        DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
    }
    catch (Throwable ex) {
        logger.debug("Could not reset JDBC Connection after transaction", ex);
    }
    if (txObject.isNewConnectionHolder()) {
        // 如果是新的数据库连接则将数据库连接放回连接池
        DataSourceUtils.releaseConnection(con, this.dataSource);
    }
    txObject.getConnectionHolder().clear();
}

AbstractPlatformTransactionManager.resume()
如果事务执行前有事务挂起,那么当前事务执行结束后需要将挂起的事务恢复,挂起事务时保存了原事务信息,重置了当前事务信息,所以恢复操作就是将当前的事务信息设置为之前保存的原事务信息。

 protected final void resume(Object transaction, AbstractPlatformTransactionManager.SuspendedResourcesHolder resourcesHolder) throws TransactionException {
        if (resourcesHolder != null) {
            Object suspendedResources = resourcesHolder.suspendedResources;
            if (suspendedResources != null) {
                // 执行 恢复挂起事务 ,绑定资源bindResource
                this.doResume(transaction, suspendedResources);
            }
            List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
            if (suspendedSynchronizations != null) {
                TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);
                TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
                TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
                TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);
                this.doResumeSynchronization(suspendedSynchronizations);
            }
        }
    }

TransactionAspectSupport.cleanupTransactionInfo()
清除当前节点的事务消息,将旧事务节点信息通过thradLoacl更新到当前线程。

protected void cleanupTransactionInfo(TransactionAspectSupport.TransactionInfo txInfo) {
        if (txInfo != null) {
            //从当前线程的 ThreadLocal 获取上层的事务信息,将当前事务出栈,继续执行上层事务
            txInfo.restoreThreadLocalStatus();
        }
    }

 private void restoreThreadLocalStatus() {
            //当前事务处理完之后,恢复上层事务上下文 
            TransactionAspectSupport.transactionInfoHolder.set(this.oldTransactionInfo);
        }

小结

如果方法正常执行完成且没有异常,调用
commitTransactionAfterReturning()
方法。如果执行中出现异常,调用
completeTransactionAfterThrowing()
方法。

两个方法内部都会判断是否存在事务以及是否满足回滚条件来决定最终执行提交操作还是回滚操作。

面试题专栏

Java面试题专栏
已上线,欢迎访问。

  • 如果你不知道简历怎么写,简历项目不知道怎么包装;
  • 如果简历中有些内容你不知道该不该写上去;
  • 如果有些综合性问题你不知道怎么答;

那么可以私信我,我会尽我所能帮助你。

SQLite数据库

HarmonyOS的关系型数据库基于SQLite
导入模块

import { relationalStore } from '@kit.ArkData';

实现步骤:

  1. 获取RdbStore对象,用于创建数据库,数据表,以及数据库升级等操作
let storeConfig = {
  name: 'Poetry.db',  //数据库文件名
  securityLevel: relationalStore.SecurityLevel.S1,  //安全级别
  encrypt: false, //是否加密,可选,默认不加密
  customDir: '', 	//自定义路径,可选,目录:context.databaseDir + '/rdb/' + customDir
  isReadOnly: false, //是否已只读方式打开,可选,默认false
}

relationalStore.getRdbStore(this.context, this.storeConfig)
  .then(store => {
    //创建表
    store.executeSql('sql')
    //判断版本
    store.version
  })
  .catch((err: Error) => {
    
  })
  1. 插入数据
let data :ValuesBucket={
  name:"zhangsan",
  age: 23,
}
store.insert("tableName",data).then((rowId)=>{
  //操作成功返回rowId,否则返回-1
})

store.batchInsert() //用于插入批量数据
  1. 修改,删除数据:通过组件提供的谓词(Predicates)修改或删除组件
let data :ValuesBucket={
  name:"zhangsan",
  age: 26,
}
let predicates = new relationalStore.RdbPredicates("tableName")
predicates.equalTo("name","zhangsan")
//更新数据
store.update(data,predicates).then((value)=>{

})

//删除数据
store.delete(predicates).then((value)=>{

})
  1. 查询数据
let predicates = new relationalStore.RdbPredicates("tableName")
predicates.equalTo("name","zhangsan")
store.query(predicates).then((resultSet)=>{
  while (resultSet.goToNextRow()){
    const name = resultSet.getString(resultSet.getColumnIndex("name"))
    const age = resultSet.getLong(resultSet.getColumnIndex("age"))
  }
  resultSet.close()
})
//也可以通过下面接口使用sql查询
store.querySql(sql: string, bindArgs?: Array<ValueType>): Promise<ResultSet>;
  1. 备份数据和恢复数据
//备份数据
store.backup("backup.db")
//恢复数据
store.restore("backup.db")

SmartDB

SmartDB与Android中的room组件类似,可以简化我们数据库操作的步骤,使代码更易维护。
安装和导入模块

//安装模块
ohpm install @liushengyi/smartdb
//导入模块
import sql from "@liushengyi/smartdb"

定义数据结构:

export class Poetry {
  @sql.SqlColumn(sql.ColumnType.TEXT)
  uuid?: string
  @sql.SqlColumn(sql.ColumnType.TEXT)
  title?: string
  @sql.SqlColumn(sql.ColumnType.TEXT)
  dynasty?: string
  @sql.SqlColumn(sql.ColumnType.TEXT)
  author?: string
  @sql.SqlColumn(sql.ColumnType.TEXT)
  introduction?: string
  @sql.SqlColumn(sql.ColumnType.TEXT)
  text?: string
  @sql.SqlColumn(sql.ColumnType.TEXT)
  textAlign?: string
  @sql.SqlColumn(sql.ColumnType.TEXT)
  translation?: string
  @sql.SqlColumn(sql.ColumnType.TEXT)
  rectify?: string
  @sql.SqlColumn(sql.ColumnType.TEXT)
  searchKey?: string
}

执行数据库操作:进行增删改查、事务操作

export class PoetryDao {
  public static TABLE_NAME = "Poetry"
  public static SQL_CREATE_TABLE =
    "CREATE TABLE IF NOT EXISTS `Poetry` (`uuid` TEXT NOT NULL, `title` TEXT, `dynasty` TEXT, `author` TEXT, `introduction` TEXT, `text` TEXT, `textAlign` TEXT, `translation` TEXT, `rectify` TEXT, `searchKey` TEXT, PRIMARY KEY(`uuid`))"

  @sql.SqlQuery(`select * from ${PoetryDao.TABLE_NAME} where uuid=#{uuid}`)
  @sql.ReturnType(Poetry)
  queryOne(@sql.Param('uuid') uuid: string): Promise<Poetry> {
    return sql.PromiseNull()
  }

  @sql.SqlQuery(`select count(*) from ${PoetryDao.TABLE_NAME} `)
  @sql.ReturnType(Number)
  queryCount(): Promise<Number> {
    return sql.PromiseNull()
  }

  @sql.SqlInsert(`insert into ${PoetryDao.TABLE_NAME} values (#{data.uuid},#{data.title},#{data.dynasty},#{data.author},#{data.introduction},#{data.text},#{data.textAlign},#{data.translation},#{data.rectify},#{data.searchKey})`)
  insert(@sql.Param('data') data: Poetry): Promise<void> {
    return sql.PromiseNull()
  }
  
  @sql.Transactional()
  async insertPoetryAll(list: Poetry[]) {
    for (let item of list) {
      await this.insert(item)
    }
  }
}

数据库管理:创建数据库、数据库升级

export class DatabaseManager {
  static readonly DATABASE_VERSION = 1
  static readonly DATABASE_NAME = 'poetry.db'

  static init(context: Context) {
    sql.dbHelper.initDb(context,
      DatabaseManager.DATABASE_NAME,
      DatabaseManager.DATABASE_VERSION,
      new DbOpenHelperImpl()
    )
  }
}

class DbOpenHelperImpl extends sql.DbOpenHelper {
  //创建数据库
  onCreate(db: relationalStore.RdbStore): void {
    db.executeSql(PoetryDao.SQL_CREATE_TABLE)
  }

  //升级数据
  onUpgrade(db: relationalStore.RdbStore, oldVersion: number, newVersion: number): void {

  }
}

最后在app启动的时候调用初始化方法

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
    DatabaseManager.init(this.context)
  }
}

数据初始化和使用

将json格式的数据插入到数据库中

poetryDao: PoetryDao = new PoetryDao()

new Promise<boolean>(async (resolve, reject) => {
  try {
    let count = await this.poetryDao.queryCount()
    if (!count) {
      let list = await (await import("resources/rawfile/poetry.json")).default
      let poetryArray: Array<Poetry> = []
      for (let item of list) {
        let poetry = item as Poetry
        poetry.uuid = util.generateRandomUUID()
        if (poetry.rectify) {
          poetry.rectify = JSON.stringify(poetry.rectify)
        }
        poetryArray.push(poetry)
      }
      this.poetryDao.insertPoetryAll(poetryArray)
      resolve(true)
    } else {
      resolve(false)
    }
  } catch (e) {
    reject(e)
  }
})

读取数据

//读取所有数据
new PoetryDao().queryList()
  .then((value) => {

  })
//读取一条数据
new PoetryDao().queryOne("id")
  .then((value) => {

  })


本文的技术设计和实现都是基于作者工作中的经验总结,如有错误,请留言指正,谢谢。

之前介绍了如何在
Streamlit App
中使用
Matplotlib
库来绘图。

本篇介绍
Steamlit
结合
Poltly
的方法,相比于
Matplotlib

Poltly
的交互性更强,

更适合在
Web
应用中做为可视化的工具。

1. st.plotly_chart函数

st.plotly_chart
函数专门用于在
Steamlit
应用中显示
Plotly
绘制的图形。

这个函数能够直接将
Plotly Figure对象
或者
Poltly支持的数据对象
直接渲染到页面的指定位置上。

st.plotly_chart
的参数不多,与
st.pyplot
比,多了一些交互用的参数:

名称 类型 说明
figure_or_data Figure或Data对象
theme str 指定图表的主题
use_container_width bool 决定是否使用父容器的宽度覆盖图形的原始宽度
key str 为元素提供标识
on_select str 控制图表如何响应用户选择事件
selection_mode str 图表的选择模式

因为
Plotly
绘制的图形可交互,通过
key
参数,在交互的过程中,我们可以精确地定位到交互的图表。

on_select
参数有以下几种取值:

  1. ignore
    :不对图表中的任何选择事件做出反应
  2. rerun
    :在图表中选择数据时,会重新运行应用程序
  3. 可调用对象****
    :会重新运行应用程序,并在应用程序的其余部分之前执行该可调用对象作为回调函数

selection_mode
参数定义图表的选择模式,包括:

  1. points
    :允许基于单个数据点进行选择
  2. box
    :允许基于矩形区域进行选择
  3. lasso
    :允许基于自由绘制区域进行选择

on_select
不同时,页面的效果如下:

1.1. on_select=ignore

ignore

on_select
的默认值,此时
Plotly
图形上无法选择对象。

import streamlit as st
import plotly.express as px

df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length")


st.plotly_chart(fig, key="iris")
# 或者
# st.plotly_chart(fig, key="iris", on_select="ignore")

此时,工具栏上没有选择数据的小工具。

1.2. on_select=rerun

此时,
st.plotly_chart
会将选择的数据点返回。

选择数据点时,可以切换成
矩形选择

自由区域
选择。

event = st.plotly_chart(fig, key="iris", on_select="rerun")
event

1.3. on_select=callable

on_select=callable
的效果
on_select=rerun
差不多,也能对数据点选择并得到选择的数据点。

不同之处在于,可以在选择数据点之后,调用
callable
函数进行额外的处理。

def handle_selection():
    from datetime import datetime

    st.write(f"Selected data at {datetime.now()}")


event = st.plotly_chart(fig, key="iris", on_select=handle_selection)
event

每次选择数据之后,上面的时间都会变化,说明
handle_selection
函数在每次选择数据之后都被回调。

2. 使用示例

下面通过示例演示实际场景中如何使用
streamlit

Poltly
图表。

2.1. 销售数据时间序列分析

在这个示例中,首先创建了一个模拟的销售数据时间序列,然后通过st.plotly_chart展示图表,并设置
on_select
回调函数来处理用户在图表上的选择操作。

当用户选择图上的点时,会在
Streamlit
应用中显示所选数据点对应的日期和销售额信息。

import streamlit as st
import plotly.express as px
import pandas as pd

# 模拟销售数据
data = {
    "Date": pd.date_range(start="2024-01-01", periods=100),
    "Sales": [i**2 + 50 + 10 * (i % 10) for i in range(100)],
}
df = pd.DataFrame(data)

# 创建时间序列折线图
fig = px.scatter(df, x="Date", y="Sales")


# 显示图表并处理选择事件
def handle_selection():
    selected_points = st.session_state["sales_chart"].selection.points
    st.write("已选择的数据点:")
    df = pd.DataFrame(columns=["日期", "销售额"])

    for idx, p in enumerate(selected_points):
        df.loc[idx, "日期"] = p["x"]
        df.loc[idx, "销售额"] = p["y"]

    st.dataframe(df)


st.plotly_chart(fig, key="sales_chart", on_select=handle_selection)

2.2. 模拟股票分析

使用
generate_stock_data
函数生成模拟的股票数据,再使用
plotly.graph_objects
创建一个烛台图,将模拟数据绘制到图表中。

编写一个回调函数,当用户在图表上选择某个点时,它会获取所选点的详细信息并在
Streamlit
应用中展示出来。

import streamlit as st
import plotly.graph_objects as go
import pandas as pd
import numpy as np


# 生成随机模拟的股票数据
def generate_stock_data(days=300):
    dates = pd.date_range(start="2024-01-01", periods=days)
    open_prices = np.random.rand(days) * 100 + 50
    high_prices = open_prices + np.random.rand(days) * 10
    low_prices = open_prices - np.random.rand(days) * 10
    close_prices = open_prices + np.random.randn(days) * 5

    data = {
        "Date": dates,
        "Open": open_prices,
        "High": high_prices,
        "Low": low_prices,
        "Close": close_prices,
    }
    return pd.DataFrame(data)


# 生成模拟股票数据
df = generate_stock_data()

# 创建交互式图表
fig = go.Figure(
    data=[
        go.Candlestick(
            x=df["Date"],
            open=df["Open"],
            high=df["High"],
            low=df["Low"],
            close=df["Close"],
        )
    ]
)

fig.update_layout(title="模拟股票价格", xaxis_title="Date", yaxis_title="Price")


# onselect 回调函数
def handle_selection():
    selected_points = st.session_state.stock_chart.selection.points
    st.write("Selected Stock Data Points:")

    df = pd.DataFrame(columns=["日期", "开盘价", "收盘价", "最高价", "最低价"])

    for idx, p in enumerate(selected_points):
        print(idx, p)
        df.loc[idx, "日期"] = p["x"]
        df.loc[idx, "开盘价"] = p["open"]
        df.loc[idx, "收盘价"] = p["close"]
        df.loc[idx, "最高价"] = p["high"]
        df.loc[idx, "最低价"] = p["low"]

    st.dataframe(df)


# 显示图表
st.plotly_chart(fig, key="stock_chart", on_select=handle_selection)

3. 总结

Streamlit
可以简化
Web
应用构建流程,
Plotly
提供丰富图表类型,二者结合能快速将数据转化为交互式可视化应用,节省开发时间。

此外,
Plotly
图表交互性高,在
Streamlit
应用中可实现数据探索、筛选等操作,增强用户体验。