【Spring】深入理解 Spring 事务及其传播机制

news2025/3/15 20:37:52

文章目录

  • 一、Spring 事务是什么
  • 二、Spring 中事务的实现方法
    • 2.1 Spring 编程式事务(手动)
      • 2.1.1 编程式事务的使用演示
      • 2.1.2 编程式事务存在的问题
    • 2.2 Spring 声明式事务(自动)
      • 2.2.1 @Transactional 作用范围
      • 2.2.2 @Transactional 参数说明
      • 2.2.3 @Transactional 捕获异常时回滚失效问题
      • 2.4.4 @Transactional 工作原理
    • 2.3 Spring 事务失效场景
  • 三、事务的隔离级别
    • 3.1 事务的特性回顾
    • 3.2 MySQL 的事务隔离级别
    • 3.3 Spring 事务的隔离级别
  • 四、Spring 事务的传播机制
    • 4.1 为什么需要事务传播机制
    • 4.2 事务传播机制的分类
    • 4.3 Spring 事务传播机制使用案例


一、Spring 事务是什么

在 Spring 框架中,事务(Transaction)是一种用于管理数据库操作的机制,旨在确保数据的一致性、可靠性和完整性。事务可以将一组数据库操作(如插入、更新、删除等)视为一个单独的执行单元,要么全部成功地执行,要么全部回滚。这样可以确保数据库在任何时候都保持一致的状态,即使在发生故障或错误时也能保持数据的完整性。

Spring 框架通过提供事务管理功能,使开发者能够更轻松地管理事务的边界。Spring 主要提供了两种主要的事务管理方式:

  1. 编程式事务管理:通过编写代码显式地管理事务的开始、提交和回滚操作。这种方式提供了更大的灵活性,但也需要更多的代码维护。

  2. 声明式事务管理:通过在配置中声明事务的行为,由 Spring 框架自动处理事务的边界,减少了开发者的工作量,并提高了代码的可维护性。

二、Spring 中事务的实现方法

2.1 Spring 编程式事务(手动)

2.1.1 编程式事务的使用演示

在 Spring 中,编程式事务管理是一种手动控制事务边界的方式,与 MySQL 操作事务的方法类似,它涉及三个重要的操作步骤:

  1. 开启事务(获取事务):首先需要通过获取事务管理器(例如 DataSourceTransactionManager)来获取一个事务,从而开始一个新的事务。事务管理器是用于管理事务的核心组件。

  2. 提交事务:一旦一组数据库操作成功执行,并且希望将这些更改永久保存到数据库中,就可以调用事务对象的提交方法。这将使得事务中的所有操作都被应用到数据库。

  3. 回滚事务:如果在事务处理过程中发生错误或某种条件不满足,就可以调用事务对象的回滚方法,从而撤销事务中的所有操作,回到事务开始前的状态。

在 Spring Boot 中,可以利用内置的事务管理器 DataSourceTransactionManager 来获取事务,提交或回滚事务。此外,TransactionDefinition 是用来定义事务的属性的,当获取事务时需要将 TransactionDefinition 传递进DataSourceTransactionManager以获取一个事务状态 TransactionStatus

例如,下面的代码演示了编程式事务:

@RestController
@RequestMapping("/user")
public class UserController {

    // 编程式事务
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;

    @Autowired
    private UserService userService;

    @RequestMapping("/del")
    public int delById(@RequestParam("id") Integer id) {
        if (id == null || id < 0) return 0;
        // 1. 开启事务
        TransactionStatus transactionStatus = null;
        int res = 0;
        try {
            transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);

            // 2. 业务操作 —— 删除用户
            res = userService.delById(id);
            System.out.println("删除: " + res);

            // 3. 提交、回滚事务
            // 提交事务
            dataSourceTransactionManager.commit(transactionStatus);
        } catch (Exception e) {
            e.printStackTrace();

            // 回滚事务
            if (transactionStatus != null) {
                dataSourceTransactionManager.rollback(transactionStatus);
            }
        }
        return res;
    }
}

这段代码展示了如何通过编程式事务管理在Spring Boot中处理用户删除操作。编程式事务允许我们在代码中明确地控制事务的边界,以及在需要时手动提交或回滚事务。

2.1.2 编程式事务存在的问题

通过上面的示例代码可以发现,编程式事务虽然提供了更大的灵活性,但也存在一些问题和挑战:

  1. 代码冗余和可读性差: 编程式事务需要在代码中显式地添加事务管理的逻辑,导致代码变得冗余且难以维护。每次需要使用事务的地方都需要重复编写事务开启、提交和回滚的代码,降低了代码的可读性。

  2. 事务边界控制复杂: 开发者需要手动管理事务的边界,确保事务的开始、提交和回滚都在正确的位置。这可能会导致遗漏事务管理的代码,从而影响数据的一致性。

  3. 事务传播和嵌套问题: 在涉及多个方法调用的场景中,手动控制事务的传播和嵌套关系可能变得复杂。需要开发者确保事务在各个方法间正确传播,同时处理好嵌套事务的问题。

  4. 异常处理繁琐: 编程式事务需要在异常处理时手动进行回滚操作,如果异常处理不当,事务可能无法正确回滚,导致数据不一致。

  5. 可维护性差: 随着项目的发展,业务逻辑可能会变得更加复杂,可能需要频繁地修改事务管理的代码。这会增加代码维护的难度,可能导致错误的引入。

  6. 不利于横向扩展: 编程式事务难以支持横向扩展,因为事务管理的代码紧耦合在业务逻辑中,扩展时可能需要修改大量代码。

相比之下,声明式事务管理通过在方法上添加注解或在配置文件中进行声明,使事务管理与业务逻辑分离,提供了更好的代码组织和可维护性。声明式事务可以在切面中自动处理事务的开始、提交和回滚,从而减轻了开发者的工作负担。

所以,大多数情况下,建议使用声明式事务管理来处理事务,特别是在简化事务逻辑和提高代码可读性方面更加有效。

2.2 Spring 声明式事务(自动)

声明式事务的实现非常简单,只需要在需要的方法上添加 @Transactional 注解就可以轻松实现,无需手动开启或提交事务。

  • 当进入被注解的方法时,Spring 会自动开启一个事务。
  • 方法执行完成后,如果没有抛出未捕获的异常,事务会自动提交,保证数据的一致性。
  • 然而,如果方法在执行过程中发生了未经处理的异常,事务会自动回滚,以确保数据库的完整性和一致性。

这种方式大大简化了事务管理的编码,减少了手动处理事务的繁琐操作,提高了代码的可读性和可维护性。例如下面的代码实现:

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    // 声明式事务
    @RequestMapping("/del")
    @Transactional
    public int delById(Integer id) {
        if (id == null || id < 0) return 0;
        int result = userService.delById(id);
        return result;
    }
}

在这个示例中,delById 方法使用了 @Transactional 注解,表示该方法需要受到声明式事务的管理。在这个方法内部,首先检查了传入的 id,如果为负数则直接返回结果。然后,调用了 userService.delById(id) 方法,删除了指定用户。在方法结束时,事务会自动提交。

同时,如果在执行过程中发生了未处理的异常,事务将会自动回滚,以保持数据库的一致性。这种方式简化了事务管理,提高了代码的可读性和可维护性。

2.2.1 @Transactional 作用范围

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

  • 当修饰方法时:需要注意它只能应用到 public 访问修饰符的方法上,否则注解不会生效。通常推荐在方法级别使用 @Transactional

  • 当修饰类时:表示该注解对于类中所有的 public 方法都会生效。如果在类级别添加了 @Transactional,那么该类中所有的公共方法都将自动应用事务管理。

一般来说,推荐将 @Transactional 注解应用在方法级别,以便更精确地控制事务的范围,从而避免不必要的事务开销。如果类中的所有方法都需要事务管理,那么将注解应用在类级别是一个更方便的选择。

2.2.2 @Transactional 参数说明

通过查看 @Transactional 的源码,可以发现它支持多个参数,用来配置事务的行为。

以下是对其中参数说明:

参数名称类型默认值描述
valueString“”事务管理器的名称,与 transactionManager 等效。
transactionManagerString“”事务管理器的名称,与 value 等效。
labelString[]空数组事务标签,暂无具体用途。
propagationPropagationPropagation.REQUIRED事务的传播行为,默认为 REQUIRED。
isolationIsolationIsolation.DEFAULT事务的隔离级别,默认为数据库默认隔离级别。
timeoutint-1事务的超时时间,单位为秒。-1 表示没有超时限制。
timeoutStringString“”事务的超时时间的字符串表示,与 timeout 等效。
readOnlybooleanfalse是否将事务设置为只读,默认为 false。
rollbackForClass<? extends Throwable>[]空数组触发回滚的异常类型。
rollbackForClassNameString[]空数组触发回滚的异常类型的类名字符串。
noRollbackForClass<? extends Throwable>[]空数组不触发回滚的异常类型。
noRollbackForClassNameString[]空数组不触发回滚的异常类型的类名字符串。

这些参数提供了对事务行为的灵活配置,可以根据具体业务需求来调整事务的传播、隔离、超时和回滚策略等。

2.2.3 @Transactional 捕获异常时回滚失效问题

针对于上述的实例代码,现在代码中间模拟实现一个异常,观察会出现什么情况:

@RequestMapping("/del")
@Transactional
public int delById(Integer id) {
    if (id == null || id < 0) return 0;
    int result = userService.delById(id);
    System.out.println(result);
    try {
        int num = 10 / 0;
    } catch (Exception e) {
        // 如果直接处理异常,则不会回滚
      	e.printStackTrace();
    }
    return result;
}

通过浏览器访问,发现服务器成功捕获了异常:

但是事务却没有回滚,对应的用户数据还是被删除了:

其原因在于:

在异常处理中直接捕获了异常并进行了处理,从而导致事务回滚失效。默认情况下,@Transactional 注解会在方法内抛出 RuntimeException 及其子类异常时触发事务回滚。然而,当自己在 catch 块内捕获异常并处理时,Spring 无法感知到异常,从而无法触发事务回滚。

解决方法:

对于这个问题的解决方法大致可以分为两种:

  1. 将捕获的异常再次抛出:
e.printStackTrace();
throw e;

这种方法通过重新抛出异常,使得 Spring 能够捕获异常并触发事务回滚。在异常发生后,事务将被回滚,确保之前的数据库操作不会生效,从而保持数据的一致性。

  1. 使用 TransactionAspectSupport 手动回滚事务:
e.printStackTrace();
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

这种方法利用了 Spring 提供的 TransactionAspectSupport 类来手动设置事务回滚状态。在捕获异常后,通过调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(),可以将当前事务设置为回滚状态,从而达到回滚事务的效果。这种方法更加灵活,可以在需要的时候手动控制事务的回滚。

无论选择哪种方法,都可以在异常发生时触发事务回滚,保障数据的完整性和一致性。选择哪种方法取决于具体的代码逻辑和需求。

2.4.4 @Transactional 工作原理

@Transactional 注解的工作原理基于 Spring AOP(面向切面编程)和事务管理器。它利用了 Spring 框架的代理机制来实现事务管理。

当一个被 @Transactional 注解修饰的方法被调用时,Spring 会创建一个代理对象来包装这个方法。代理对象会在方法执行之前和之后添加事务管理的逻辑,以确保事务的开始、提交和回滚。这个过程是通过 AOP 技术实现的。

具体来说,以下是 @Transactional 注解的工作流程:

  1. 事务代理的创建: Spring 在运行时会为每个被 @Transactional 注解修饰的类创建一个代理对象。这个代理对象会包含事务管理的逻辑。

  2. 方法调用: 当调用一个被 @Transactional 注解修饰的方法时,实际上是通过代理对象来调用。

  3. 事务切面的触发: 在代理对象中,事务切面会在方法执行前后被触发。在方法执行前,切面会开启一个事务;在方法执行后,切面会根据方法的执行情况决定是提交事务还是回滚事务。

  4. 事务管理器的使用: 切面会通过事务管理器来控制事务。事务管理器负责实际的事务管理操作,如开启、提交和回滚事务。

  5. 事务控制: 如果方法正常执行完毕,切面会通知事务管理器提交事务。如果方法在执行过程中抛出异常,切面会通知事务管理器回滚事务。

总体来说,@Transactional 注解的工作原理是通过代理和切面来实现事务管理,将事务的控制与业务逻辑分离,使代码更加模块化和可维护。这也是声明式事务管理的核心机制之一。

2.3 Spring 事务失效场景

在某些情况下,Spring 中的事务可能会失效,导致事务不生效或不按预期执行。以下是一些可能导致事务失效的场景:

  1. public 修饰的方法: 默认情况下,@Transactional 注解只对 public 访问修饰符的方法起作用。如果你在非 public 方法上添加了 @Transactional 注解,事务可能不会生效。

  2. timeout 超时: 如果事务执行的时间超过了设置的 timeout 值,事务可能会被强制回滚。这可能会导致事务不按预期执行,特别是当事务需要执行较长时间的操作时。

  3. 代码中有 try/catch 如果在方法内部捕获并处理了异常,Spring 将无法感知到异常,从而无法触发事务回滚。这可能导致事务在异常发生时不会回滚。

  4. 调用类内部带有 @Transactional 的方法: 当一个类内部的方法被调用时,它的 @Transactional 注解可能不会生效。这是因为 Spring 默认使用基于代理的事务管理,直接在类内部调用方法不会经过代理,从而事务管理可能不会生效。

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    public int del(Integer id){
        return delById(id);
    }
    
    // 声明式事务
    @RequestMapping("/del")
    @Transactional
    public int delById(Integer id) {
        if (id == null || id < 0) return 0;
        int result = userService.delById(id);
        return result;
    }
}
  1. 数据库不支持事务: 如果你的数据库不支持事务,例如使用了某些特殊的数据库引擎,事务可能无法正常工作。在这种情况下,应该确保使用支持事务的数据库引擎。

三、事务的隔离级别

3.1 事务的特性回顾

在数据库中,事务具有以下四个重要的特性,通常被称为 ACID 特性:

  1. 原子性(Atomicity): 事务被视为一个不可分割的操作单元,要么全部执行成功,要么全部失败回滚。

  2. 一致性(Consistency): 事务使数据库从一个一致的状态转变到另一个一致的状态,保证数据的完整性和一致性。

  3. 隔离性(Isolation): 并发执行的事务之间应该互不影响,每个事务都感觉自己在独立地操作数据。

  4. 持久性(Durability): 一旦事务提交,其对数据库的修改就应该是永久性的,即使发生系统崩溃也不应该丢失。

3.2 MySQL 的事务隔离级别

MySQL 支持以下四个事务隔离级别,用于控制多个事务之间的相互影响程度:

  1. 读未提交(Read Uncommitted): 允许一个事务读取另一个事务尚未提交的数据。这是最低的隔离级别,可能会导致脏读、不可重复读和幻读的问题。

  2. 读已提交(Read Committed): 允许一个事务只能读取另一个事务已经提交的数据。这可以避免脏读,但可能会出现不可重复读和幻读的问题。

  3. 可重复读(Repeatable Read): 保证在同一个事务中多次读取同样记录的结果是一致的,即使其他事务对该记录进行了修改。这可以避免脏读和不可重复读,但可能出现幻读。

  4. 串行化(Serializable): 最高的隔离级别,确保每个事务都完全独立运行,避免了脏读、不可重复读和幻读问题,但可能影响并发性能。

以下是事务四个隔离级别对应的脏读、不可重复读、幻读情况:

隔离级别脏读不可重复读幻读
读未提交
读已提交×
可重复读××
串行化×××
  • √ 表示可能出现该问题。
  • × 表示该问题不会出现。

3.3 Spring 事务的隔离级别

Spring 通过 @Transactional 注解中的 isolation 参数来支持不同的事务隔离级别。Isolation的源码如下:

可以使用这些枚举值来设置隔离级别:

  • Isolation.DEFAULT:使用数据库的默认隔离级别。
  • Isolation.READ_UNCOMMITTED:读未提交。
  • Isolation.READ_COMMITTED:读已提交。
  • Isolation.REPEATABLE_READ:可重复读。
  • Isolation.SERIALIZABLE:串行化。

例如,指定 Spring 事务的隔离级别为 DEFAULT

@RequestMapping("/del")
@Transactional(isolation = Isolation.DEFAULT)
public int delById(Integer id) {
    if (id == null || id < 0) return 0;
    int result = userService.delById(id);
    return result;
}

通过选择合适的事务隔离级别,可以在并发环境中控制事务之间的相互影响程度,从而避免数据不一致的问题。不同的隔离级别在性能和数据一致性方面有不同的权衡,开发人员需要根据具体的业务需求来选择合适的隔离级别。

四、Spring 事务的传播机制

4.1 为什么需要事务传播机制

在复杂的应用场景中,一个事务操作可能会调用多个方法或服务。这些方法可能需要独立地进行事务管理,但又需要协同工作,以保持数据的一致性和完整性。这时就需要引入事务传播机制。

事务传播机制定义了多个事务方法之间如何协同工作,如何共享同一个事务,以及在嵌套事务中如何进行隔离和提交。通过事务传播机制,可以确保多个事务方法在执行时能够按照一定的规则进行协调,避免数据不一致的问题。

4.2 事务传播机制的分类

Spring 定义了七种事务传播行为,用于控制多个事务方法之间的交互。这些传播行为可以在 @Transactional 注解中的 propagation 参数中进行设置。以下是这些传播行为:

  1. REQUIRED(默认): 如果当前存在事务,就加入到当前事务中;如果没有事务,就创建一个新的事务。这是最常用的传播行为。

  2. SUPPORTS: 如果当前存在事务,就加入到当前事务中;如果没有事务,就以非事务方式执行。

  3. MANDATORY: 如果当前存在事务,就加入到当前事务中;如果没有事务,就抛出异常。

  4. REQUIRES_NEW: 无论当前是否存在事务,都创建一个新的事务。如果当前存在事务,则将当前事务挂起。

  5. NOT_SUPPORTED: 以非事务方式执行,如果当前存在事务,就将当前事务挂起。

  6. NEVER: 以非事务方式执行,如果当前存在事务,就抛出异常。

  7. NESTED: 如果当前存在事务,就在一个嵌套的事务中执行;如果没有事务,就与 REQUIRED 一样。

以上 7 种传播行为,可以根据是否支持当前事务分为以下 3 类:

4.3 Spring 事务传播机制使用案例

REQUIRED 和 NESTED 传播机制的事务演示:

控制层 ControllerUserController

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/add") // /add?username=lisi&password=123456
    @Transactional(propagation = Propagation.NESTED)
    // Transactional(propagation = Propagation.REQUIRED)
    //@Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(@RequestParam("username") String username, @RequestParam("password") String password) {
        if (null == username || null == password || "".equals(username) || "".equals(password)) {
            return 0;
        }

        int result = 0;

        // 用户添加操作
        UserInfo user = new UserInfo();
        user.setUsername(username);
        user.setPassword(password);

        result = userService.add(user);


        try {
            int num = 10 / 0; // 加入事务:外部事务回滚,内部事务也会回滚
        } catch (Exception e) {
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }

        return result;
    }
}

服务层ServiceUserService

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private LogService logService;

    public int delById(Integer id){
        return userMapper.delById(id);
    }

    @Transactional(propagation = Propagation.NESTED)
    // Transactional(propagation = Propagation.REQUIRED)
    //@Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(UserInfo user){
        // 添加用户信息
        int addUserResult = userMapper.add(user);
        System.out.println("添加用户结果:" + addUserResult);

        //添加日志信息
        Log log = new Log();
        log.setMessage("添加用户信息");
        logService.add(log);

        return addUserResult;
    }
}

服务层ServiceLogService

@Service
public class LogService {
    @Autowired
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.NESTED)
    // Transactional(propagation = Propagation.REQUIRED)
    //@Transactional(propagation = Propagation.REQUIRES_NEW)
    public int add(Log log){
        int result =  logMapper.add(log);
        System.out.println("添加日志结果:" + result);
        // 模拟异常情况
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            // 加入事务:内部事务回滚,外部事务也会回滚,并且会抛异常
            // 嵌套事务:内部事务回滚,不影响外部事务
            e.printStackTrace();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }
}

在事务传播机制中,REQUIREDNESTED 是两种不同的传播行为,它们在事务的嵌套、回滚以及对外部事务的影响等方面有所不同。通过上面代码的演示,可以得出 REQUIREDNESTED 之间的主要区别如下:

  1. 嵌套性质:

    • REQUIRED:内部方法与外部方法共享同一个事务,内部方法的事务操作是外部方法事务的一部分。
    • NESTED:内部方法创建一个嵌套事务,它是外部事务的子事务,具有独立的事务状态,内部事务的回滚不会影响外部事务。
  2. 回滚行为:

    • REQUIRED:如果内部方法抛出异常或设置回滚,会导致整个外部事务回滚,包括内部方法和外部方法的操作。
    • NESTED:如果内部方法抛出异常或设置回滚,只会回滚内部事务,而外部事务仍然可以继续执行。
  3. 影响外部事务:

    • REQUIRED:内部方法的事务操作会影响外部事务的状态,内部方法回滚会导致外部事务回滚。
    • NESTED:内部方法的事务操作不会影响外部事务的状态,内部方法回滚不会影响外部事务的提交或回滚。
  4. 支持性:

    • REQUIRED:较为常用,适用于将多个方法的操作作为一个整体进行事务管理的情况。
    • NESTED:在某些数据库中不支持,需要数据库支持保存点(Savepoint)的功能。

总的来说,REQUIRED 适用于需要将多个方法的操作作为一个整体事务管理的情况,而 NESTED 适用于需要在内部方法中创建嵌套事务的情况,保持内部事务的独立性,不影响外部事务。选择使用哪种传播行为取决于业务需求和数据库的支持情况。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

化繁为简,使用Hibernate Validator实现参数校验

前言 在之前的悦享校园的开发中使用了SSM框架&#xff0c;由于当时并没有使用参数参数校验工具&#xff0c;方法的入参判断使用了大量的if else语句&#xff0c;代码十分臃肿&#xff0c;因此最近在重构代码时&#xff0c;将框架改为SpringBoot后&#xff0c;引入了Hibernate V…

HCIP MPLS实验

MPLS实验 拓扑MPLS VPN配置PE与PE间建立MP-BPG邻居关系R7访问R2,R3,R4环回 拓扑 MPLS VPN配置 首先可以用OSPF协议将R2,R3,R4跑通&#xff0c;然后配置MPLS [R2]mpls lsr-id 2.2.2.2 定义MPLS的router-id&#xff0c;要为本地设备的真实ip地址&#xff0c;且邻居可达&#…

机器学习算法之-逻辑回归(2)

为什么需要逻辑回归 拟合效果太好 特征与标签之间的线性关系极强的数据&#xff0c;比如金融领域中的 信用卡欺诈&#xff0c;评分卡制作&#xff0c;电商中的营销预测等等相关的数据&#xff0c;都是逻辑回归的强项。虽然现在有了梯度提升树GDBT&#xff0c;比逻辑回归效果更…

【学习FreeRTOS】第8章——FreeRTOS列表和列表项

1.列表和列表项的简介 列表是 FreeRTOS 中的一个数据结构&#xff0c;概念上和链表有点类似&#xff0c;列表被用来跟踪 FreeRTOS中的任务。列表项就是存放在列表中的项目。 列表相当于链表&#xff0c;列表项相当于节点&#xff0c;FreeRTOS 中的列表是一个双向环形链表列表的…

【大数据之Kafka】三、Kafka生产者之消息发送流程及同步异步发送API

将外部传送给过来的数据发送到kafka集群。 1 发送原理 &#xff08;1&#xff09;创建main()线程&#xff0c;创建producer对象&#xff0c;调用send方法&#xff0c;经过拦截器&#xff08;可选&#xff09;、序列化器、分区器。 &#xff08;2&#xff09;分区器将数据发送…

java面试题(16):Mysql一致性视图是啥时候建立的

1 演示错误案例 先给大家来一个错误演示。 我们打开两个会话窗口&#xff0c;默认情况下隔离级别是可重复读&#xff0c;我们来看下&#xff1a; 首先在 A 会话中查看当前 user 表&#xff0c;查看完成后开启事务&#xff1a; 可以看到id3的数据sex是男。 接下来在 B 会话中…

场景入门12----关卡切换和流送

在游戏时往往需要切换关卡&#xff0c;有两种方法&#xff0c;关卡切换和推送。关卡切换的方法是进入了一个新的地图&#xff0c;这时人物的值都复原了&#xff0c;一般都是在一个地图上推送关卡。 关卡切换 首先&#xff0c;文件新建一个新关卡&#xff0c;命名。找到之前制…

地址解析协议-ARP

ARP协议 无论网络层使用何种协议&#xff0c;在实际网络的链路上传输数据帧时&#xff0c;最终必须使用硬件地址 地址解析协议&#xff08;Address Resolution Protocol&#xff0c;ARP&#xff09;&#xff1a;完成IP地址到MAC地址的映射&#xff0c;每个主机都有一个ARP高速缓…

C语言刷题训练DAY.4

1.计算体重指数 解题思路&#xff1a; 这里我们只需要按照他的要求写出公式。 注意&#xff1a;身高要换算成米&#xff0c;打印的结构是个浮点数&#xff0c;打印的格式要相对应 解题代码&#xff1a; #include<stdio.h> int main() {int weight 0;int height 0;…

Python自动化小技巧18——自动化资产月报(word设置字体表格样式,查找替换文字)

案例背景 每月都要写各种月报&#xff0c;经营管理月报&#xff0c;资产月报.....这些报告文字目标都是高度相似的&#xff0c;只是需要替换为每个月的实际数据就行&#xff0c;如下&#xff1a; (打码是怕信息泄露.....) 可以看到&#xff0c;这个报告的都是高度模板化&…

浏览器 - 事件循环机制详解

目录 1&#xff0c;浏览器进程模型进程线程浏览器的进程和线程1&#xff0c;浏览器进程2&#xff0c;网络进程3&#xff0c;渲染进程 2&#xff0c;渲染主线程事件循环异步同步 JS 为什么会阻塞渲染任务优先级 3&#xff0c;常见面试题1&#xff0c;如何理解 js 的异步2&#x…

时序预测 | MATLAB实现基于KNN K近邻的时间序列预测-递归预测未来(多指标评价)

时序预测 | MATLAB实现基于KNN K近邻的时间序列预测-递归预测未来(多指标评价) 目录 时序预测 | MATLAB实现基于KNN K近邻的时间序列预测-递归预测未来(多指标评价)预测结果基本介绍程序设计参考资料 预测结果 基本介绍 基于KNN K近邻的时间序列预测-递归预测未来(多指标评价) …

数据集成与流动优化:解锁企业数据的无限潜力

在当今数字化时代&#xff0c;企业拥有海量数据&#xff0c;这些数据散落在不同部门、系统和平台之间&#xff0c;形成了所谓的“数据孤岛”。要想实现数据的最大化价值&#xff0c;就必须解决数据集成与流动的挑战。本文将深入探讨数据集成与流动优化的重要性&#xff0c;以及…

833-字符串中查找与替换

题目描述&#xff1a; 你会得到一个字符串 s (索引从 0 开始)&#xff0c;你必须对它执行 k 个替换操作。替换操作以三个长度均为 k 的并行数组给出&#xff1a;indices, sources, targets。 要完成第 i 个替换操作: 检查 子字符串 sources[i] 是否出现在 原字符串 s 的索…

POSTGRESQL 关于安装中自动启动的问题 详解

开头还是介绍一下群&#xff0c;如果感兴趣Polardb ,mongodb ,MySQL ,Postgresql ,redis &#xff0c;SQL SERVER ,ORACLE,Oceanbase 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请加 liuaustin3微信号 &…

使用SpringBoot + Thymeleaf 完成简单的用户登录

&#x1f600;前言 本篇博文是关于Thymeleaf 的综合案例&#xff0c; 使用SpringBoot Thymeleaf 完成简单的用户登录-列表功能&#xff0c;希望你能够喜欢&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨…

数据结构--最短路径 Floyd算法

数据结构–最短路径 Floyd算法 F l o y d 算法&#xff1a;求出每⼀对顶点之间的最短路径 \color{red}Floyd算法&#xff1a;求出每⼀对顶点之间的最短路径 Floyd算法&#xff1a;求出每⼀对顶点之间的最短路径 使⽤动态规划思想&#xff0c;将问题的求解分为多个阶段 对于n个顶…

re学习(31)BUUCTF-xx(多层加密)

参考文章&#xff1a;【BUUCTF逆向 [2019红帽杯]xx】_nb_What_DG的博客-CSDN博客 re学习笔记&#xff08;26&#xff09;BUUCTF-re-[2019红帽杯]xx_Forgo7ten的博客-CSDN博客 还有B站 水番正文 IDA64位载入 shiftF12查看字符串 交叉引用找到关键代码 使用findcrypt插件找到…

H13-922题库 HCIP-GaussDB-OLAP V1.5

**H13-922 V1.5 GaussDB(DWS) OLAP题库 华为认证GaussDB OLAP数据库高级工程师HCIP-GaussDB-OLAP V1.0自2019年10月18日起&#xff0c;正式在中国区发布。当前版本V1.5 考试前提&#xff1a; 掌握基本的数据库基础知识、掌握数据仓库运维的基础知识、掌握基本Linux运维知识、…

互联网发展历程:速度与效率,交换机的登场

互联网的演进就像一场追求速度与效率的竞赛&#xff0c;每一次的技术升级都为我们带来更快、更高效的网络体验。然而&#xff0c;在网络的初期阶段&#xff0c;人们面临着数据传输速度不够快的问题。一项关键的技术应运而生&#xff0c;那就是“交换机”。 速度不足的困境&…