Spring事务和事务的传播机制(JavaEE进阶系列7)

news2025/1/12 18:11:54

目录

前言:

1.为什么需要事务

2.Spring中事务的实现

2.1编程式事务

2.2声明式事务

2.3@Transactional的作用范围

2.4@Transactional参数说明

2.5@Transactional的注意事项

2.6@Transactional工作原理

3.事务隔离级别

3.1事务特性的回顾

3.2Spring中设置事务的隔离级别

3.3MySQL事务隔离级别

3.4Spring的事务隔离级别

4.Spring事务的传播机制

4.1事务传播机制是什么

4.2为什么需要事务传播

结束语:


前言:

1.为什么需要事务

对于事务我们之前也是有所了解的我们在来回顾一下有关于事务的定义将一组操作封装成一个执行单元(封装在一起),要么全部执行成功,要么全部执行失败。

就拿转账来说:

  • 第一步操作:A账户向B账户转账100元,A账户 - 100元。
  • 第二步操作:B账户接收100元,B账户+100元。

如果没有事务,第一步如果成功了第二步失败了,那么A账户的100元就相当于是平白无故的人间蒸发了,不符合现实。所有使用事务就可以解决这个问题了,让这一组操作要么一起成功,要么一起失败。

2.Spring中事务的实现

在Spring中事务的操作分为两类:

  • 编程式事务(手动写代码的事务)。
  • 声明式事务(利用注解自动开启和提交事务)。

接下来我们先来回顾一下在MySQL中是如何使用的。

事务在MySQL中有三个重要的操作:开启事务(start transaction)、提交事务(commit)、回滚事务(rollback)

2.1编程式事务

Spring手动操作事务和上面MySQL操作事务类似,他也是有是三个重要的操作步骤:

  • 开启事务(获取事务)。
  • 提交事务。
  • 回滚事务。

SpringBoot内置了两个对象,DataSourceTransactionManager用来获取事务(开启事务)、提交或回滚事务的,而TransactionDefinition是事务的属性,在获取事务的时候要将TransactionDefinition传递进去从而获得一个事务TransactionStatus,实现代码如下所示:

package com.example.demo.controller;

import com.example.demo.service.UserService;
import org.apache.catalina.User;
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;

import javax.annotation.Resource;

@RestController
public class UserController2 {
    @Resource
    private UserService userService;
    //JDBC事务管理器
    @Resource
    private DataSourceTransactionManager dataSourceTransactionManager;
    //定义事务属性
    @Resource
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/save")
    public Object save(User user) {
        //开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        //插入数据库
        int result = userService.save(user);
        //提交事务
        dataSourceTransactionManager.commit(transactionStatus);
        //回滚事务
        dataSourceTransactionManager.rollback(transactionStatus);
        return result;
    }
}

上述的实心方式太过于繁琐,所以接下来我们就使用声明式事务。

2.2声明式事务

声明式事务的实现很简单,只需要在需要的方法上添加@Transactional注解就可以实现了,无需手动开启事务和提交事务,进入方法时开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务,具体代码实现如下所示:

model层代码:

package com.example.demo.model;

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class Userinfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private int state;
}

dao层代码:

package com.example.demo.dao;

import com.example.demo.model.Userinfo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
    @Insert("insert into userinfo(username, password) values(#{username}, #{password})")
    int add(Userinfo userinfo);
}

service层代码:

package com.example.demo.service;

import com.example.demo.dao.UserMapper;
import com.example.demo.model.Userinfo;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
@Service
public class UserService {
    @Resource
    private UserMapper userMapper;

    public int add(Userinfo userinfo) {
        int result = userMapper.add(userinfo);
        System.out.println("add result -> " + result);
        return result;
    }
}

controller层代码:

package com.example.demo.controller;

import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

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

    @RequestMapping("/add")
    @Transactional
    public int add() {
        //1.非空判断
        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("二二");
        userinfo.setPassword("123");
        //2.调用service执行添加
        int result = userService.add(userinfo);
        System.out.println("result: " + result);
        int n = 10 / 0;
        //3.将结果给前端
        return result;
    }
}

结果展示:

当启动服务器的时候访问页面的时候会出现报错异常。

在控制台中可以看到以及将数据添加到数据库中的信息。

但是在访问数据库的时候会发现没有数据,说明触发异常之后数据已经被回滚了。

2.3@Transactional的作用范围

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

  • 修饰方法时:需要注意只能用到public方法上,否则不生效。(推荐此种方法)
  • 修饰类时:表明该注解对该类中所有的public方法都生效。

2.4@Transactional参数说明

参数作用
value当配置了多个事务管理器时,可以使用该属性指定选择那个事务管理器。
transactionManager当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。
propagation事务的传播行为,默认值为Propagation.REQUIRED
isolation事务的隔离级别,默认值为Isolation.DEFAULT
timeout事务的隔离级别,默认值为-1,如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly指定事务是否为只读事务,默认值为false,为了忽略那些不需要事务的方法,比如读取事务可以设置read-only为true。
rollbackFor用于指定能够触发事务回滚的异类型,可以指定多个异常。
rollbackForClassName用于指定能够触发事务回滚的异类型,可以指定多个异常。
noRollbackFor抛出指定的异常类型,不会滚事务,也可以指定多个异常类型。
noRollbackForClassName抛出指定的异常类型,不会滚事务,也可以指定多个异常类型。

2.5@Transactional的注意事项

当程序发生异常,但被try-catch处理之后,就不会发生自动回滚事务了。

如下代码所示:

注意这里只是在上述代码的基础之上改动了controller层的代码。

结果展示:

浏览器不会显示异常。


控制台会输出添加的信息。

数据库会面会显示出添加之后的数据。

这里大家就好奇了,为什么明明是发生了异常,为什么事务不回滚了?

原因是因为在Spring中是使用代理来进行管理的,而当异常被捕获之后,外部的代理就没有办法感受到异常信息了,所以就会直接将数据添加到数据库中,不进行回滚事务了。

那么针对于上述的情况当然也会有解决方法,根据它的原因我们就可以找到解决问题的办法了,我们可以让代理感受到异常信息,这样事务就会发生回滚了。下面是被try-catch处理之后,不自动回滚事务的解决方案:

①将异常继续抛出去(代理对象就能感受到异常,也就能自动回滚事务了)

代码展示:

结果展示:

浏览器访问的时候仍然会报错。

控制台显示结果如下所示:

数据库再次查询数据的时候,发现三三这条数据没有被加载到数据库中,已经别回滚了。

②使用代码手动回滚事务。

代码展示:

结果展示:
这次在浏览器中展示的就不是一大堆的报错信息了。

接下来是控制台的信息展示,也是显示已经将数据添加到数据库中了。

在数据库中会发现数据不存在,就说明已经被回滚掉了。

注意:以上两种都可以用来解决被try-catch处理之后,不能自动回顾事务的问题,但是一般我们使用第二种解决方案多,也是推荐的一种写法。

2.6@Transactional工作原理

@Transactional是基于AOP实现的,AOP又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用JDK的动态代理,如果目标对象没有实现接口,会使用CGLIB动态代理。

@Transactional在开始执行业务之前,通过代理先开启事务,在执行之后再提交事务。如果中途遇到异常,则回滚事务。

3.事务隔离级别

3.1事务特性的回顾

事务有4大特性(ACID),原子性、一致性、持久性、隔离性。具体的概念如下所示:

  • 原子性:一个事务(transaction)中的所有操作,要么全部执行完成,要么全部不执行。不会结束在中间某个环节。事务在执行的过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就这个事务从来没有执行过一样。
  • 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏的。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精准度、串行性以及后续数据库可以自发性的完成预定的工作。
  • 持久性:事务处理结束之后,对数据的修改就是永久的,即便系统故障也不会丢失。
  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同的级别,包括读未提交(Readuncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

在上述的四大特性中只有隔离性(隔离级别)是可以设置的。

为什么要设置事务的隔离级别?

设置事务的隔离级别是用来保障多个并发事务执行可控,更符合操作者的预期。

3.2Spring中设置事务的隔离级别

Spring中事务隔离级别可以通过@Transactional中的isolation属性来进行设置,如下所示:

3.3MySQL事务隔离级别

MySQL的事务隔离级别有以下4种:

  • READ UNCOMMITTED:读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读
  • READ COMMITTED:读已提交,也叫提交读,该隔离级别的事务能读取到以提交事务的数据,因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同SQL查询中,可能会得到不同的结果,这种现象叫做不可重复读
  • REPEATABLE READ:可重复读是MySQL的默认事务隔离级别,他能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入的时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插不进去,这就叫幻读(Phantom Read)
  • SERIALIZABLE:序列化,事务的最高隔离级别,他会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率太低,所以真正使用的场景并不多。
事务隔离级别脏读不可重复读幻读
读未提交(READ UNCOMMITTED)
读已提交(READ COMMITED)×
可重复读(REPEATABLE READ)××
串行化(SERIALIZABLE)×××

脏读、幻读、不可重复读导致的原因如下所示: 

  • 脏读:一个事务读取到另一个事务修改的数据之后,后一个事务又进行了回滚操作,从而导致第一个事务读取的数据是错误的。
  • 不可重复读:一个事务两次查询得到的结果不同,因为在两次查询中间,有另一个事务把数据修改了。
  • 幻读:一个事务两次查询中得到的结果集不同,因为在两次查询中另一个事务又新增了一部分数据。

在数据库中我们可以通过以下SQL语句来查询到当前全局事务隔离级别和当前连接的事务隔离级别: 

那么在MySQL中到底有没有解决掉幻读的问题呢?

有但是又没彻底解决,因为 REPEATABLE READ 是通过 + MVCC来进行解决幻读问题的,但是REPEATABLE READ又有两种读,一种是当前读,一种是快照读,快照读的时候是从内存中读取的。所以快照读通过+MVCC是可以解决掉幻读问题的,而当前读是读取当前的数据,可能会发生变化,所以当前读+MVCC是解决不掉幻读问题的,除非加锁。

所以要想彻底解决幻读问题有两种解决办法:

  1. 串行化。
  2. MVCC+锁。

3.4Spring的事务隔离级别

而Spring中事务隔离级别包含以下5种:

  • Isolation.DEFAULT:以连接的数据的事务隔离级别为主。
  • Isolation.READ_UNCOMMITTED:读未提交,可以读到未提交的事务,存在脏读。
  • Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
  • Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级
  • 别)。
  • Isolation.SERIALIZABLE:可以解决所有的并发问题,但是性能太低。

从上述介绍中可以看出,相比于MySQL的事务隔离级别,Spring的事务隔离级别只是多一个Isolation.DEFAULT(以数据库的全局事务隔离级别为主)。

我们只需要在Spring事务中设置@Transactional里的属性isolation属性的隔离级别即可,如下代码所示:

4.Spring事务的传播机制

4.1事务传播机制是什么

Spring事务传播机制定义了多个包含了事务的方法,互相调用,事务是如何在这些方法间进行传递的。他其实就是规定了多个事务在互相调用时,事务的执行行为。

4.2为什么需要事务传播

事务的隔离级别是保证多个并发执行的可控性(稳定性的),而事务传播机制是保证一个事务在多个调用方法间的可控性(稳定性)。就像是疫情期间,会存在不同的隔离方式,酒店隔离、居家隔离...,这就是为了保证疫情的可控性。事务的隔离级别解决的是多个事务同时调用一个数据库的问题,如下所示:

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

4.3事务的七大传播机制

Spring事务的传播机制包含以下7点:

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

针对于Propagation.REQUIRED它是表示如果当前存在事务,则加入该事务,没有存在则不加入,以非事务的方式继续运行。接下来我们通过画图和代码来给大家进行具体演示一下。

代码展示:

Controller层代码展示:

package com.example.demo.controller;

import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

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

    @RequestMapping("/add")
    @Transactional(propagation = Propagation.REQUIRED)
    public int add() {
        //1.非空判断
        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("思思");
        userinfo.setPassword("123456");
        //2.调用service执行添加
        int result = userService.add(userinfo);
        System.out.println("result: " + result);
        try {
            int n = 10 / 0;
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        //3.将结果给前端
        return result;
    }
}

Service层代码展示:

package com.example.demo.service;

import com.example.demo.dao.UserMapper;
import com.example.demo.model.Userinfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
@Service
public class UserService {
    @Resource
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public int add(Userinfo userinfo) {
        int result = userMapper.add(userinfo);
        System.out.println("add result -> " + result);
        insert(userinfo);
        return result;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public int insert(Userinfo userinfo) {
        int result = userMapper.add(userinfo);
        System.out.println("add result -> " + result);
        int n = 10 / 0;
        return result;
    }
}

结果展示:

浏览器页面展示,会发现存在算数异常。

控制台会发现有插入的两条数据的记录。

在数据库中发现没有插入任何数据,说明在触发异常之后数据都被回滚了。

解析:当使用Propagation.REQUIRED时说明第一个事务已经开启了事务了,则后续的事务发现前面的事务开启之后就会加入到事务中来。如下图所示:

针对于Propagation.SUPPORTS它是如存在当前事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

代码展示:

controller层代码展示:

package com.example.demo.controller;

import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

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

    @RequestMapping("/add")
    @Transactional(propagation = Propagation.SUPPORTS)
    public int add() {
        //1.非空判断
        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("思思");
        userinfo.setPassword("123456");
        //2.调用service执行添加
        int result = userService.add(userinfo);
        System.out.println("result: " + result);
        try {
            int n = 10 / 0;
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        //3.将结果给前端
        return result;
    }
}

service层代码展示:

package com.example.demo.service;

import com.example.demo.dao.UserMapper;
import com.example.demo.model.Userinfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
@Service
public class UserService {
    @Resource
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.SUPPORTS)
    public int add(Userinfo userinfo) {
        int result = userMapper.add(userinfo);
        System.out.println("add result -> " + result);
        insert(userinfo);
        return result;
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public int insert(Userinfo userinfo) {
        int result = userMapper.add(userinfo);
        System.out.println("add result -> " + result);
        int n = 10 / 0;
        return result;
    }
}


结果展示:

浏览器依然会报错:


控制台中会显示插入数据。

在数据库中国会发现插入了两条数据,说明他是以非事务的方式运行的。

画图解析:


针对于 Propagation.NESTED 它表示当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则该取值等于PROPAGATION_REQUIRED。

代码展示:

Controller层代码展示:

package com.example.demo.controller;

import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

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

    @RequestMapping("/add")
    @Transactional(propagation = Propagation.NESTED)
    public int add() {
        //1.非空判断
        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("五五");
        userinfo.setPassword("123456");
        //2.调用service执行添加
        int result = userService.add(userinfo);
        System.out.println("result: " + result);

        userService.insert(userinfo);
//        try {
//            int n = 10 / 0;
//        } catch (Exception e) {
//            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
//        }
        //3.将结果给前端
        return result;
    }
}


service层代码展示:

package com.example.demo.service;

import com.example.demo.dao.UserMapper;
import com.example.demo.model.Userinfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import javax.annotation.Resource;
@Service
public class UserService {
    @Resource
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public int add(Userinfo userinfo) {
        int result = userMapper.add(userinfo);
        System.out.println("add result -> " + result);
        return result;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public int insert(Userinfo userinfo) {
        int result = userMapper.add(userinfo);
        System.out.println("add result -> " + result);
        try {
            int n = 10 / 0;
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }
}

结果展示:

对于浏览器端仍然出现报错现象,但是不是算数异常错误了。

对于控制台依然会显示插入两条数据。

对于数据库而言它不会插入一条数据,所有的数据都会被回滚掉。

画图解释:

结束语:

好了这节小编就给大分享到这里啦,希望这节对大家有关于Spring事务和事务的传播机制的基础知识的了解有一定帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)

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

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

相关文章

Android Fragment 基本概念和基本使用

Android Fragment 基本概念和基本使用 一、基本概念 Fragment,简称碎片,是Android 3.0(API 11)提出的,为了兼容低版本,support-v4库中也开发了一套Fragment API,最低兼容Android 1.6。 过去s…

Linux中使用nfs共享存储

NFS是一种基于TCP/IP传输的网络文件系统协议。通过使用NFS协议,客户机可以像访问本地目录一样访问远程服务器中的共享资源。对于大多数负载均衡群来说,使用NFS协议来共享数据存储是比较常见的做法,NFS也是存储设备必然支持的一种协议。但是由…

VM虚拟机运行的Ubuntu连入同一局域网,并实现双机方法

环境: Windows 10 VMware Workstation Pro 16 Ubuntu 20.4 在虚拟机设置桥接模式 确保虚拟机处于关闭状态,在Vm中设置: 编辑->虚拟网络编辑器 如果你以前设置过,可以重置之。 重置之后,添加桥接模式: …

pytorch_神经网络构建4

文章目录 循环神经网络LSTM词嵌入skip-Gram模型N-Gram模型词性预测RNN循环神经网络的基础模块实现RNN识别图片RNN时间序列预测词向量模块词向量运用N-Gram模型lstm词性预测 循环神经网络 这个网络主要用来处理序列信息,之前处理图片时大部分是分析图片的结构信息, 什么是序列信…

Mac电脑Dock窗口预览工具 DockView

DockView是一款适用于Mac操作系统的软件,为用户提供了一种方便的窗口管理工具。这款软件可以让用户更高效地管理和浏览当前打开的窗口。 DockView的主要功能是在Dock栏上显示每个窗口的缩略图,并提供了一些相关的操作选项。当用户将鼠标悬停在Dock栏上的…

前端之【数据可视化】

目录 🌟前言🌟为什么要数据可视化(优点)🌟前端数据可视化框架🌟Echarts🌟Highcharts🌟D3 🌟数据可视化框架的选择🌟写在最后 🌟前言 数据可视化主要旨在借助于图形化手段…

阿里云云服务器实例使用教学

目录 云服务器免费试用 详细步骤 Xshell 远程连接 云服务器免费试用 阿里云云服务器网址:阿里云免费试用 - 阿里云 详细步骤 访问阿里云免费试用。单击页面右上方的登录/注册按钮,并根据页面提示完成账号登录(已有阿里云账号)…

Qt/C++编写物联网组件/支持modbus/rtu/tcp/udp/websocket/mqtt/多线程采集

一、功能特点 支持多种协议,包括Modbus_Rtu_Com/Modbus_Rtu_Tcp/Modbus_Rtu_Udp/Modbus_Rtu_Web/Modbus_Tcp/Modbus_Udp/Modbus_Web等,其中web指websocket。支持多种采集通讯方式,包括串口和网络等,可自由拓展其他方式。自定义采…

视频目标语义分割自动标注——从图像轮廓提取到转成json标签文件

前言 语义分割数据标注是为训练语义分割模型准备数据的过程。语义分割是计算机视觉领域的任务,其中需要为图像中的每个像素分配一个类别标签,以区分不同的对象或区域。标注数据时,通常需要为每个对象或区域分配一个唯一的标签,并…

STM32 HAL库高级定时器输入捕获脉宽测量

STM32 HAL库高级定时器输入捕获脉宽测量 📌相关篇《STM32 HAL库定时器输入捕获SlaveMode脉宽测量》 ✨相比于上面所使用的高级定时器输入捕获从模式来测量PWM信号,实现方法更为复杂一下,但是还是将实现的方法记录下来。 📌本篇实现…

OpenCV16-图像连通域分析

OpenCV16-图像连通域分析 1.图像连通域分析2.connectedComponents3.connectedComponentsWithStatus 1.图像连通域分析 连通域是指图像中具有相同像素值并且位置相邻的像素组成的区域。连通域分析是指在图像中寻找彼此互相独立的连通域并将其标记出来。 4邻域与8邻域的概念&am…

TatukGIS Developer Kernel使用教程:如何为FMX创建第一个应用程序

概述:TatukGIS Developer Kernel(DK)是一个用于开发自定义地理信息系统(GIS)应用程序以及解决方案的综合性软件开发工具包(SDK)。本篇文章主要介绍用DK11为FMX创建一个应用程序,现在…

solidworks 2024新功能之--保存为低版本 硕迪科技

大家期盼已久的SOLIDWORKS保存低版本文件功能来了,从SOLIDWORKS 2024 开始,您可以将在最新版本的SOLIDWORKS 中创建的SOLIDWORKS零件、装配体和工程图另存为SOLIDWORKS 早期版本的全功能文档(完成的特征树与相关参数)。 将文件另…

Spring Boot项目中使用 TrueLicense 生成和验证License

1、Linux 在客户linux上新建layman目录,导入license.sh文件, [rootlocalhost layman]# mkdir -p /laymanlicense.sh文件内容: #!/bin/bash # 1.获取要监控的本地服务器IP地址 IPifconfig | grep inet | grep -vE inet6|127.0.0.1 | awk {p…

Kubernetes简略架构

kubectl kubectl是Kubernetes的命令行工具。它的全称为Kubernetes Command Line Tool,是用于管理Kubernetes集群的工具。它可以用来创建、更新、删除资源对象、查看日志等操作 Node: Kubernetes 通过将容器放入在节点(Node)上运行的Pod 中…

5、使用 pgAdmin4 图形化创建和管理 PostgreSQL 数据库

通过上几篇文章我们讲解了如何安装 PostgreSQL 数据库软件和 pgAdmin4 图形化管理工具。 今天我们继续学习如何通过 pgAdmin4 管理工具图形化创建和管理 PostgreSQL 数据库。 一、PostgreSQL的基本工作方式 在学习如何使用PostgreSQL创建数据库之前,我们需要了解一…

C++学习: 文件I/O

作者: 苏丙榅 原文链接: https://subingwen.cn/c/file/ 文章目录 1. 文件概述1.1 什么是文件I/O1.2 磁盘文件分类 2. 文件的打开和关闭2.1 文件指针2.2 打开文件 2.3 关闭文件3. 文件的读写3.1 按照字符读写文件3.1.1 写文件3.1.2 读文件3.1.3 EOF 3.2 按照行读写文件3.2.1 写文…

华为交换机S5700系列产品命名规则

华为交换机的全系列产品命名规则如下: S系列:代表固定端口交换机。例如,S5720系列、S6720系列。CE系列:代表企业级交换机。例如,CE5800系列、CE6800系列。CloudEngine系列:代表华为云引擎交换机&#xff0c…

数据库系列之MySQL中Join语句优化问题

最近使用MySQL 8.0.25版本时候遇到一个SQL问题,两张表做等值Join操作执行很慢,当对Join连接字段添加索引优化后,执行效率反而变得更差,其中的原因值得分析。因此本文介绍下MySQL中常见的Join算法,并对比使用不同Join算…

【MATLAB源码-第47期】基于matlab的GMSK调制解调仿真,输出误码率曲线,采用相干解调。

操作环境: MATLAB 2022a 1、算法描述 GMSK(高斯最小移相键控)是数字调制技术的一种。下面是关于GMSK调制解调、应用场景以及其优缺点的详细描述: 1. 调制解调: - 调制:GMSK是一种连续相位调制技术&am…