【Spring】Spring事务和事务传播机制

在这里插入图片描述

文章目录

  • 什么是事务
  • 事务的操作
  • Spring 中事务的实现
    • Spring编程式事务
    • Spring 声明式事务 @Transactional
      • @Transactional作用
      • @Transactional 详解
        • rollbackFor
        • 事务隔离级别
        • Spring 事务隔离级别
        • Spring 事务传播机制

什么是事务

事务(Transaction)是一个程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功,要么失败)。在计算机术语中,事务通常是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL、C++、Java)书写的用户程序的执行所引起,并用形如BeginTransaction和EndTransaction语句(或函数调用)来界定。

前面我们学习 MySQL 的时候,也为大家介绍了关于事务方面的知识,事务具有以下特性:

  1. 原子性(Atomicity):事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。原子性可以消除系统处理操作子集的可能性。
  2. 一致性(Consistency):事务在完成时,必须使所有的数据都保持一致状态。事务可以保证数据库的完整性,避免因各种原因而导致数据库的内容不一致,产生错误的数据。
  3. 隔离性(Isolation):事务处理过程中的中间状态对其他事务是透明的。事务隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
  4. 持续性(Durability):持续性是指事务一旦提交,它对数据库中数据的改变是永久性的。接下来的操作或故障不应对其有任何影响。

当我们在进行转账或者购物的时候,往往需要用到事务操作,为什么呢?假设我向别人转账 100 元,我钱已经转过去了,但是在对方接收的时候出现了问题,那么我自己账户上的余额少了 100,但是对方的账户上却没有收到我的 100,这种现象是绝对不可以出现的。有了事务的插足,如果在我们钱已经转出去了情况,但是在对方收的过程中发生问题的话,事务就会进行回滚,发出方的 100 元就不会扣掉。

事务的操作

事务的操作主要为下面三个部分:

  1. 开启事务:start transaction/begin(一组操作前开启事务)
  2. 提交事务:commit(这组操作全部成功,提交事务)
  3. 回滚事务:rollback(这组操作中间任何一个操作出现异常,并且这个异常没有被处理,就是回滚事务)

Spring 中事务的实现

在 Spring 中实现事务的方式有两种:

  1. 编程式事务(手动写代码操作事务)
  2. 声明式事务(利用注解自动开启事务和提交事务)

这篇文章为大家介绍事务,主要通过操作数据库的操作来体现,所以我们先准备两个表:

-- 创建数据库
DROP DATABASE IF EXISTS trans_test;
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;
-- 用户表
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';

配置 MyBatis:

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/**Mapper.xml

为对应的表创建 model:

import lombok.Data;

import java.util.Date;

@Data
public class UserInfo {
    private int id;
    private String userName;
    private String password;
    private Date createTime;
    private Date updateTime;
}
import lombok.Data;

import java.util.Date;

@Data
public class LogInfo {
    private int id;
    private String userName;
    private String op;
    private Date createTime;
    private Date updateTime;
}

MyBatis 操作数据库:

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserInfoMapper {
    @Insert("insert into user_info (·user_name·,`password`) values (#{userName},#{password})")
    Integer insert(String userName, String password);
}
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface LogInfoMapper {
    @Insert("insert into log_info (`name`, `op`) values (#{name},#{op})")
    Integer insertLog(String name,String op);
}

Service 层:

import com.example.springtransaction.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserInfoService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    public void registryUser(String name, String password) {
        userInfoMapper.insert(name, password);
    }
}
import com.example.springtransaction.mapper.LogInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class LogInfoService {
    @Autowired
    private LogInfoMapper logInfoMapper;

    public void insertLog(String name, String op) {
        logInfoMapper.insertLog(name,op);
    }
}

Contoller 层:

import com.example.springtransaction.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserInfoController {
    @Autowired
    private UserInfoService userInfoService;

    @RequestMapping("/registry")
    public String  registry(String name, String password) {
        userInfoService.registryUser(name, password);
        return "注册成功";
    }
}
import com.example.springtransaction.service.LogInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/log")
@RestController
public class LogInfoController {
    @Autowired
    private LogInfoService logInfoService;

    @RequestMapping("/insert")
    public String insertLog(String name, String op) {
        logInfoService.insertLog(name, op);
        return "日志插入成功";
    }
}

Spring编程式事务

Spring 手动操作事务,有三个重要步骤:

  1. 开启事务(获取事务)
  2. 提交事务
  3. 回滚事务

在 Spring 中如何获取到事务呢?

要想获取到事务,我们需要借助 Spring 内置的两个对象:

  • DataSourceTransactionManager:事务管理器,用来获取事务(开启事务),提交或回滚事务
  • TransactionDefinition:事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从而获得一个事务 TransactionStatus
import com.example.springtransaction.service.UserInfoService;
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 UserInfoController {
    @Autowired
    private UserInfoService userInfoService;

    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/registry")
    public String  registry(String name, String password) {
        //开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        userInfoService.registryUser(name, password);
        //提交事务
        dataSourceTransactionManager.commit(transactionStatus);
        return "注册成功";
    }
}

访问 127.0.0.1:8080/user/registry 之后,我们查看日志:

在这里插入图片描述

观察表可以发现数据插入成功:

在这里插入图片描述

这个是事务提交成功的日志,然后我们再来看看当事务回滚之后会出现什么日志:

//回滚事务
dataSourceTransactionManager.rollback(transactionStatus);

在这里插入图片描述
可以看到,当发生事务回滚的时候,就只有打开 sqlSession 和关闭 sqlSession 的操作,没有 commit 提交的操作,并且观察数据库可以发现,并没有插入数据:

在这里插入图片描述
通过编程式实现事务操作比较复杂,而使用声明式事务就简单很多。

Spring 声明式事务 @Transactional

Spring 声明式实现事务很简单,只需要加上 @Transactional 注解就可以实现了,无需手动开启和提交事务,进入方法的时候会自动开始事务,中途发生了未处理的异常就会自动回滚事务。跟前面的 AOP 统一功能处理是一样的,方法开始前干什么,结束后干什么,抛出异常后干什么。

import com.example.springtransaction.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/trans")
@RestController
public class TransactionalController {
    @Autowired
    private UserInfoService userInfoService;

    @Transactional
    @RequestMapping("/registry")
    public String registry(String name, String password) {
        userInfoService.registryUser(name,password);
        return "注册成功";
    }
}

在这里插入图片描述
在这里插入图片描述
我们在这个方法中制造出异常,看是否会发生事务的回滚:

System.out.println(10/0);

在这里插入图片描述

没有提交事务就说明发生了事务的回滚。

在这里插入图片描述

@Transactional作用

@Transactional 可以修饰方法,也可以修饰类:

  • 修饰方法时:只有修饰 public 方法的时候才会生效(修饰其他权限的方法的时候不会报错,但是也不会生效)【推荐】
  • 修饰类时:对 @Transactional 修饰的类中所有的 public 方法都生效

当类/方法被 @Transactional 修饰的时候,在目标方法执行之前,就会自动开启事务,方法执行结束之后,会自动结束事务。但是如果在方法执行的过程中出现了异常,并且异常没有被正确捕获的话,就会进行事务的回滚操作;如果这个异常被成功捕获,那么方法就会被认为是正常执行,事务就会被正常提交。

我们对上面制造的异常进行捕获:

try {
    System.out.println(10/0);
} catch (Exception e) {
    e.printStackTrace();
}

在这里插入图片描述

事务被提交,并且数据库的插入操作成功:

在这里插入图片描述

如果我们想要自己控制事务的回滚,有两种方式可以达到:

(1)重新抛出异常:

try {
    System.out.println(10/0);
} catch (Exception e) {
    throw e;
}

在这里插入图片描述
在这里插入图片描述
(2)手动回滚事务:

首先我们需要通过 TransactionAspectSupport.currentTransactionStatus()方法来获取到当前事务,然后再调用 setRollbackOnly 进行事务的回滚操作:

try {
    System.out.println(10/0);
} catch (Exception e) {
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}

@Transactional 详解

上面我们了解了 @Transactional 的基本使用,那么接下来我们将详细学习一下 @Transactional 注解。

在这里插入图片描述
@Transactional 注解中的属性有很多,但是我们主要学习这三个属性:

  1. rollbackFor:异常回滚属性。指定能够触发事务回滚的异常类型,默认可以发生事务回滚的异常类型是 RuntimeException 及其子类以及 Error ,可以指定多个异常类型
  2. Isolation:事务的隔离级别。默认值为 Isolation.DEFAULT
  3. propagation:事务的传播机制。默认值为 Propagation.REQUIRED
rollbackFor

@Transactional 默认只会在发生运行时异常和 Error 的时候才会发生事务的回滚操作:

在这里插入图片描述
假设我们抛出的异常是非运行时异常:

try {
    System.out.println(10/0);
} catch (Exception e) {
    throw new IOException();
}

在这里插入图片描述
在这里插入图片描述

可以看到,当抛出的异常类型是非运行异常的时候,不会发生事务的回滚。

如果我们想指定,发生非运行异常的时候也能进行事务回滚的操作的话,我们就需要配置 @Transactional 注解中的 rollbackFor 属性:

@Transactional(rollbackFor = Exception.class)

在这里插入图片描述
抛出非运行时异常的时候,也进行了事务的回滚操作。

结论:

  • 在 Spring 的事务管理中,默认只在遇到运行时异常 RuntimeException 和 Error 的时候才会回滚
  • 如果需要回滚指定类型的异常,可以通过 rollbackFor 属性来指定
事务隔离级别

事务的隔离级别有下面几种:

  1. 未提交读(Read Uncommitted):最低的事务隔离级别,允许读取尚未提交的事务数据。这意味着可能会出现脏读、不可重复读和幻读的情况。
  • 该隔离级别可以读到其他事务未提交的数据,但是如果其他事务发生了事务回滚的话,那么我们读到的数据就是“脏数据”,这个问题被称为脏读
  1. 提交读(Read Committed):在事务执行过程中,只允许读取已经提交的数据。这样可以避免脏读问题,但仍然可能出现不可重复读和幻读。
  • 该隔离级别可以避免出现脏读的情况,但是由于该隔离级别可以读取到其他事务已提交的数据,所以在不同时间段读取到的数据可能是不同的,这种现象叫做不可重复读
  1. 可重复读(Repeatable Read):在这个级别中,事务在其生命周期内可以多次读取同一个数据,而不会看到其他事务对该数据的修改。这可以避免脏读和不可重复读问题,但仍然可能出现幻读。
  • 假设该事务级别的事务正在读取数据,并且在此期间,其他事务又插入了新的数据,因为该隔离级别下读取到的数据都是一样的,所以就无法读取到新插入的这条数据,当前事务再插入这条数据的时候,因为唯一主键的约束,就无法成功插入,,但是在当前事务中又查询不到这条数据,又插入不成功,所以就出现了幻读的情况
  1. 可串行化(Serializable):最高的事务隔离级别,通过强制事务串行执行,避免了脏读、不可重复读和幻读问题。但这种级别的性能开销较大,因为事务必须串行执行。

给大家举个例子:假设我要考试了,但是考试前两天我去老师办公室叫作业,我看见老师电脑上显示的是 2024年高数期末考试试卷草稿,所以我就将试卷给拍了下来,然后回到了寝室就只琢磨复习试卷上出现的题目就,等到考试那天我信心满满的走进考场,但是当我看到试卷的那一刻,我懵了,很多题目都不一样。这是因为我那天看到的只是草稿,在我走后老师又对其进行了修改,这就是脏读的问题。

假设我工厂生产机器,要生产两批不同的机器,A机器生产100台,B机器生产50台,先生产的是 A 机器,我按照给定的图纸来制造,当制造了 95 台 A 机器的时候,上面就将 B 机器的图纸传过来了,但是我不知道,我知道的就是按照图纸来造,这样就导致了 A 机器制造的数量不够,这就是不可重复读的问题。

假设公司让我们一个团队的人进行数据库的增加操作,我的操作就是先查询一遍数据库,看看还有那些数据需要插入,然后我后面插入的时候也是看第一遍查询的结果吗,这样我一个人做的话,是不会出现什么问题的,但是如果跟我同一个团队的人也在执行同样的操作的话,他插入了我将要插入的数据,但是我还是按照第一编查询的结果来,那么这个数据就不能成功插入,我再查询,还是第一遍的结果,我说这条数据我没插入啊,为什么插入不进去呢?这就是幻读的问题。

事务隔离级别脏读不可重复读幻读
读未提交
读已提交×
可重复读××
串行化×××

随着隔离级别的提高,效率也会降低。

Spring 事务隔离级别

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

  1. Isolation.DEFAULT:以连接的数据库事务隔离级别为主
  2. Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准的 READ UNCOMMIT
  3. Isolation.READ_COMMITTED:读已提交,对应SQL标准的 READ COMMIT
  4. Isolation.REPEATABLE_READ:可重复读,对应SQL标准的REPEATABLE READ
  5. Isolation.SERIALIZABLE:串行化,对应SQL标准的SERIALIZABLE

在这里插入图片描述

Spring 中隔离级别的配置需要配置 @Transactional 注解的 isolation 属性:

@Transactional(rollbackFor = Exception.class, isolation = Isolation.REPEATABLE_READ)
Spring 事务传播机制

什么是事务传播机制?

事务传播机制是指当一个事务(父事务)调用另一个事务(子事务)的方法时,子事务如何传播的事务处理机制。它主要解决的是在多个事务方法相互调用时,如何决定使用哪个事务上下文以及如何管理这些事务的执行顺序和隔离级别。

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

在这里插入图片描述
而事务传播机制解决的是一个事务在多个节点(方法)中传递的问题

在这里插入图片描述
Spring 中的事务传播机制有7种:

  1. Propagation.REQUIRED:默认的事务传播机制。如果当前存在事务,则加入该事务;如果没有事务,则创建一个新事务
  2. Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果不存在事务,则以非事务的方式继续运行
  3. Propagation.MANDATORY:强制性。如果当前存在事务,则加入该事务,如果不存在,则抛出异常
  4. Propagation.REQUIRES_NEW:创建一个新的事务。如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法都是新开始自己的事务,且开启的事务相互独立,互不干扰
  5. Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起(不用)
  6. Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
  7. Propagation.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 Propagation.REQUIRED

在这里插入图片描述

对于上面的事务传播机制,我这里主要为大家说明两种:

  1. REQUIRED(默认值)
  2. REQUIRES_NEW

这里我们在 controller 层和 service 层都加上这个注解 @Transactional(propagation = Propagation.REQUIRED)

@RequestMapping("/trans")
@RestController
public class TransactionalController {
    @Autowired
    private UserInfoService userInfoService;

    @Autowired
    private LogInfoService logInfoService;

    @Transactional(rollbackFor = Exception.class, isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED)
    @RequestMapping("/registry")
    public String registry(String name, String password) throws IOException {
        userInfoService.registryUser(name,password);
        logInfoService.insertLog(name,"注册");
        return "注册成功";
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在其中一个事务中制造异常:

@Transactional(propagation = Propagation.REQUIRED)
public void registryUser(String name, String password) {
    userInfoMapper.insert(name, password);
    System.out.println(10/0);
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到,当事务传播机制为 Propagation.REQUIRED 的时候,当其中任何一个事务出现异常的时候,整个事务都会执行事务回滚的操作。

再来看看 Propagation.REQUIRED_NEW 隔离级别:

@Transactional(propagation = Propagation.REQUIRES_NEW)

还是 userInfoService 中抛出异常:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到 Propagation.REQUIRED_NEW 隔离级别中的事务都是相互独立的,互不影响。

将隔离级别改为 NEVER

在这里插入图片描述
当隔离级别为 NEVER 的时候,如果当前存在事务,就会直接报错。

NESTES 隔离级别

在这里插入图片描述
使用嵌套 NESTED 隔离级别,当其中一个事务抛出异常之后,所有事务都会回滚。

但是这样不就和 REQUIRED 隔离级别是一样的吗?这样看确实一样,但是还是有区别的:

我们将出现错误的事务单独进行回滚:

@Transactional(propagation = Propagation.NESTED)
public void registryUser(String name, String password)throws RuntimeException {
    userInfoMapper.insert(name, password);
    try {
        System.out.println(10/0);
    }catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后将隔离级别改为 REQUIRED:

在这里插入图片描述
整个事务都回滚了。

所以 REQUIRED 和 NESTED 隔离界别的区别:

  • 整个事务如果都执行成功,二者的结果是一样的
  • 如果事务一部分执行成功,REQUIRED 加入事务会导致整个事务回滚,NESTED 嵌套事务可以实现局部回滚,不会影响上一个方法的执行结果

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

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

相关文章

嵌入式软件的设计模式与方法

思想有多远,我们就能走多远 4、状态与工作流类设计模式 4.1 状态与事件 行为随条件变化而改变,这里状态切换的模式也称为状态机。有限状态机 (Finite State Machine,FSM) 是由3 个主要元素组成的有向图: 状态、转换和动作。 状态是系统或者…

jsp教材管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 教材管理系统是一套完善的java web信息管理系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql5.0&…

python常用的深度学习框架

目录 一:介绍 二:使用 Python中有几个非常受欢迎的深度学习框架,它们提供了构建和训练神经网络所需的各种工具和库。以下是一些最常用的Python深度学习框架: 一:介绍 TensorFlow:由Google开发的TensorF…

LeetCode-第171题-Excel表的序列号

1.题目描述 给你一个字符串 columnTitle ,表示 Excel 表格中的列名称。返回 该列名称对应的列序号 。 例如: A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 ... 2.样例描述 3.思路描述 遍历时将每个字母与 A 做减法&…

python 动态数据 展示 ,数据是由51单片机发送过来的,温度传感器。

import tkinter as tk import randomimport seriallis[] for i in range(50):lis.append(i1) # 打开串行端口 ser serial.Serial(COM3, 9600) # 9600为波特率,根据实际情况进行调整# 初始化数据 lis [random.randint(15, 35) for _ in range(50)]def update_data…

jenkins 发布远程服务器并部署项目

安装参考另一个文章 配置maven 和 jdk 和 git 注意jdk的安装目录,是jenkins 安装所在服务器的jdk目录 注意maven的目录 是jenkins 安装所在服务器的maven目录 注意git的目录 是jenkins 安装所在服务器的 git 目录 安装 Publish Over SSH 插件 配置远程服务器 创…

信号系统之线性系统详解

1 线性系统 信号描述了一个参数如何随另一个参数变化。例如,电子电路中的电压随时间变化,或图像中随距离变化的亮度。系统是响应输入信号而产生输出信号的任何过程。如图中的框图所示。 有几个规则用于命名信号: 连续信号使用圆括号&#x…

Python tkinter (15) —— PhotoImage

本文主要介绍Python tkinter PhotoImage图像应用及示例。 系列文章 python tkinter窗口简单实现 Python tkinter (1) —— Label标签 Python tkinter (2) —— Button标签 Python tkinter (3) —— Entry标签 Python tkinter (4) —— Text控件 Python tkinter (5) 选项按…

22.HarmonyOS App(JAVA)位置布局PositionLayout使用方法

不常用 在PositionLayout中,子组件通过指定准确的x/y坐标值在屏幕上显示。(0, 0)为左上角;当向下或向右移动时,坐标值变大;允许组件之间互相重叠 布局方式 PositionLayout以坐标的形式控制组件的显示位置,允许组件相…

JVM 性能调优 - Java 虚拟机内存体系(1)

Java 虚拟机我们简称为 JVM(Java Virtual Machine)。 Java 虚拟机在执行 Java 程序的过程中,会管理几个不同的数据区域。如下图所示: 下面我会介绍这几个数据区的特点。 堆 堆区的几个特点: 线程共享。启动时创建堆…

【MATLAB源码-第131期】基于matlab的淘金优化算法(GRO)机器人栅格路径规划,输出做短路径图和适应度曲线。

操作环境: MATLAB 2022a 1、算法描述 淘金优化算法(GoldRush Optimizer,简称GRO)是一种启发式优化算法,它受到淘金过程的启发。在淘金过程中,淘金者在河流或矿区中寻找金矿,通过筛选沙砾来寻…

Django通过Json配置文件分配多个定时任务

def load_config():with open("rule.json", rb)as f:config json.load(f)return configdef job(task_name, config, time_interval):# ... 通过task_name判断进行操作if task_name get_data_times:passdef main():config load_config()for task_name, task_value…

SpringBoot多模块项目proguard混淆

SpringBoot多模块项目proguard混淆 前言整活项目目录混淆后的效果图混淆配置混淆配置规则keep相关通配符和关键字keep说明常见问题解决办法效果前言 proguard 是压缩、优化和混淆Java字节码文件的免费的工具。 它可以删除无用的类、字段、方法和属性。可以删除没用的注释,最大…

网络故障的排错思路

一、网络排错必备知识 1、网络通信的基础设备和其对应的OSI层次 在网络通信中,了解基础设备如交换机、三层交换机、路由器和防火墙以及它们在OSI七层模型中 的作用至关重要。对于网络管理员和工程师来说,深入了解这些设备在OSI模型中的位置和功能可 …

探索Gin框架:Golang Gin框架请求参数的获取

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。 前言 我们在专栏的前面几篇文章内讲解了Gin框架的路由配置,服务启动等内容。 专栏地址&…

为什么有的人渐渐不点外卖了?

​随着互联网的发展和普及,外卖行业也在近几年内得到了迅猛的发展,它方便快捷、节省时间的特点使得外卖成为了很多人生活的一部分。但是,随着时间的推移,越来越多的人开始减少点外卖的频率,这是为什么呢? 首…

npm修改镜像源

背景:切换npm镜像源是经常遇到的事,下面记录下具体操作命令 1. 打开终端运行"npm config get registry"命令来查看当前配置的镜像源 npm config get registry2. 修改成淘宝镜像源"https://registry.npmjs.org/" npm config set re…

编译原理与技术(三)——语法分析(二)自顶向下-递归下降

一、语法分析的两种方法 自顶向下(Top-down): 针对输入串,从文法的开始符号出发,尝试根据产生式规则推导(derive)出该输入串。 从根部开始构造语法树。 自底向上(Bottom-up&#…

鸿蒙踩坑合集

各位网络中的小伙们,关于鸿蒙的踩坑陆陆续续收集中,本文章会持续更新,希望对您有所帮助 1、预览视图无法正常加载 重新编译项目,点击刷新按钮,控制台提示Build task failed. Open the Run window to view details. 解…

图书借阅管理系统

文章目录 图书借阅管理系统一、项目演示二、项目介绍三、万字文档参考四、系统部分功能截图五、部分代码展示六、底部获取项目和万字文档(9.9¥带走) 图书借阅管理系统 一、项目演示 图书借阅管理系统 二、项目介绍 基于Springbootvue的前后…
最新文章