Spring事务详述

本文深入解析Spring事务管理,涵盖事务的基本概念、核心流程、隔离级别及传播行为,阐述原子性、一致性、隔离性和持久性等特性,详细介绍不同隔离级别的优劣及应用场景,对比7种传播行为的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、先来说一说事务是什么,在Spring中事务指的是对数据库进行增删改查的一系列过程,它具有以下几种特性:

  • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  • 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
  • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
  • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

2、 Spring事务的核心流程图如下:

 

è¿éåå¾çæè¿°

 3、Spring隔离级别

在Spring中,多个事务并发执行,隔离级别的话,主要是分为5种,分别有着不同的代表,且看下表:

ISOLATION_DEFAULTPlatformTranscationManager种默认的隔离级别,代表使用数据库的默认事务隔离级别。
ISOLATION_READ_UNCOMMITTED最低的隔离级别,一个事务未提交前的结果可以被其他事务获取,可能会造成脏读、不可重复度、幻读的问题。
ISOLATION_READ_COMMITTED一个事务再提交前不可以被其他事务获取结果,不会造成脏读,可能会造成不可重复读、幻读等问题。
ISOLATION_REPEATABLE_READ可以防止脏读、不可重复读问题,但不会解决幻读问题。
ISOLATION_SERIALIZABLE花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。

下面来介绍一下上边的名词:

  1. ( Dirty reads)脏读:指的是事务A读取到事务B的数据后,事务B可能会因为一些原因失败而进行回滚,导致事务A获取的值是不正确的值。
  2. (non-repeatable reads)不可重复读:比如事务A中有两次读取某一全局变量total的值,第一次读取为100,然后事务B对 total的值改成了200,事务A第二次读取total的值为200,造成事务A的数据混乱。
  3. (phantom reads)幻读数据:幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。

4、Spring事务的7种传播行为:

 事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:

PROPAGATION_REQUIRED如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
PROPAGATION_SUPPORTS如果存在一个事务,则支持当前事务。如果没有事务则以非事务的形式执行。
PROPAGATION_MANDATORY如果存在一个事务,则支持当前事务。如果没有一个活动的事务,则抛出异常。
PROPAGATION_REQUIRED_NEW总是开启新的事务。如果已有事务,则挂起已存在的事务。
PROPAGATION_NOT_SUPPORTED总是非事务的执行,并挂起当前所有的事务。
PROPAGATION_NEVER总是非事务的执行,如果存在事务则抛出异常。
PROPAGATION_NESTED表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务

下边来对其中几种进行详细叙述:

1、PROPAGATION_REQUIRED,如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

public class Test{
    //PROPAGATION_REQUIRED
    public void fun1(){
        ...
        fun2();
    }
    //PROPAGATION_REQUIRED
    public void fun2(){
        ...
    }
}

单独执行fun2相当于如下:

...
main{
    Connection c = null;
    try{
        c.getConnection();
        con.setAutoCommit(false); 
        fun2();
        c.commit();
    }catch(RuntimeException e){
        c.rollback();
    }finally{
        c.close();
    }
    
    
}
...

由于当前没有事务存在,故会创建一个新的事务去执行fun2();

执行fun1时,也会调用fun2,因此fun2方法会加入到fun1的事务中去执行:

...
main{
    Connection c = null;
    try{
        c.getConnection();
        con.setAutoCommit(false); 
        fun1();
        c.commit();
    }catch(RuntimeException e){
        c.rollback();
    }finally{
        c.close();
    }
    
    
}
...

 2、PROPAGATION_SUPPORTS,如果存在一个事务,则支持当前事务。如果没有事务则以非事务的形式执行。

public class Test{
    //PROPAGATION_REQUIRED
    public void fun1(){
        ...
        fun2();
    }
    //PROPAGATION_SUPPORTS
    public void fun2(){
        ...
    }
}

单独调用fun2时,如果当前没有事务可用也不会开启事务;如果有可用事务,fun2会加入到调用他的事务中去,就比如fun1执行时会调用fun2,因此此时fun2加入了fun1的事务中。

3、PROPAGATION_MANDATORY,如果存在一个事务,则支持当前事务。如果没有一个活动的事务,则抛出异常。

public class Test{
    //PROPAGATION_REQUIRED
    public void fun1(){
        ...
        fun2();
    }
    //PROPAGATION_MANDATORY
    public void fun2(){
        ...
    }
}

单独执行fun2方法会抛出异常,但是通过fun1去调用fun2,则不会抛出异常。

4、 PROPAGATION_REQUIRED_NEW,总是开启新的事务。如果已有事务,则挂起已存在的事务。需要使用JtaTransactionManager作为事务管理器。

public class Test{
    //PROPAGATION_REQUIRED
    public void fun1(){
        System.out.println("AAA_Begin");
        fun2();
        System.out.println("AAA_End");
    }
    //PROPAGATION_REQUIRED_NEW
    public void fun2(){
        ...
        System.out.println("BBB");
    }
}

调用方法fun1时,相当于如下:

...
main{
    TransactionManager tm = null;
    try{
       tm = getTranscationManager();
       tm.begin();
       Transaction ts1 = tm.getTransaction(); 
       System.out.println("AAA_Begin");
       tm.suspend();
       try{
           tm.begin();
           TransactionManager ts2 = tm.getTranscationManager();
           fun2();
           ts2.commit();
       }catch(RuntimeException e){
           ts2.rollback();
       }finally{
           //释放资源
       }
    tm.resume(ts1); 
    System.out.println("AAA_End");
    ts1.commit();
    }catch(RuntimeException e){
        tm.rollback();
    }finally{
        //释放资源
    }
    
    
}
...

在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了methodB之外的其它代码导致的结果却被回滚了。使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。 

5、PROPAGATION_NESTED,表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务。

public class Test{
    //PROPAGATION_REQUIRED
    public void fun1(){
        ...
        System.out.println("AAA");
        fun2();
    }
    //PROPAGATION_NESTED
    public void fun2(){
        ...
        System.out.println("BBB");
    }
}

单独执行fun2则会开启新的事务,去支持fun2。

调用fun1方法,相当于如下:

...
main{
    Connection c = null;
    try{
        c.getConnection();
        con.setAutoCommit(false); 
        fun1();
        savepoint = c.setSavepoint();
        try{
            fun2();
        }catch(RuntimeException e){
            c.rollback(savepoint);
        }
        c.commit();
    }catch(RuntimeException e){
        c.rollback();
    }finally{
        c.close();
    }
    
    
}
...

 这里可以看到,在执行fun2之前,会使用savepoint去保留当前的执行状态,如果fun2执行失败,则回滚到savepoint的状态,而且fun2并没有执行commit方法,如果后续代码执行失败,则会回滚到起始状态。由此可见,外部事务回滚会影响到内层事务,而内层事务的回滚不会影响到外层事务。

参考自:https://www.cnblogs.com/csniper/p/5536633.htmlhttps://www.cnblogs.com/yixianyixian/p/8372832.html

资源下载链接为: https://pan.quark.cn/s/1bfadf00ae14 “STC单片机电压测量”是一个以STC系列单片机为基础的电压检测应用案例,它涵盖了硬件电路设计、软件编程以及数据处理等核心知识点。STC单片机凭借其低功耗、高性价比和丰富的I/O接口,在电子工程领域得到了广泛应用。 STC是Specialized Technology Corporation的缩写,该公司的单片机基于8051内核,具备内部振荡器、高速运算能力、ISP(在系统编程)和IAP(在应用编程)功能,非常适合用于各种嵌入式控制系统。 在源代码方面,“浅雪”风格的代码通常简洁易懂,非常适合初学者学习。其中,“main.c”文件是程序的入口,包含了电压测量的核心逻辑;“STARTUP.A51”是启动代码,负责初始化单片机的硬件环境;“电压测量_uvopt.bak”和“电压测量_uvproj.bak”可能是Keil编译器的配置文件备份,用于设置编译选项和项目配置。 对于3S锂电池电压测量,3S锂电池由三节锂离子电池串联而成,标称电压为11.1V。测量时需要考虑电池的串联特性,通过分压电路将高电压转换为单片机可接受的范围,并实时监控,防止过充或过放,以确保电池的安全和寿命。 在电压测量电路设计中,“电压测量.lnp”文件可能包含电路布局信息,而“.hex”文件是编译后的机器码,用于烧录到单片机中。电路中通常会使用ADC(模拟数字转换器)将模拟电压信号转换为数字信号供单片机处理。 在软件编程方面,“StringData.h”文件可能包含程序中使用的字符串常量和数据结构定义。处理电压数据时,可能涉及浮点数运算,需要了解STC单片机对浮点数的支持情况,以及如何高效地存储和显示电压值。 用户界面方面,“电压测量.uvgui.kidd”可能是用户界面的配置文件,用于显示测量结果。在嵌入式系统中,用
资源下载链接为: https://pan.quark.cn/s/abbae039bf2a 在 Android 开发中,Fragment 是界面的一个模块化组件,可用于在 Activity 中灵活地添加、删除或替换。将 ListView 集成到 Fragment 中,能够实现数据的动态加载与列表形式展示,对于构建复杂且交互丰富的界面非常有帮助。本文将详细介绍如何在 Fragment 中使用 ListView。 首先,需要在 Fragment 的布局文件中添加 ListView 的 XML 定义。一个基本的 ListView 元素代码如下: 接着,创建适配器来填充 ListView 的数据。通常会使用 BaseAdapter 的子类,如 ArrayAdapter 或自定义适配器。例如,创建一个简单的 MyListAdapter,继承自 ArrayAdapter,并在构造函数中传入数据集: 在 Fragment 的 onCreateView 或 onActivityCreated 方法中,实例化 ListView 和适配器,并将适配器设置到 ListView 上: 为了提升用户体验,可以为 ListView 设置点击事件监听器: 性能优化也是关键。设置 ListView 的 android:cacheColorHint 属性可提升滚动流畅度。在 getView 方法中复用 convertView,可减少视图创建,提升性能。对于复杂需求,如异步加载数据,可使用 LoaderManager 和 CursorLoader,这能更好地管理数据加载,避免内存泄漏,支持数据变更时自动刷新。 总结来说,Fragment 中的 ListView 使用涉及布局设计、适配器创建与定制、数据绑定及事件监听。掌握这些步骤,可构建功能强大的应用。实际开发中,还需优化 ListView 性能,确保应用流畅运
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值