【JavaEE进阶】Spring 事务和事务传播机制

目录

1.事务回顾

1.1 什么是事务

1.2 为什么需要事务

1.3 事务的操作

2. Spring 中事务的实现

2.1 Spring 编程式事务(了解)

2.2 Spring声明式事务 @Transactional

对比事务提交和回滚的日志

3. @Transactional详解

3.1 rollbackFor

3.2 @Transactional 注解什么时候会失效

3.3 事务的隔离级别

3.3.1 MySQL 事务隔离级别

3.3.2 Spring 事务隔离级别

3.4 Spring 事务传播机制

3.4.1 什么是事务传播机制

3.4.2 事务的传播机制有哪些


1.事务回顾

1.1 什么是事务

事务是一组操作的集合, 是一个不可分割的操作:
事务会把所有的操作作为一个整体, 一起向数据库提交或者是撤销操作请求, 所以这组操作要么同时成功, 要么同时失败.

1.2 为什么需要事务

我们在进行程序开发时,也会有事务的需求
比如转账操作:
第一步: A账户 -100 元.
第二步: B 账户 +100 元
如果没有事务,第一步执行成功了, 第二步执行失败了, 那么A 账户的100 元就平白无故消失了.    如果使用事务就可以解决这个问题, 让这一组操作要么一起成功, 要么一起失败.

比如秒杀系统,
第一步: 下单成功
第二步:  扣减库存
下单成功后, 库存也需要同步减少, 如果下单成功, 库存扣减失败, 那么就会造成下单超出的情况.  所以就需要把这两步操作放在同一个事务中. 要么一起成功, 要么一起失败.

理解事务概念为主, 实际企业开发时, 并不是简单的通过事务来处理。

1.3 事务的操作

事务的操作主要有三步: 

-- 开启事务 
start transaction;

-- 提交事务 
commit;

-- 回滚事务 
rollback;

2. Spring 中事务的实现

Spring 中的事务操作分为两类:
1.编程式事务 (手动写代码操作事务)
2.声明式事务 (利用注解自动开启和提交事务)

在学习事务之前, 我们先准备数据和数据的访问代码
需求: 用户注册, 注册时在日志表中插入一条操作记录.

数据准备:

-- 创建数据库 
DROP DATABASE IF EXISTS trans_test;
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;

use trans_test;
-- ⽤⼾表 
DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (
 `id` INT NOT NULL AUTO_INCREMENT,
 `user_name` VARCHAR (128) NOT NULL,
 `password` VARCHAR (128) NOT NULL,
 `create_time` DATETIME DEFAULT now(),
 `update_time` DATETIME DEFAULT now() ON UPDATE now(),
 PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARACTER 
SET = utf8mb4 COMMENT = '⽤⼾表';

-- 操作⽇志表 
DROP TABLE IF EXISTS log_info;
CREATE TABLE log_info (
 `id` INT PRIMARY KEY auto_increment,
 `user_name` VARCHAR ( 128 ) NOT NULL,
 `op` VARCHAR ( 256 ) NOT NULL,
 `create_time` DATETIME DEFAULT now(),
 `update_time` DATETIME DEFAULT now() ON UPDATE now() 
) DEFAULT charset 'utf8mb4';

代码准备:

model层:

mapper层: 

server层: 

2.1 Spring 编程式事务(了解)

Spring 手动操作事务和上面 MySOL操作事务类似,有3个重要操作步骤:

SpringBoot 内置了两个对象:


我们还是根据代码的实现来学习:

package com.example.trans.controller;

import com.example.trans.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;

    @Autowired
    private UserService userService;
    @RequestMapping("register")
    public Boolean register(String username, String password) {
        //开启事务
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        Integer result = userService.insert(username, password);
        System.out.println("插入用户表, result: " + result);
        //回滚事务
//        dataSourceTransactionManager.rollback(transaction);
        //提交事务
        dataSourceTransactionManager.commit(transaction);
        return true;
    }
}

 这部分代码了解即可:

以上代码虽然可以实现事务,但操作也很繁琐,有没有更简单的实现方法呢?
接下来我们学习声明式事务

2.2 Spring声明式事务 @Transactional

声明式事务的实现很简单

在需要事务的方法上添加 @Transactional

注解就可以实现了.无需手动开启事务和提交事务, 进入方法时自动开启事务, 方法执行完会自动提交事务, 如果中途发生了没有处理的异常会自动回滚事务.

我们来看代码实现:

@RequestMapping("/trans")
@RestController
public class TransController {
    @Autowired
    UserService userService;

    @Autowired
    LogService logService;

    @Transactional
    @RequestMapping("/register")
    public Boolean register(String userName, String password) {
        Integer result = userService.insert(userName, password);
        System.out.println("插入用户表, result: " + result);
        return true;
    }
}

运行程序, 发现数据插入成功

修改程序,使之出现异常

运行程序:


发现虽然日志显示数据插入成功, 但数据库却没有新增数据, 事务进行了回滚

我们一般会在业务逻辑层当中来控制事务, 因为在业务逻辑层当中, 一个业务功能可能会包含多个数据访问的操作, 在业务逻辑层来控制事务, 我们就可以将多个数据访问操作控制在一个事务范围内上述代码在Controller中书写,  只是为了方便学习.

@Transactional作用

@Transactional 可以用来修饰方法或类:

方法/类 被 @Transactional 注解修饰时, 在目标方法执行开始之前, 会自动开启事务, 方法执行结束
之后,自动提交事务.

如果在方法执行过程中, 出现异常, 且异常未被捕获, 就进行事务回滚操作

如果异常被程序捕获, 方法就被认为是成功执行, 依然会提交事务,

运行程序, 事务成功提交,: 

在上述程序中, 发现虽然程序出错了, 但是由于异常被捕获了, 所以事务依然得到了提交

如果需要事务进行回滚, 有以下两种方式:

1.重新抛出异常

2.手动回滚事务

对比事务提交和回滚的日志

事务提交时:

事务回滚时:

当事务提交时, 日志会含有: Transaction synchronization committing SqlSession

3. @Transactional详解

通过上面的代码, 我们学习了 @Transactional 的基本使用. 接下来我们学习 @Transactional
注解的使用细节.

我们主要学习 @Transactional 注解当中的三个常见属性:
1. rollbackFor: 异常回滚属性, 指定能够触发事务回滚的异常类型, 可以指定多个异常类型
2.lsolation: 事务的隔离级别. 默认值为 Isolation.DEFAULT
3. propagation: 事务的传播机制. 默认值为 Propagation.REQUIRED


3.1 rollbackFor

  • 默认情况下,Spring 的事务管理器只会在抛出运行时异常(RuntimeException及其子类)和错误(Error)时才会回滚事务。这是因为在 Java 的异常体系中,非运行时异常(例如IOExceptionSQLException等检查异常)通常被认为是可以预期和合理处理的情况。
  • 例如,如果你有一个方法是从文件中读取数据,可能会抛出IOException。在这种情况下,开发人员可能希望在捕获这个异常后进行一些特定的处理,比如记录日志、提示用户重新输入等操作,而不是直接回滚事务。

验证抛出IOException异常, 事务提交:

 运行程序, 事务成功提交:

如果我们需要所有异常都回滚, 需要来配置 @Transactional 注解当中的 rollbackFor 属性, 通
过 rollbackFor 这个属性指定出现何种异常类型时事务进行回滚.

结论:

1.在Spring的事务管理中,默认只在遇到运行时异常 RuntimeException 和 Error 时才会回滚

2.如果需要回滚指定类型的异常, 可以通过 rollbackFor 属性来指定 

3.2 @Transactional 注解什么时候会失效

1. 方法的访问权限问题

情况说明:如果@Transactional注解标记的方法是private的,那么这个注解将会失效。这是因为 Spring 事务管理是基于代理的机制来实现的,代理对象无法访问目标对象的private方法。

示例代码

import org.springframework.transaction.annotation.Transactional;
public class TransactionalService {
    @Transactional
    private void privateTransactionalMethod() {
        // 业务逻辑
    }
}

解释:在这个示例中,privateTransactionalMethod方法虽然被标注了@Transactional,但是由于它是private方法,Spring 无法为其创建有效的代理来管理事务,所以该注解实际上不会起作用。

2. 方法内部调用自身方法的情况

情况说明:当一个类中的方法 A 调用了同一个类中的另一个被@Transactional注解标记的方法 B,并且这个调用是在方法 A 内部直接调用(而不是通过代理对象调用)时,方法 B 上的@Transactional注解会失效。这是因为在这种情况下,没有经过 Spring 的代理拦截,也就无法开启事务管理。

示例代码

import org.springframework.transaction.annotation.Transactional;
public class TransactionalService {
    public void outerMethod() {
        innerTransactionalMethod();
    }
    @Transactional
    public void innerTransactionalMethod() {
        // 业务逻辑
    }
}

解释:在这个例子中,outerMethod直接调用innerTransactionalMethod,这种内部调用不会触发 Spring 的事务代理,所以innerTransactionalMethod上的@Transactional注解在这里不会产生事务管理的效果。

3. 异常被捕获但没有重新抛出的情况

情况说明@Transactional注解默认是在运行时异常(RuntimeException)和错误(Error)抛出时才会回滚事务。如果在被@Transactional注解标记的方法中捕获了异常,并且没有重新抛出(对于需要回滚事务的异常类型),那么事务将不会回滚。

示例代码

import org.springframework.transaction.annotation.Transactional;
public class TransactionalService {
    @Transactional
    public void methodWithTransaction() {
        try {
            // 业务逻辑,可能会抛出RuntimeException
            throw new RuntimeException("模拟异常");
        } catch (RuntimeException e) {
            // 捕获异常但没有重新抛出
        }
    }
}

解释:在这个示例中,methodWithTransaction方法中虽然抛出了RuntimeException,但是在捕获这个异常后没有重新抛出,所以事务不会回滚。

4.数据库不支持事务或者事务配置错误的情况

情况说明:如果使用的数据库本身不支持事务(虽然这种情况比较少见,大多数主流数据库都支持事务),或者在 Spring 配置中事务相关的配置(如数据源、事务管理器等)出现错误,那么@Transactional注解也会失效。

5. 使用了不兼容的事务传播行为组合

情况说明:在多个方法嵌套调用并且每个方法都有@Transactional注解的情况下,如果事务传播行为组合不兼容,可能会导致事务失效或者不符合预期。例如,在一个方法中使用REQUIRES_NEW传播行为,而在嵌套调用的方法中使用NEVER传播行为,这可能会导致事务无法正常开启或者出现冲突。

示例代码

import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public class TransactionalService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void outerMethod() {
        innerMethod();
    }
    @Transactional(propagation = Propagation.NEVER)
    public void innerMethod() {
        // 业务逻辑
    }
}

解释:在这个例子中,outerMethod要求开启一个新事务(REQUIRES_NEW),而innerMethod却声明不允许在事务中执行(NEVER),这种不兼容的传播行为组合可能会导致事务管理出现问题,使得@Transactional注解无法按照预期工作。

3.3 事务的隔离级别

3.3.1 MySQL 事务隔离级别

SQL标准定义了四种隔离级别, MySQL全都支持, 这四种隔离级别分别是:

1.读未提交(READ UNCOMMITTED): 读未提交, 也叫未提交读, 该隔离级别的事务可以看到其他事务中未提交的数据.

因为其他事务未提交的数据可能会发生回滚,但是该隔离级别却可以读到,我们把该级别读到的数据称之为脏数据,这个问题称之为脏读。

2.读提交(READ COMMITTED): 读已提交, 也叫提交读, 该隔离级别的事务能读取到已经提交事务的数据.

该隔离级别不会有脏读的问题, 但由于在事务的执行中可以读取到其他事务提交的结果, 所以在不同时间的相同 SOL 查询可能会得到不同的结果, 这种现象叫做不可重复读

3. 可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也就可以确保同一事务多次查询的结果一致, 但是其他事务新插入的数据, 是可以感知到的. 这也就引发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别.

比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因).明明在事务中查询不到这条信息,但自己就是插入不进去,这个现象叫幻读.

4.串行化(SERIALIZABLE): 序列化, 事务最高隔离级别, 它会强制事务排序, 使之不会发生冲突, 从而解决了脏读, 不可重复读和幻读问题, 但因为执行效率低,所以真正使用的场景并不多,

  1. 读未提交(Read Uncommitted)

    • 定义:这是最低的隔离级别。在这个级别下,一个事务可以读取到另一个事务未提交的数据。这就可能导致脏读(Dirty Read)的问题。
    • 脏读示例:假设有两个事务,事务 A 和事务 B。事务 A 修改了一条记录但尚未提交,事务 B 此时读取了这条被修改的记录。如果事务 A 后来回滚了修改,那么事务 B 读取到的数据就是 “脏” 数据,这种情况就是脏读。例如,事务 A 将账户余额从 100 元修改为 200 元,但还没提交,事务 B 读取账户余额为 200 元,然后事务 A 回滚,实际余额还是 100 元,事务 B 读取的数据就不准确了。
    • 应用场景:这种隔离级别一般很少在实际生产环境中使用,因为脏读问题可能会导致数据的不一致性。不过在一些对数据准确性要求不高,并且需要最大程度提高性能和并发度的场景下,可能会考虑使用,比如某些数据分析系统,对数据的实时性要求高于准确性。
  2. 读已提交(Read Committed)

    • 定义:在这个隔离级别下,一个事务只能读取另一个事务已经提交的数据,避免了脏读的问题。但是可能会出现不可重复读(Non - Repeatable Read)的情况。
    • 不可重复读示例:事务 A 在执行过程中,两次读取同一条记录,在两次读取之间,事务 B 修改并提交了这条记录。这样事务 A 两次读取的结果就不一样了。例如,事务 A 先读取账户余额为 100 元,然后事务 B 将余额修改为 200 元并提交,事务 A 再次读取余额就变成了 200 元,这就是不可重复读。
    • 应用场景:在很多对数据准确性有一定要求的业务场景中比较常用,比如一般的 Web 应用程序中的查询操作,大多数情况下可以接受不可重复读的情况,因为这些查询通常是获取最新的数据状态。
  3. 可重复读(Repeatable Read)

    • 定义:在这个隔离级别下,一个事务在执行过程中多次读取同一数据会得到相同的结果,不会出现不可重复读的情况。不过,在这个级别下可能会出现幻读(Phantom Read)的情况。
    • 幻读示例:事务 A 在执行过程中,根据某个条件查询记录,第一次查询得到了一定数量的记录。在事务 A 还未结束时,事务 B 插入了符合事务 A 查询条件的新记录并提交。当事务 A 再次根据相同条件查询时,就会发现比第一次查询多了一些记录,就好像出现了 “幻影” 一样。例如,事务 A 查询年龄大于 30 岁的员工名单,第一次查询有 5 个人,然后事务 B 插入了一个年龄大于 30 岁的新员工记录并提交,事务 A 再次查询就有 6 个人了。
    • 应用场景:对于一些对数据一致性要求较高的业务场景,如金融系统中的账户交易记录查询等,可重复读是比较合适的隔离级别。它可以保证在一个事务内部,对同一数据的读取是稳定的。
  4. 串行化(Serializable)

    • 定义:这是最高的隔离级别。在这个级别下,事务是串行执行的,一个事务必须等待前一个事务完成后才能开始,就像把多个事务的执行顺序排好队一样。这样可以避免脏读、不可重复读和幻读的问题。
    • 应用场景:在对数据准确性和一致性要求极高的场景下使用,如一些涉及到高价值交易或者重要数据修改的系统,像银行的核心账务系统等。不过,这种隔离级别会严重影响系统的并发性能,因为事务是串行执行的,所以在使用时需要权衡性能和数据一致性的需求。

3.3.2 Spring 事务隔离级别

Spring 中事务隔离级别有5 种:

package org.springframework.transaction.annotation;

public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);

    private final int value;

    private Isolation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进行设置

@Transactional(isolation = Isolation.READ_COMMITTED)

3.4 Spring 事务传播机制

3.4.1 什么是事务传播机制

事务传播机制就是: 多个事务方法存在调用关系时, 事务是如何在这些方法间进行传播的。

比如有两个方法 A, B 都被 @Transactional 修饰,A方法调用B方法

A方法运行时, 会开启一个事务. 当A调用B时, B方法本身也有事务, 此时B方法运行时, 是加入A的事务, 还是创建一个新的事务呢?

这个就涉及到了事务的传播机制.

比如公司流程管理

执行任务之前,需要先写执行文档,任务执行结束,再写总结汇报

此时A部门有一项工作, 需要B部门的支援, 此时B部门是直接使用A部门的文档, 还是新建一个文档呢?

事务隔离级别解决的是多个事务同时调用一个数据库的问题

而事务传播机制解决的是一个事务在多个节点(方法)中传递的问题

3.4.2 事务的传播机制有哪些

@Transactional 注解支持事务传播机制的设置, 通过 propagation 属性来指定传播行为

Spring 事务传播机制有以下7种:

public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);

    private final int value;

    private Propagation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

比如一对新人要结婚了, 关于是否需要房子

1.REQUIRED(默认)

  • 定义:如果当前没有事务,就创建一个新事务;如果当前已经存在一个事务,就加入这个事务。这是最常用的传播机制。
  • 示例场景
    • 假设我们有一个业务方法 A 调用另一个业务方法 B,方法 A 已经开启了一个事务。当方法 B 使用REQUIRED传播机制时,它会直接加入到方法 A 的事务中。例如,在一个电商系统中,下单操作(方法 A)开启了一个事务,在下单过程中需要更新库存(方法 B),方法 B 就可以使用REQUIRED传播机制加入到下单事务中,保证下单和库存更新要么都成功,要么都失败。
  • 代码示例
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    // 事务开始
    methodB();
    // 事务提交或回滚
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    // 加入methodA的事务中
}
 

2.SUPPORTS

  • 定义:如果当前存在事务,就加入当前事务;如果当前没有事务,就以非事务方式执行。
  • 示例场景
    • 考虑一个日志记录方法。如果在一个事务性的业务操作过程中调用它,它可以加入事务,保证日志记录和业务操作一起成功或失败;但如果是在非事务环境下调用,它也能正常工作,只是不会有事务管理。例如,在一个用户注册系统中,注册方法(有事务)可能会调用记录注册日志的方法,日志方法使用SUPPORTS传播机制,就可以在注册事务中记录日志;而单独测试日志方法时,它也能正常记录日志而不需要事务。
  • 代码示例
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    // 事务开始
    methodB();
    // 事务提交或回滚
}
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
    // 如果methodA有事务就加入,没有就非事务执行
}
 

3.MANDATORY

  • 定义:如果当前存在事务,就加入当前事务;如果当前没有事务,就抛出异常。
  • 示例场景
    • 想象一个必须在事务环境下执行的关键业务操作,比如在一个资金转账系统中,有一个计算转账手续费的方法,这个方法必须在转账事务中执行,因为手续费的计算和转账操作紧密相关。如果调用这个方法时没有事务,就应该抛出异常,提醒开发人员必须在事务环境下调用。
  • 代码示例
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    // 事务开始
    methodB();
    // 事务提交或回滚
}
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
    // 如果没有事务就抛出异常,有事务就加入
}
 

4.REQUIRES_NEW

  • 定义:不管当前是否存在事务,都创建一个新事务。如果当前已经存在一个事务,就先暂停当前事务,等新事务执行完毕后,再恢复原来的事务。
  • 示例场景
    • 在一个电商系统中,下单操作(有事务)可能需要调用一个发送通知的方法。发送通知的操作应该独立于下单事务,即使下单事务回滚,通知也已经发送出去了。所以发送通知的方法可以使用REQUIRES_NEW传播机制,开启一个新事务来发送通知。
  • 代码示例
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    // 事务开始
    methodB();
    // 事务提交或回滚
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    // 开启新事务,与methodA的事务独立
}
 

5.NOT_SUPPORTED

  • 定义:以非事务方式执行操作,并且如果当前存在事务,就先暂停当前事务,等操作执行完毕后,再恢复原来的事务。
  • 示例场景
    • 假设在一个事务性的业务流程中,需要调用一个外部的统计服务。这个统计服务本身不需要事务管理,而且为了不影响原来事务的性能,最好以非事务方式执行。例如,在一个订单处理系统中,有一个事务性的订单审核方法,在审核过程中需要调用一个外部的订单统计服务,这个统计服务就可以使用NOT_SUPPORTED传播机制,暂停审核事务,执行完统计后再恢复审核事务。
  • 代码示例
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    // 事务开始
    methodB();
    // 事务提交或回滚
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodB() {
    // 暂停methodA的事务,非事务执行,然后恢复methodA的事务
}
 

6.NEVER

  • 定义:以非事务方式执行操作,如果当前存在事务,就抛出异常。
  • 示例场景
    • 考虑一个简单的查询方法,它只是用于快速获取数据,不应该在事务环境下执行。如果在一个有事务的上下文中调用这个查询方法,就应该抛出异常,因为它可能会影响查询性能或者不符合设计意图。例如,在一个数据查询接口中,有一个简单的获取用户基本信息的方法,这个方法可以使用NEVER传播机制,确保它不会在事务环境下执行。
  • 代码示例
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    // 事务开始
    methodB();
    // 事务提交或回滚
}
@Transactional(propagation = Propagation.NEVER)
public void methodB() {
    // 如果有事务就抛出异常,以非事务执行
}
 

7.NESTED

  • 定义:如果当前存在事务,就在当前事务中嵌套一个子事务。子事务可以独立于父事务提交或回滚,但是如果父事务回滚,子事务也会回滚。
  • 示例场景
    • 在一个复杂的业务流程中,可能有一个主业务操作和一些附属的子业务操作。例如,在一个订单处理系统中,主订单处理事务(父事务)可能包含一个子事务,如更新相关的推荐商品信息。如果推荐商品信息更新成功(子事务成功),但主订单处理失败(父事务失败),那么子事务也会回滚;而如果子事务失败,父事务可以根据具体情况决定是否继续执行或者也回滚。
  • 代码示例
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    // 事务开始
    methodB();
    // 事务提交或回滚
}
@Transactional(propagation = Propagation.NESTED)
public void methodB() {
    // 在methodA的事务中创建嵌套子事务
}

总结

 

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

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

相关文章

npm list @types/node 命令用于列出当前项目中 @types/node 包及其依赖关系

文章目录 作用示例常用选项示例命令注意事项 1、实战举例**解决方法**1. **锁定唯一的 types/node 版本**2. **清理依赖并重新安装**3. **设置 tsconfig.json 的 types**4. **验证 Promise 类型支持** **总结** npm list types/node 命令用于列出当前项目中 types/node 包及其…

【靶点Talk】BCMA疗法能否成为下一个掘金点?

BCMA是一种极其重要的B细胞生物标志物,广泛存在于MM细胞表面,近年来已成为MM和其他血液系统恶性肿瘤的一个非常热门的免疫治疗靶点。今天靶点科普给大家带来BCMA作用机制和临床研究进展: 1 BCMA的“简历” B细胞成熟抗原(BCMA,又…

Linux 网络编程

网络编程:OSI 七层模型、TCP 协议、UDP 协议、三次握手、四次挥手、socket编程及编程实战 // 掌握网络编程,TCP、UDP等,socket函数编程 前置知识: 网络通信 网络通信本质上是一种进程间通信,是位于网络中不同主机上的进…

中文核心期刊论文模板免费下载

大家好,今天我要和大家分享一个对学术研究人员非常有帮助的资源——中文核心期刊论文模板。这个模板严格遵循中文核心期刊的出版标准,旨在帮助作者按照期刊的基本格式和内容要求撰写论文,确保论文结构清晰、规范,从而提高投稿效率…

Linux之DNS服务器

一、DNS 简介 定义与作用:DNS(Domain Name System)是互联网上的一项服务,作为将域名和 IP 地址相互映射的分布式数据库,使人更方便地访问互联网。使用 53 端口,通常以 UDP 查询,未查到完整信息…

已解决:spark代码中sqlContext.createDataframe空指针异常

这段代码是使用local模式运行spark代码。但是在获取了spark.sqlContext之后,用sqlContext将rdd算子转换为Dataframe的时候报错空指针异常 Exception in thread "main" org.apache.spark.sql.AnalysisException: java.lang.RuntimeException: java.lang.Nu…

物联网低功耗广域网LoRa开发(一):LoRa物联网行业解决方案

一、LoRa的优势以及与其他无线通信技术对比 (一)LoRa的优势 1、164dB链路预算 、距离>15km 2、快速、灵活的基础设施易组网且投资成本较少 3、LoRa节点模块仅用于通讯电池寿命长达10年 4、免牌照的频段 网关/路由器建设和运营 、节点/终端成本低…

【2024最新】渗透测试工具大全(超详细),收藏这一篇就够了!

【2024最新】渗透测试工具大全(超详细),收藏这一篇就够了! 黑客/网安大礼包:👉基于入门网络安全/黑客打造的:👉黑客&网络安全入门&进阶学习资源包 所有工具仅能在取得足够合…

Redis - 哨兵(Sentinel)

Redis 的主从复制模式下,⼀旦主节点由于故障不能提供服务,需要⼈⼯进⾏主从切换,同时⼤量 的客⼾端需要被通知切换到新的主节点上,对于上了⼀定规模的应⽤来说,这种⽅案是⽆法接受的, 于是Redis从2.8开始提…

双十二入手什么比较划算?双十二母婴好物推荐

随着双十二购物狂欢节的临近,双十二入手什么比较划算?许多准父母和有小孩的家庭都在寻找最佳的母婴产品优惠。在这个特别的日子里,各大电商平台都会推出一系列针对母婴用品的折扣和促销活动,使得这个时期成为囤货和更新宝宝生活必…

[运维][Nginx]Nginx学习(1/5)--Nginx基础

Nginx简介 背景介绍 Nginx一个具有高性能的【HTTP】和【反向代理】的【WEB服务器】,同时也是一个【POP3/SMTP/IMAP代理服务器】,是由伊戈尔赛索耶夫(俄罗斯人)使用C语言编写的,Nginx的第一个版本是2004年10月4号发布的0.1.0版本。另外值得一…

手动安装Ubuntu系统中的network-manager包(其它包同理)

自己手闲把系统中的network-manager包给删了,导致的结果就是Ubuntu系统彻底没有网络。结果再装network-manager时,没有网络根本装不了,网上的方法都试了也没用,然后就自己源码装,这篇文章就是记录一下怎么手动下载包然…

【 ElementUI 组件Steps 步骤条使用新手详细教程】

本文介绍如何使用 ElementUI 组件库中的步骤条组件完成分步表单设计。 效果图: 基础用法​ 简单的步骤条。 设置 active 属性,接受一个 Number,表明步骤的 index,从 0 开始。 需要定宽的步骤条时,设置 space 属性即…

Java项目实战II基于微信小程序的童装商城(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 基于微信小…

基于Cocos Creator开发的打砖块游戏

一、简介 Cocos简而言之就是一个开发工具,详见官方网站TypeScript简而言之就是开发语言,是JavaScript的一个超集详解官网 今天我们就来学习如何写一个打砖块的游戏,很简单的一个入门级小游戏。 二、实现过程 2.1 布局部分 首先来一个整体…

BigDecimal 详解

《阿里巴巴 Java 开发手册》中提到:“为了避免精度丢失,可以使用 BigDecimal 来进行浮点数的运算”。 浮点数的运算竟然还会有精度丢失的风险吗?确实会! 示例代码: float a 2.0f - 1.9f; float b 1.8f - 1.7f; Sys…

使用git命令实现对gitee仓库的下载、更新、上传、上传更新操作。

博客内容为使用git命令实现对gitee仓库的下载、更新、上传、上传更新操作。 1、下载(检出) 使用 git clone 命令 项目仓库地址 eg: git clone https://gitee.com/zzzzzed/ChinessChess.git 如果本地已经下载了该项目则跳过该步骤。 注意使用 git clone 首次检出需要输入用户名…

攻防世界38-FlatScience-CTFWeb

攻防世界38-FlatScience-Web 点开这个here看到一堆pdf,感觉没用&#xff0c;扫描一下 试试弱口令先 源码里有&#xff1a; 好吧0.0 试试存不存在sql注入 根本没回显&#xff0c;转战login.php先 输入1’,发现sql注入 看到提示 访问后得源码 <?php ob_start(); ?>…

JavaWeb后端开发案例——苍穹外卖day01

day1遇到问题&#xff1a; 1.前端界面打不开&#xff0c;把nginx.conf文件中localhost:80改成81即可 2.前后端联调时&#xff0c;前端登录没反应&#xff0c;application.yml中默认用的8080端口被占用&#xff0c;就改用了8081端口&#xff0c;修改的时候需要改两个地方&…

常用中间件介绍

1. RabbitMQ RabbitMQ是一个基于AMQP&#xff08;Advanced Message Queuing Protocol&#xff0c;高级消息队列协议&#xff09;的开源消息代理软件&#xff0c;实现了面向消息的中间件。它支持消息持久化、队列、交换机&#xff08;Exchange&#xff09;和绑定&#xff08;Bin…