一文了解spring事务特性

推荐工具 objectlog

对于重要的一些数据,我们需要记录一条记录的所有版本变化过程,做到持续追踪,为后续问题追踪提供思路。objectlog工具是一个记录单个对象属性变化的日志工具,工具采用spring切面和mybatis拦截器相关技术编写了api依赖包,以非侵入方式实现对标记的对象属性进行记录,仅需要导入依赖即可,几乎不需要对原系统代码改动,下面展示简单的效果(根据对象field渲染即可):
在这里插入图片描述

该系统具有以下特点:

  • 简单易用:系统将核心逻辑抽离,采用非侵入方式,只需要导入依赖后标注相关注解即可。
  • 业务共享:系统可以同时供多个业务系统使用,彼此之间互不影响。
  • 自动解析:能自动解析对象的属性变化,自动生成变化记录。
  • 便于扩展:支持更多对象属性类型的扩展,支持自定义解析处理逻辑等。
  • 工具性能:工具采用线程模式,脱离业务主线程,避免了解析过程对业务性能的影响。

开源地址:https://gitee.com/opensofte/objectlog,有兴趣的朋友可以看看点个star.

Spring事务说明

Spring事务本质是对数据库事务的支持,如果数据库不支持事务(例如MySQL的MyISAM引擎不支持事务),则Spring事务也不会生效。

事务的特征(ACID)

  • 原子性:事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。
  • 一致性:这表示数据库的引用完整性的一致性,表中唯一的主键等。
  • 隔离性:可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。
  • 持久性:一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除

并发事务的问题

  • 脏读(Dirty Reads)
    一个事务可以读取另一个事务未提交的数据.
  • 不可重复读(Non-Repeatable Reads)
    同一个事务中执行两次相同的查询, 可能得到不一样的结果. 这是因为在查询间隔内,另一个事务修改了该记录并提交了事务.
  • 幻读(Phantom Reads)
    当某个事务在读取某个范围内的记录时, 另一个事务又在该范围内插入了新的记录, 当之前的事务再次读取该范围的记录时, 会产生幻行.
  • 更新丢失(Lost Update)
    多个事务修改同一行记录(都未提交), 后面的修改覆盖了前面的修改.

解决这些问题需要进行事务隔离

Spring事务的传播方式

事务传播行为是指一个事务方法A被另一个事务方法B调用时,这个事务A应该如何处理。事务A应该在事务B中运行还是另起一个事务,
这个有事务A的传播行为决定。
事务传播属性定义TransactionDefinition

int PROPAGATION_REQUIRED = 0;
int PROPAGATION SUPPORTS = 1;
int PROPAGATION MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION NEVER = 5;
int PROPAGATION NESTED = 6;
常量名称常量解释
PROPAGATION_REQUIRED支持当前事务,如果当前没有事务,就新建一个事务。这是Spring默认人的事务的传播。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。使用JtaTransactionManager作为事务管理器
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果,内层事事务失败抛出异常,外层事务捕获,也可以不处理回滚操作。使用JtaTransactionManager作为事务管理器

PROPAGATION_REQUIRED

如果存在一个事务,则支持当前事务,如果没有事务则开启事务。

在这里插入图片描述
如下例子,单独调用methodB时,当前上下文没有事务,所以会开开启一个新的事务。
调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,
因此就加入到当前事务A中来。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    methodB(); 
    // do something
}
 
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    // do something
}

PROPAGATION_SUPPORTS

如果存在一个事务,支持当前事务。如果没有事务,则非事事务的执行.
在这里插入图片描述
单独的调用methodB时,methodB方法是非事务的执行的。当调用methdA时,methodB则加入了methodA的事务中,事务地执行。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    methodB();
    // do something
}
 
// 事务属性为SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
    // do something
}

PROPAGATION_MANDATORY

如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
在这里插入图片描述
当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常书throw new IllegalTransactionStateException("Transactionpropagation 'mandatory' but no existing transaction found")当调用methodA时,methodB则加入到methodA的事务中,以事务方式执行。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    methodB();
    // do something
}

@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
    // do something
}

PROPAGATION_NEVER

总是非事务地执行,如果存在一个活动事务,则抛出异常。

PROPAGATION_NESTED

如果一个活动的事务存在,则运行在一个嵌套的事务中。

如果没有活动事务,则按TransactionDefinition.PROPAGATIONREQUIRED属性执行。

这是一个嵌套事务,使用JDBC3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC驱动的
java.sql.Savepoint类。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为
true(属性值默认为false)。

在这里插入图片描述

@Transactional(propagation = Propagation.REQUIRED)
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}
 
@Transactional(propagation = Propagation.NEWSTED)
methodB(){
    // do something
}

单独调用methodB方法,则按REQUIRED属性执行。如果调用methodA方法,则相当于:

main(){
    Connection con = null;
    Savepoint savepoint = null;
    try{
        con = getConnection();
        con.setAutoCommit(false);
        doSomeThingA();
        savepoint = con2.setSavepoint();
        try{
            methodB();
        } catch(RuntimeException ex) {
            con.rollback(savepoint);
        } finally {
            //释放资源
        }
        doSomeThingB();
        con.commit();
    } catch(RuntimeException ex) {
        con.rollback();
    } finally {
        //释放资源
    }
}

当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。

需要注意的是,这时的事务并没有进行提交,如果后续的代码(do!SomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

PROPAGATION_REQUIRES_NEW

使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。它会开启一个新的事务。如果一个事务已经存在,则先将这个有在的事务挂起。
在这里插入图片描述
从下面代码可以看出,事务B与事务A是两个独立的事务,互不相干。事务B是否成功并不依赖于事务A。如果methodA方法在调用 methodB方法后的doSomeThingB方法失败了,而methodB方法月听做的结果依然被提交。而除了methodB之外的其它代码导致的果却被回滚了

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    doSomeThingA();
    methodB();
    doSomeThingB();
    // do something else
}
 
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    // do something
}

当调用methodA(),相当于

public static void main(){
    TransactionManager tm = null;
    try{
        //获得一个JTA事务管理器
        tm = getTransactionManager();
        tm.begin();//开启一个新的事务
        Transaction ts1 = tm.getTransaction();
        doSomeThing();
        tm.suspend();//挂起当前事务
        try{
            tm.begin();//重新开启第二个事务
            Transaction ts2 = tm.getTransaction();
            methodB();
            ts2.commit();//提交第二个事务
        } Catch(RunTimeException ex) {
            ts2.rollback();//回滚第二个事务
        } finally {
            //释放资源
        }
        //methodB执行完后,恢复第一个事务
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();//提交第一个事务
    } catch(RunTimeException ex) {
        ts1.rollback();//回滚第一个事务
    } finally {
        //释放资源
    }
}

PROPAGATION_NOT_SUPPORTED

总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。
在这里插入图片描述

Spring事务的隔离级别

事务隔离级别定义TransactionDefinition

int ISOLATION DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION SERIALIZABLE = 8;
隔离级别解释
ISOLATION_DEFAULT这是个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。
ISOLATION_READ_UNCOMMITTED这是事务最低的隔离级别,它允许另外一个事务可以看到这个事事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
ISOLATION_READ_COMMITTED保证一个事务修改的数据提交后才能被另外一个事务读取。另外个事务不能读取该事务未提交的数据。
ISOLATION_REPEATABLE_READ当前事务执行开始后,所读取到的数据都是该事务刚开始时所读取的数据和自己事务内修改的数据。这种事务隔离级别下,无论其他事务对数据怎么修改,在当前事务下读取到的数据都是该事务开始时的数据,所以这种隔离级别下可以避免不可重复读的问题,但还是有可能出现幻读
ISOLATION_SERIALIZABLE这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行

环境准备

mysql> select * from account;       
+----+-------+---------+----------+ 
| id | owner | balance | currency | 
+----+-------+---------+----------+ 
|  1 | one   |     100 | CNY      | 
|  2 | two   |     100 | CNY      | 
|  3 | three |     100 | CNY      | 
+----+-------+---------+----------+ 
3 rows in set (0.06 sec)

ISOLATION_READ_UNCOMMITTED

在这里插入图片描述
我分别设置事务1、事务2隔离级别为read uncommitted,从图中步骤3、4都可以看到,为了方便,后面的展示将不再说明。

  • 分别开始事务1、事务2(步骤5、6)。

  • 在事务1中,进行一个简单的查询,用来对比数据前后变化。

  • 在事务2中,查看 id 为 1 的账户,金额为 100 元。

  • 在事务1中,对 id 为 1 的账户余额减去 10 元,然后查询确认一下余额已经更改为 90 元。

  • 但是,如果在事务2中再次运行相同的 select 语句怎么办?
    你会看到余额被修改为了 90 元,而不是先前的 100 元。请注意,事务1并未提交,但事务2却看到了事务1所做的更改。这就是脏读现象,因为我们使用的事务隔离级别为read uncommitted(读未提交)。

ISOLATION_READ_COMMITTED

read-committed隔离级别只能防止脏读,但是会出现不可重复读和幻读。

在这里插入图片描述
设置隔离级别为 read committed,并开始事务。

  • ③ 在事务1中,进行一个简单的查询,用来对比数据前后变化。

  • ④ 在事务2中,查看 id 为 1 的账户,金额为 90 元。

  • ⑤⑥ 在事务1中,通过更新帐户余额减去 10 元,然后查询确认一下余额已经更改为 80 元,让我们看看此更改是否对事务2可见。

  • ⑦ 事务2中可以看到,其余额仍然与以前一样为 90 元。
    这是因为事务正在使用read-committed隔离级别,并且由于事务1还没有提交,所以它的写入数据不能被其他事务看到。
    因此,读已提交 (read-committed) 隔离级别可以防止脏读现象。那么对于不可重复读和幻读呢?

  • ⑧ 在事务2中,执行另一个操作,查询大于或等于 90 元的账户。

  • ⑨ 事务1进行提交。

  • ⑩ 现在,如果我们再次在事务2中查询帐户1余额,我们可以看到余额已更改为 80元 。所以,获得帐户1余额的同一查询返回了不同的值。 这就叫不可重复读。

  • 另外,在步骤11中,再次运行如⑧中的操作,这次只得到了2条记录,而不是以前的3条,因为事务1提交后,账户1的余额已经减少到 80 元了。

执行了相同的查询,但是返回了不同的行数。由于其他事务的提交,而导致一行数据消失,这种现象叫做幻读。

ISOLATION_REPEATABLE_READ

在这里插入图片描述

  • 设置隔离级别为 Repeatable Read,并开始事务。

  • ③查询事务1中的所有帐户,然后④查询事务2中ID为1的帐户,除此之外,还要⑤查询余额至少为80元的所有帐户。 这将用于验证幻读是否仍然发生。

  • 回到事务1⑥更新账户1余额减去 10 元;可以看到⑦帐户1的余额减少到了 70 元。

我们知道脏读已在较低的隔离级别read-committed不会出现。因此,由于以下规则,我们不需要在此级别进行检查:
在较低隔离级别被阻止的了读现象,不会出现在较高级别。

  • 因此,让我们⑧提交事务1,然后转移到⑨事务2,看看它是否能读取到事务1所做的新更改。

可以看到,该查询返回账户1的余额与先前相同,为 80 元,尽管事务1将账户1的余额更改为 70 元,并成功提交。
.
这是因为Repeatable Read(可重复读)隔离级别确保所有读查询都是可重复的,这意味着即使其他已提交的事务对数据进行了更改,它也始终返回相同的结果。

  • 我们重新运行⑩查询余额至少 80 元的帐户。

它仍然返回与之前相同的3条记录。所以在Repeatable_Read隔离级别中,可以解决不可重复读的问题。
.
但是,我想知道如果我们还运行步骤 11,从事务1更新过的帐户1的余额中减去10,会发生什么情况? 它将余额更改为70、60还是抛出错误? 试试吧!
.
结果没有报错,该账户余额现在改为了 60 元,这是正确的值,因此事务1早已经提交而将余额修改为了 70 元。
.
但是,从事务2的角度来看,这是没有意义的,因为在上一个查询中,它获取到的是 80 元的余额,但是从帐户中减去 10 元后,现在却得到 60 元。数学运算在这里不起作用,因为此事务仍受到其他事务的并发更新的干扰。

ISOLATION_SERIALIZABLE

在这里插入图片描述

  • 设置隔离级别为 Serializable,并开始事务。
  • ③查询事务1中的所有帐户,然后④查询事务2中ID为1的帐户。
  • 回到⑤事务1更新账户1余额减去 10 元。

有趣的是,这一次更新被阻止了。 事务2的 select 查询语句阻塞了事务1中的 update 更新语句。
.
原因是,在Serializable隔离级别中,MySQL隐式地将所有普通的 SELECT 查询转换为 SELECT FOR SHARE。 持有 SELECT FOR SHARE 锁的事务只允许其他事务读取行,而不能更新或删除行。
.
因此,有了这种锁定机制,我们以前看到的不一致数据场景不再可能出现。
.
但是,这个锁有一个超时持续时间。因此,如果事务2在该持续时间内未提交或回滚以释放锁,我们将看到锁等待超时错误(⑤下面显示错误)。
.
因此,当在应用程序中使用Serializable隔离级别时,请确保实现了一个事务重试策略,以防超时发生。

好的,将事务回滚,现在我将重新测试,看看另一种情况:
在这里插入图片描述

  • 这一次,到步骤⑤的时候,我不会让锁等待超时发生,然后到步骤⑥也进行了跟⑤一样的操作。
  • 到⑥这里,发生了死锁,因为现在事务2也需要等待事务1的 select 查询的锁。

所以请注意,除了锁等待超时之外,还需要处理可能出现的死锁情况。

现在,然我们尝试重启这两个事务:
在这里插入图片描述

  • 这次操作还是跟上面相同,到步骤⑤时,我们知道会阻塞,但如果此时步骤⑥事务2提交了,会怎样呢?
  • 如你所见,在提交了事务2后,事务2的 select 锁立即释放,从而⑤事务1中不再阻塞,更新成功。

Spring事务实现方式

开启事务支持

首先我们需要引入事务标签tx

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

声明式事务

基于xml声明式事务
  <!-- 直接配置连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/user_db"></property>
        <property name="username" value="root"></property>
        <property name="password" value="yuan159951."></property>
    </bean>

<!-- 1、创建事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 2、配置通知 -->
    <tx:advice id="txAdvice">
        <!-- 配置事务参数 -->
        <tx:attributes>
            <!-- 指定那种规则的方法上添加事务 -->
            <tx:method name="*" propagation="REQUIRED" read-only="false" rollback-for="*"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>			
        </tx:attributes>
    </tx:advice>

    <!--3、配置切入点和切面-->
    <aop:config>
        <!-- 切入点 -->
        <aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.*(..))"/>
        <!-- 配置切面 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>


基于注解声明式事务

第一步配置基本的事务信息

  <!-- 直接配置连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/user_db"></property>
        <property name="username" value="root"></property>
        <property name="password" value="yuan159951."></property>
    </bean>

<!-- 创建事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

<!-- 开启事务注解 -->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

当然上面的xml配置可以替换为以下内容,两者是等价的

@Configuration
@ComponentScan(basePackages = "com.atguigu.spring5")//开启注解扫描
@EnableTransactionManagement//开启事务
public class TxConfig {

    //创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/user_db");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("yuan159951.");
        return druidDataSource;
    }

    //创建jdbcTemplate
    @Bean  //根据类型注入dataSource
    public JdbcTemplate getJdbcTemplate(DruidDataSource druidDataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(druidDataSource);
        return jdbcTemplate;
    }

    //创建事务管理器
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource druidDataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(druidDataSource);
        return dataSourceTransactionManager;
    }
}

第二步业务中使用声明式事务注解@Transactional

	//参数
	@Transactional
    public void doBusiness(){
    	//业务逻辑
    }

@Transactional开启事务时,spring内部会执行一些操作,为了方便大家理解,咱们看看伪代码:

/有一个全局共享的threadLocal对象 resources
static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
//获取一个db的连接
DataSource datasource = platformTransactionManager.getDataSource();
Connection connection = datasource.getConnection();
//设置手动提交事务
connection.setAutoCommit(false);
Map<Object, Object> map = new HashMap<>();
map.put(datasource,connection);
resources.set(map);

编程式事务

开启事务配置

先来个配置类,将事务管理器PlatformTransactionManager、事务模板TransactionTemplate都注册到spring中。

@Configuration
@ComponentScan
public class MainConfig3 {
    @Bean
    public DataSource dataSource() {
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/user_db");
        dataSource.setUsername("root");
        dataSource.setPassword("root123");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
        return new TransactionTemplate(transactionManager);
    }
}

事务定义与状态
public interface TransactionDefinition {
    int getPropagationBehavior(); // 返回事务的传播行为
    int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
    int getTimeout();  // 返回事务必须在多少秒内完成
    boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
} 
public interface TransactionStatus{
    boolean isNewTransaction(); // 是否是新的事物
    boolean hasSavepoint(); // 是否有恢复点
    void setRollbackOnly();  // 设置为只回滚
    boolean isRollbackOnly(); // 是否为只回滚
    boolean isCompleted; // 是否已完成
} 

PlatformTransactionManager

在这里插入图片描述

在这里插入图片描述

  • 1.获取事务管理器;
  • 2.创建事务属性对象;
  • 3.获取事务状态对象;
  • 4.创建JDBC模板对象;
  • 5.业务数据操作处理;
public class test {
    @Resource
    private PlatformTransactionManager txManager;
    @Resource
    private  DataSource dataSource;
    private static JdbcTemplate jdbcTemplate;
    @Test
    public void testdelivery(){
        //定义事务隔离级别,传播行为,
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();  
        def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);  
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);  
        //事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;获取事务状态后,Spring根据传播行为来决定如何开启事务
        TransactionStatus status = txManager.getTransaction(def);  
        jdbcTemplate = new JdbcTemplate(dataSource);
        try {  
            jdbcTemplate.update("insert into testtranstation(sd) values(?)", "1");  
             //提交status中绑定的事务
            txManager.commit(status); 
        } catch (RuntimeException e) {  
            //回滚
            txManager.rollback(status);  
        } 
    }
    
}

在类中增加了两个属性:一个是 TransactionDefinition 类型的属性,它用于定义一个事务;另一个是 PlatformTransactionManager 类型的属性,用于执行事务管理操作。

如果方法需要实施事务管理,我们首先需要在方法开始执行前启动一个事务,调用PlatformTransactionManager.getTransaction(…) 方法便可启动一个事务。创建并启动了事务之后,便可以开始编写业务逻辑代码,然后在适当的地方执行事务的提交或者回滚。

TransactionTemplate

TransactionTemplate主要有2个方法,executeWithoutResult,execute

  • executeWithoutResult:无返回值场景,需传递一个Consumer对象,在accept方法中做业务操作
transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
    @Override
    public void accept(TransactionStatus transactionStatus) {
        //执行业务操作
    }
});
  • execute:有返回值场景,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作
Integer result = transactionTemplate.execute(new TransactionCallback<Integer>() {
    @Nullable
    @Override
    public Integer doInTransaction(TransactionStatus status) {
        return jdbcTemplate.update("insert into t_user (name) values (?)", "executeWithoutResult-3");
    }
});
public class test {
    @Resource
    private  DataSource dataSource;
    private static JdbcTemplate jdbcTemplate;
    private static PlatformTransactionManager platformTransactionManager;
	@Test
    public void test2() throws Exception {
        //定义一个JdbcTemplate,用来方便执行数据库增删改查
        jdbcTemplate = new JdbcTemplate(dataSource);
        //1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
        platformTransactionManager = new DataSourceTransactionManager(dataSource);
        //2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
        DefaultTransactionDefinition  transactionDefinition = new DefaultTransactionDefinition();
        transactionDefinition.setTimeout(10);
        //3.创建TransactionTemplate对象
        TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager,transactionDefinition);/**
         * 4.通过TransactionTemplate提供的方法执行业务操作
         * 主要有2个方法:
         * (1).executeWithoutResult(Consumer<TransactionStatus> action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作
         * (2).<T> T execute(TransactionCallback<T> action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作
         * 调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。
         * 那么什么时候事务会回滚,有2种方式:
         * (1)transactionStatus.setRollbackOnly();将事务状态标注为回滚状态
         * (2)execute方法或者executeWithoutResult方法内部抛出异常
         * 什么时候事务会提交?
         * 方法没有异常 && 未调用过transactionStatus.setRollbackOnly();
         */
        transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
            @Override
            public void accept(TransactionStatus transactionStatus) {
                jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-1");
            }
        });
        System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
    }
}

Spring事务失效的几种常见

1. @Transactional 应用到 非public (只有public方法才有效)

2.避免 SpringAOP 的自调用问题在 SpringAOP 代理下,只能目标方法由外部调用,
若同一类中的:
    其他没有@Transactional注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚,
    除此之外,即使是有@Transcation注解的,被调用的事务方法也会失效。

3.数据库引擎不支持事务

4.没有被 Spring 管理 没有添加@Service等注解放入到容器中

5.数据源没有配置事务管理器

6.把异常吃了,然后又不抛出来,事务也不会回滚!
    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order
        } catch {

        }
    }
    
7.异常类型错误 默认是RuntimeException自动回滚,
    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order
        } catch {
            throw new Exception("更新错误");
        }
    }
    @Transactional(rollbackFor = Exception.class)
    值得注意的是这个配置仅限于 Throwable 异常类及其子类。


8.@Transactional的扩展配置不支持事务
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void updateOrder(Order order) {

    }
  Propagation.NOT_SUPPORTED:表示不以事务运行,当前若存在事务则挂起。
  这表示不支持以事务的方式运行,所以即使事务生效也是白搭!

参考博文

mysql和postgresql事务隔离级别差异

spring事务介绍

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue2 组件通信方式

props/emit props 作用&#xff1a;父组件通过 props 向子组件传递数据parent.vue <template><div><Son :msg"msg" :pfn"pFn"></Son></div> </template><script> import Son from ./son export default {name: …

面向对象进阶——内部类

1、初始内部类 什么是内部类&#xff1f; 类的五大成员&#xff1a; 属性、方法、构造方法、代码块、内部类 在一个类的里面&#xff0c;再定义一个类。 举例&#xff1a;在A类大的内部定义B类&#xff0c;B类就被称为内部类 public class Outer{ 外部类 public …

流媒体学习之路(WebRTC)——GCC中ProbeBitrateEstimator和AcknowledgedBitrateEstimator的大作用(7)

流媒体学习之路(WebRTC)——GCC中ProbeBitrateEstimator和AcknowledgedBitrateEstimator的大作用&#xff08;7&#xff09; —— 我正在的github给大家开发一个用于做实验的项目 —— github.com/qw225967/Bifrost目标&#xff1a;可以让大家熟悉各类Qos能力、带宽估计能力&a…

抛弃英特尔,采用自研M4芯片,苹果公司的iPad Pro有什么好?

【科技明说 &#xff5c; 科技热点关注】 看到​苹果公司最新发布的M4芯片iPad Pro&#xff0c;我的眼前一亮&#xff0c;“其是否又该更换新pad了&#xff1f;”在这样的扪心自问之中&#xff0c;我发现了M4芯片的iPad Pro几大好处。 一是性能更好&#xff0c;M4芯片采用第二…

系统Cpu利用率降低改造之路

系统Cpu利用率降低改造之路 一.背景 1.1 系统背景 该系统是一个专门爬取第三方数据的高并发系统&#xff0c;该系统单台机器以每分钟400万的频次查询第三方数据&#xff0c;并回推给内部第三方系统。从应用类型上看属于IO密集型应用,为了提高系统的吞吐量和并发&#xff0c;…

解决电脑睡眠后,主机ping不通VMware虚拟机

文章目录 问题解决方法方法一方法二注意 问题 原因&#xff1a;电脑休眠一段时间&#xff0c;再次打开电脑就ping不通VMware虚拟机。 解决方法 方法一 重启电脑即可&#xff0c;凡是遇到电脑有毛病&#xff0c;重启能解决90%问题。但是重启电脑比较慢&#xff0c;而且重启…

system函数和popen函数

system函数 #include <stdlib.h> *int system(const char command); system函数在linux中的源代码&#xff1a; int system(const char * cmdstring) {pid_t pid;int status;if(cmdstring NULL){return (1);}if((pid fork())<0){status -1;}else if(pid 0){ //子…

Spring MVC(三) 参数传递

1 Controller到View的参数传递 在Spring MVC中&#xff0c;把值从Controller传递到View共有5中操作方法&#xff0c;分别是。 使用HttpServletRequest或HttpSession。使用ModelAndView。使用Map集合使用Model使用ModelMap 使用HttpServletRequest或HttpSession传值 使用HttpSe…

Vue-Cli脚手架项目的搭建【新手快速入手】

目录 一、Vue CLI脚手架简介☺ 1.Node.js前置环境的安装 2.安装npm管理器 3.安装淘宝镜像(cnpm) 二、安装vue-cli 1. 版本号查看 2.旧版本卸载 3.新版本安装 4.检查 三、Vue项目的搭建 &#x1f4cc;进入Vue项目管理器 ★命令方式创建 若localhost拒绝访问怎么办&…

深度剖析:SSD能否全面取代HDD?-2

近日&#xff0c;希捷针对SSD即将全面取代HDD的市场预言也提出站在HDD厂商角度不同的观点。 这些观点出自希捷的一份演示文稿&#xff0c;实质上是对Pure Storage首席执行官Charlie Giancarlo所称“五年内不会再有新的磁盘系统出售”这一论断的回应&#xff0c;意味着到2028年底…

(十六)Servlet教程——Servlet文件下载

Servlet文件下载 文件下载是将服务器上的资源下载到本地&#xff0c;可以通过两种方式来下载服务器上的资源。第一种是使用超链接来下载&#xff0c;第二种是通过代码来下载。 超链接下载 在HTML或者JSP页面中使用超链接时&#xff0c;可以实现页面之间的跳转&#xff0c;但是…

深入理解卷积函数torch.nn.Conv2d的各个参数以及计算公式(看完写模型就很简单了)

代码解释帮助理解&#xff1a; torch.randn(10, 3, 32, 32)&#xff0c;初始数据&#xff1a;(10, 3, 32, 32)代表有10张图片&#xff0c;每张图片的像素点用三个数表示&#xff0c;每张图片大小为32x32。&#xff08;重点理解这个下面就好理解了&#xff09; nn.Conv2d(3, 64…

python自动打卡的代码

好的&#xff0c;以下是一个简单的Python自动打卡程序代码&#xff0c;用于在特定时间自动打卡&#xff1a; python import datetime import time # 设置打卡时间和打卡间隔 check_in_time datetime.datetime(2023, 3, 1, 9, 30) check_out_time datetime.datetime(2023, 3, …

苹果电脑免费第三方软件CleanMyMac X2025电脑版垃圾清理软件神器

Mac电脑用户在长时间使用电脑之后&#xff0c;时常会看到“暂存盘已满”的提示&#xff0c;这无疑会给后续的电脑使用带来烦恼&#xff0c;那么苹果电脑暂存盘已满怎么清理呢&#xff0c;下面将给大家带来一些干货帮你更好地解决这个问题。 CleanMyMac X2024全新版下载如下: h…

springboot之统一异常封装

一&#xff1a;统一返回实体对象 JsonInclude(Include.NON_NULL) public class ResponseObject implements Serializable {private static final long serialVersionUID 1L;private Integer code 0;private String message "success";private Long time System.…

新版文件同步工具(Python编写,其中同时加入了多进程计算MD5、多线程复制大文件、多协程复制小文件、彩色输出消息、日志功能)

两个月前&#xff0c;接到一个粉丝的要求&#xff0c;说希望在我之前编写的一个python编写的文件同步脚本(Python编写的简易文件同步工具(已解决大文件同步时内存溢出问题)https://blog.csdn.net/donglxd/article/details/131225175)上加入多线程复制文件的功能&#xff0c;前段…

flutter中固定底部按钮,防止键盘弹出时按钮跟随上移

当我们想要将底部按钮固定在底部&#xff0c;我们只需在Widget中的Scaffold里面加一句 resizeToAvoidBottomInset: false, // 设置为false&#xff0c;固定页面不会因为键盘弹出而移动 效果图如下

CSCWD 2024会议最后一天 女高音惊艳全场,相声笑破肚皮

会议之眼 快讯 今天是第27届国际计算机协同计算与设计大会&#xff08;CSCWD 2024&#xff09;举办的最后一天&#xff01;会议依然热络&#xff0c;紧张而充实&#xff01;各个技术分论坛持续展开&#xff0c;学者们的热情不减&#xff0c;对技术领域的热爱和探索精神令人赞叹…

国产开源物联网操作系统

软件介绍 RT-Thread是一个开源、中立、社区化发展的物联网操作系统&#xff0c;采用C语言编写&#xff0c;具有易移植的特性。该项目提供完整版和Nano版以满足不同设备的资源需求。 功能特点 1.内核层 RT-Thread内核包括多线程调度、信号量、邮箱、消息队列、内存管理、定时器…

VS配置三方依赖

1.配置include 1.1.打开属性 1.2.打开“配置属性”-"C/C"-"常规" 2.配置lib 2.1.配置lib目录 打开"配置属性"-“链接器”-“常规”。 2.2.配置具体的lib 打开"配置属性"-"链接器"-“输入”。 也可以通过代码方式加入&…