Spring 声明式事务

Spring 声明式事务

    • 1.Spring 事务管理概述
      • 1.1 事务管理的重要性
      • 1.2 Spring事务管理的两种方式
        • 1.2.1 编程式事务管理
        • 1.2.2 声明式事务管理
      • 1.3 为什么选择声明式事务管理
    • 2. 声明式事务管理
      • 2.1 基本用法
      • 2.2 常用属性
        • 2.2.1 propagation(传播行为)
        • 2.2.2 isolation(隔离级别)
        • 2.2.3 readOnly(只读事务)
        • 2.2.4 timeout(超时时间)
        • 2.2.5 rollbackFor(回滚异常)
    • 3. Spring事务失效
    • 4. 案例
      • 4.1 前期准备
        • 4.1.1 依赖引入
        • 4.1.2 数据库建表语句
        • 4.1.3 实体类以及相关代码
      • 4.2 转账案例
        • 4.2.1 测试成功
        • 4.2.2 测试回滚
        • 4.2.3 测试受检异常
        • 4.2.4 测试 rollBackFor 属性
        • 4.2.5 测试隔离级别

Spring 提供了两个事务管理方式一种是编程式(很少用),一种是声明式事务。声明式事务管理将事务管理的代码从业务逻辑中分离出来,使得代码更清晰、可维护。使得开发者可以通过配置而不是编写大量的代码来管理事务。

使用这里我们只介绍声明式事务

1.Spring 事务管理概述

1.1 事务管理的重要性

在应用程序中,事务管理是确保数据操作的一致性、隔离性、持久性和原子性的关键机制。当多个数据库操作必须作为一个不可分割的单元执行时,事务管理变得至关重要。对于复杂的业务逻辑,事务能够确保在并发和异常情况下,数据库始终保持一致性。

1.2 Spring事务管理的两种方式

Spring框架提供了两种主要的事务管理方式,分别是编程式事务管理和声明式事务管理。

1.2.1 编程式事务管理

编程式事务管理要求开发者通过编写代码来管理事务的开始、提交和回滚。虽然具有灵活性,但容易导致代码冗余和可读性差。

try {// 开始事务transactionManager.beginTransaction();// 执行业务逻辑// 提交事务transactionManager.commit();
} catch (Exception e) {// 发生异常,回滚事务transactionManager.rollback();throw e;
}
1.2.2 声明式事务管理

相比之下,声明式事务管理通过配置文件或注解的方式实现事务控制,将事务逻辑从业务代码中分离出来。这种方式更加简洁、可维护,并提供更好的可读性。

@Transactional
public void performBusinessLogic() {// 业务逻辑
}

1.3 为什么选择声明式事务管理

选择声明式事务管理有以下优势:

  • 简洁性: 通过注解或XML配置,开发者无需编写冗长的事务管理代码,使代码更加简洁清晰。
  • 可维护性: 事务逻辑与业务逻辑分离,易于维护和理解。
  • 可读性: 使用注解或XML配置,事务逻辑与业务逻辑在代码中更易于辨认,提高代码的可读性。
  • 一致性: 通过统一的配置方式,整个应用程序可以保持一致的事务管理策略,减少错误和不一致性。
  • 集成性: 声明式事务更好地与Spring的其他特性(如AOP)集成,提供更全面的解决方案。

综合而言,声明式事务管理是Spring中推荐的事务管理方式,它能够提高代码的可维护性、可读性,并与其他Spring特性协同工作,使得开发者能够更专注于业务逻辑的实现而不是事务的管理。

2. 声明式事务管理

声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。

Spring 根据类或者方法上是否有@transactional注解来判断是否开启事务。

2.1 基本用法

在类上使用

@Transactional
public class TestService {// 类中所有方法都将使用默认的事务配置
}

在方法上使用

public class TestService {@Transactionalpublic void method1() {// 这个方法将使用默认的事务配置}@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)public void method2() {// 这个方法将使用指定的事务配置}
}

2.2 常用属性

2.2.1 propagation(传播行为)

@Transactional 注解的 propagation 属性用于定义事务的传播行为,它指定在方法被调用时,当前方法的事务如何与现有的事务进行交互。

@Transactional(propagation = Propagation.*)
public void method1() {// ...
}

定义事务的传播行为,包括 :

  • REQUIRED:默认,如果当前存在事务,则加入该事务,如果不存在事务,则新建一个事务。
  • REQUIRES_NEW:无论当前是否存在事务,都会创建一个新的事务,如果存在事务,则将其挂起。
  • SUPPORTS:如果当前存在事务,则加入该事务,如果不存在事务,则以非事务的方式执行。
  • MANDATORY:该传播行为要求当前方法必须在一个事务中执行,否则将抛出异常。
  • NOT_SUPPORTED:以非事务的方式执行,如果当前存在事务,则将其挂起。
  • NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。
  • NESTED:如果当前存在事务,则创建一个嵌套事务,并在嵌套事务内执行。嵌套事务是外部事务的一部分,但有独立的提交和回滚。
2.2.2 isolation(隔离级别)

@Transactional 注解中的 isolation 属性用于指定事务的隔离级别。隔离级别定义了多个事务并发执行时,彼此之间的可见性和影响的程度。

@Transactional(isolation = Isolation.*)
public void method1() {// ...
}

Spring 支持以下五个隔离级别:

  • DEFAULT:默认,使用底层数据库的默认隔离级别。通常为数据库的默认配置,比如 MySQL 默认的是 REPEATABLE_READ,而 Oracle 默认的是 READ_COMMITTED

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

  • READ_COMMITTED(读已提交):保证一个事务提交后才能被其他事务读取。这是大多数数据库的默认隔离级别,可以避免脏读,但仍可能存在不可重复读和幻读的问题。

  • REPEATABLE_READ(可重复读): 对相同字段的多次读取结果是一致的,除非自己进行了数据更新。避免了不可重复读的问题,但仍可能存在幻读的问题。

  • SERIALIZABLE(串行化): 最高的隔离级别,确保每个事务都完全看不到其他事务的操作,包括读取和写入。可以避免脏读、不可重复读和幻读的问题,但也降低了并发性能。

2.2.3 readOnly(只读事务)

标识事务是否为只读,可以提高事务的性能。

@Transactional(readOnly = true)
public void method1() {// ...
}
2.2.4 timeout(超时时间)

指定事务的超时时间,单位为秒。

@Transactional(timeout = 60)
public void method1() {// ...
}
2.2.5 rollbackFor(回滚异常)

指定哪些异常触发事务回滚。@Transactional 注解默认只对运行时异常进行事务回滚,对检查时异常不回滚。

@Transactional(rollbackFor = {SQLException.class, MyCustomException.class})
public void method1() {// ...
}

3. Spring事务失效

哪些情况下会导致Spring事务失效,对应的原因是什么?

  • 1.方法内的自调用:Spring事务是基于AOP的,只要使用代理对象调用某个方法时,Spring事务才能生效,而在一个方法中调用使用this.xxxO调用方法时,this并不是代理对象,所以会导致事务失效。

    • 解决办法1:将需要在同一事务中执行的方法抽取到一个独立的Bean中,通过依赖注入的方式调用该Bean。确保方法调用经过代理对象,从而激活事务。

    • 解决办法2:在类内部通过依赖注入的方式,将当前类注入到自己中,然后通过注入的对象调用方法。这样确保调用经过代理对象,从而使事务生效。

    • 解决办法3:使用AopContext.currentProxy()获取当前代理对象,通过这个代理对象调用方法。结合@EnableAspectJAutoProxy(exposeProxy=true)注解开启对当前代理对象的暴露,确保事务能够正确地被激活。

  • 2.方法是private的:Spring事务会基于CGLIB来进行AOP,而CGLIB会基于父子类来失效,子类是代理类,父类是被代理类,如果父类中的某个方法是private的,那么子类就没有办法重写它,也就没有办法额外增加Spring事务的逻辑。

  • 3.方法是final的:原因和private是了样的,也是由于子类不能重写父类中的final的方法

  • 4.单独的线程调用方法:当Mybatis或JdbcTemplate执行SQL时,会从ThreadLocal中去获取数据库连接对象,如果开启事务的线程和执行SQL的线程是同一个,那么就能拿到数据库连接对象,如果不是同一个线程,那就拿到不到数据库连接对象,这样,Mybatis或JdbcTemplate就会自己去新建一个数据库连接用来执行SQL,此数据库连接的autocommit为true,那么执行完SQL就会提交,后续再抛异常也就不能再回滚之前已经提交了的SQL了。

  • 5.没加@Configuration注解:如果用SpringBoot基本没有这个问题,但是如果用的Spring,那么可能会有这个问题,这个问题的原因其实也是由于Mybatis或JdbcTemplate会从ThreadLocal中去获取数据库连接,但是ThreadLocal中存储的是一个MAP,MAP的key为DataSource对象,value为连接对象,而如果我们没有在AppConfig上添加@Configuration注解的话,会导致MAP中存的DataSource对象和Mybatis和JdbcTemplate中的DataSource对象不相等,从而也拿不到数据库连接,导致自己去创建数据库连接了。

  • 6.异常被吃掉:如果Spring事务没有捕获到异常,那么也就不会回滚了,默认情况下Spring会捕获RuntimeException和Error。

  • 7.类没有被Spring管理

  • 8.数据库不支持事务

4. 案例

4.1 前期准备

4.1.1 依赖引入
    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- 使用Plus 简化开发 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.17</version></dependency></dependencies>
4.1.2 数据库建表语句
CREATE TABLE `users`  (`id` int NOT NULL AUTO_INCREMENT,`user_name` varchar(18)  NOT NULL,`balance` decimal(10, 2) NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
)
4.1.3 实体类以及相关代码

User

@Data
public class User {private int id;private String userName;private Double balance;
}

UserMapper

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

UserService

public interface UserService extends IService<User> {
}

UserServiceImpl

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

application.yml

Spring:datasource:url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 120125hzy.type:  com.alibaba.druid.pool.DruidDataSource
mybatis-plus: # MyBatis Plus配置configuration: map-underscore-to-camel-case: true # 驼峰下划线转换
logging: # 控制台打印 SQLlevel:com:example:mapper: debug

4.2 转账案例

在Spring中,你可以使用@Transactional注解来实现声明式事务。这个注解可以应用于类级别或方法级别,具体取决于你想要控制事务的粒度。

接口编写

    /*** 测试转账*/String Transfer(Integer fromId, Integer toId, Double money);

生成单元测试,每次测试前两人余额都调整为 10000.00

@SpringBootTest
class UserServiceImplTest {@Autowiredprivate UserService userService;@Testvoid transfer() {String  ans =  userService.Transfer(1,2,600.0);assert ans.equals("转账成功");}
}
4.2.1 测试成功
 @Override@Transactionalpublic String Transfer(Integer fromId, Integer toId, Double money) {try {// 查询转出账户User fromUser = getById(fromId);if (fromUser == null) {throw new RuntimeException("转出账户不存在");}// 查询转入账户User toUser = getById(toId);if (toUser == null) {throw new RuntimeException("转入账户不存在");}// 检查余额是否足够if (fromUser.getBalance() < money) {throw new RuntimeException("余额不足");}// 更新转出账户余额fromUser.setBalance(fromUser.getBalance() - money);updateById(fromUser);// 更新转入账户余额toUser.setBalance(toUser.getBalance() + money);updateById(toUser);log.info("转账成功");return "转账成功";} catch (Exception e) {throw new RuntimeException("转账失败: " + e.getMessage());}}

测试通过

在这里插入图片描述

数据库也成功修改

在这里插入图片描述

4.2.2 测试回滚
   @Override@Transactionalpublic String Transfer(Integer fromId, Integer toId, Double money) {try {// 查询转出账户User fromUser = getById(fromId);if (fromUser == null) {throw new RuntimeException("转出账户不存在");}// 查询转入账户User toUser = getById(toId);if (toUser == null) {throw new RuntimeException("转入账户不存在");}// 检查余额是否足够if (fromUser.getBalance() < money) {throw new RuntimeException("余额不足");}// 更新转出账户余额fromUser.setBalance(fromUser.getBalance() - money);updateById(fromUser);// 手动抛出异常,测试事务回滚if (1 == 1) throw new RuntimeException("转账异常,事务回滚");// 更新转入账户余额toUser.setBalance(toUser.getBalance() + money);updateById(toUser);log.info("转账成功");return "转账成功";} catch (Exception e) {throw new RuntimeException("转账失败: " + e.getMessage());}}

测试未通过

在这里插入图片描述

数据库也没有改变

4.2.3 测试受检异常
    @Override@Transactionalpublic String Transfer(Integer fromId, Integer toId, Double money) throws SQLException {try {// 查询转出账户User fromUser = getById(fromId);if (fromUser == null) {throw new RuntimeException("转出账户不存在");}// 查询转入账户User toUser = getById(toId);if (toUser == null) {throw new RuntimeException("转入账户不存在");}// 检查余额是否足够if (fromUser.getBalance() < money) {throw new RuntimeException("余额不足");}// 更新转出账户余额fromUser.setBalance(fromUser.getBalance() - money);updateById(fromUser);// 手动抛出异常,测试事务回滚if (1 == 1) throw new RuntimeException("转账异常,事务回滚");// 更新转入账户余额toUser.setBalance(toUser.getBalance() + money);updateById(toUser);log.info("转账成功");return "转账成功";} catch (Exception e) {throw new SQLException("转账失败: " + e.getMessage());}}

测试未通过

在这里插入图片描述

事务未回滚

在这里插入图片描述

4.2.4 测试 rollBackFor 属性
@Transactional(rollbackFor = SQLException.class)

这次成功回滚了。

在这里插入图片描述

4.2.5 测试隔离级别

添加一个接口

    /***  测试付款*/String payment(Integer id,Double money);
    @Override@Transactional()public String payment(Integer id, Double money) {try {User user = getById(id);if (user == null) {throw new RuntimeException("支付账户不存在");}// 更新转出账户余额user.setBalance(user.getBalance()-money);updateById(user);Thread.sleep(5000);if (1 == 1) throw new RuntimeException("转账异常,事务回滚");log.info("支付成功");}catch (RuntimeException e){throw new RuntimeException("支付失败");}catch (Exception e){}return "支付成功";}

修改 Transfer()

@Override@Transactional(isolation = Isolation.READ_UNCOMMITTED)public String Transfer(Integer fromId, Integer toId, Double money){try {// 查询转出账户User fromUser = getById(fromId);if (fromUser == null) {throw new RuntimeException("转出账户不存在");}log.info("id: {}的余额:{}",fromUser.getId(),fromUser.getBalance());// 查询转入账户User toUser = getById(toId);if (toUser == null) {throw new RuntimeException("转入账户不存在");}// 检查余额是否足够if (fromUser.getBalance() < money) {throw new RuntimeException("余额不足");}// 更新转出账户余额fromUser.setBalance(fromUser.getBalance() - money);updateById(fromUser);// 更新转入账户余额toUser.setBalance(toUser.getBalance() + money);updateById(toUser);log.info("转账成功");return "转账成功";} catch (Exception e) {throw new RuntimeException("转账失败: " + e.getMessage());}}

测试函数

    @Testvoid test() throws InterruptedException {Thread t1 = new Thread(()->{userService.payment(1,500.0);});t1.start();Thread.sleep(2000);userService.Transfer(1,2,600.0);}

在这里插入图片描述

转账接口读取到了支付接口修改的数据,最终转账成功。而支付接口支付失败,但是张三还是少了1100

在这里插入图片描述

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

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

相关文章

什么情况下会产生StackOverflowError(栈溢出)和OutOfMemoryError(堆溢出)怎么排查

目录 一、概念 栈溢出&#xff08;StackOverflowError&#xff09; 堆溢出&#xff08;OutOfMemoryError&#xff09; 二、排查方法 栈溢出&#xff08;StackOverflowError&#xff09; 堆溢出&#xff08;OutOfMemoryError&#xff09; 相关的Java代码示例 栈溢出 堆溢…

K8S pod无损上下线

在最近的K8s服务上线过程中&#xff0c;我发现了一些问题&#xff0c;更具体的说&#xff0c;我在使用阿里云k8s的过程中注意到&#xff1a;会出现slb短时RT增加&#xff0c;Pod部署初期就达到了扩容上限&#xff0c;并且开始大量的扩容&#xff0c;这无疑占用了大量的k8s资源。…

接口自动化测试之Yaml数据驱动封装!

一、数据驱动&#xff1a;pytest.mark.parametrize(&#xff09; 首先看个样本&#xff1a; import pytestclass TestData:# parametrize有两个值&#xff0c;一个是args_name:参数名&#xff0c;一个是args_value:参数值,可以有多个&#xff0c;进行数据解包# args_value可以…

【广州华锐互动VRAR】VR戒毒科普宣传系统有效提高戒毒成功率

随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术已经逐渐渗透到各个领域&#xff0c;为人们的生活带来了前所未有的便利。在教育科普领域&#xff0c;VR技术的应用也日益广泛&#xff0c;本文将详细介绍广州华锐互动开发的VR戒毒科普宣传系统&#xff0…

serialVersionUID确保序列化版本

实现Serializable接口的目的是为类可持久化&#xff0c;比如在网络传输或本地存储&#xff0c;为系统的分布和异构部署提供先决条件。若没有序列化&#xff0c;现在我们所熟悉的远程调用&#xff0c;对象数据库都不可能存在&#xff0c; serialVersionUID适用于java序列化机制。…

万户协同办公平台ezoffice wpsservlet接口任意文件上传漏洞

声明 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 一、漏洞描述 万户ezOFFICE协同管理平台是一个综合信息基础应用平台&am…

生成模型之Flow-Based model

Flow-Based Model 文章目录 Flow-Based Model简介总览数学基础jacobian matrixdeterminant行列式Change of variable theorem 架构常见几种方法coupling layer采用1*1卷积进行channel shuffle 简介 ​ Flow-Based对概率密度函数的直接建模&#xff0c;这使得它们在数据生成和推…

Ubuntu22.04 使用Docker部署Neo4j出错 Exited(70)

项目场景&#xff1a; 最近需要使用Neo4j图数据库&#xff0c;因此打算使用docker部署 环境使用WSL Ubuntu22.04 问题描述 拉下最新Neo4j镜像&#xff0c;执行命令部署 启动容器脚本 docker run -d -p 7474:7474 -p 7687:7687 \ --name neo4j \ --env "NEO4J_AUTHneo…

封装了一个顺滑嵌套滚动的框架

首先查看效果图 就是开始滚动的时候&#xff0c;上面的头部和下面的内容是 一起滚动的&#xff0c;但是当滚动到segment 的时候&#xff0c;segment 是悬停 的&#xff0c;下面的tableView是分区的 架构设计 我们设计一个架构&#xff0c;以下面的tablView为主体&#xff0…

SiC MOSFET体二极管双极性退化及电流密度影响的研究

标题&#xff1a;Investigation of the bipolar degradation of SiC MOSFET body diodes and the influence of current density (IEEE International Reliability Physics Symposium (IRPS)) 摘要 摘要-双极退化在使用双极操作模式的4H-SiC器件中仍然是一个需要考虑的关键问题…

Excel 表列序号

题目链接 Excel 表列序号 题目描述 注意点 columnTitle 仅由大写英文组成1 < columnTitle.length < 7 解答思路 对于"CAB"&#xff0c;计算其序列号的思路&#xff1a;字母B的贡献值为2&#xff0c;字母A的贡献值为1 * 26&#xff0c;字母C的贡献值为3 * …

排查200M宽带下行速度低于100M问题(七十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

流量分析1--菜刀666

1&#xff1a;菜刀666&#xff1a; 题目描述 分析流量包&#xff0c;过滤http数据流 追踪TCP数据流 对比第5个流和第7个流发现&#xff0c;同样的目录下 多出了6666.jpg。猜测是由攻击者上传&#xff0c;直接在请求包里搜索FFD8--FFD9 保存为1.jpg 利用foremost工具对1.jpg进…

“无忧文件安全!上海迅软DSE文件加密软件助您轻松管控分公司数据!

许多大型企业集团由于旗下有着分布在不同城市的分支机构&#xff0c;因此在规划数据安全解决方案时&#xff0c;不适合采用市面上常见的集中式部署方式来管控各分部服务器&#xff0c;而迅软DSE文件加密软件支持采用分布式部署的方式来解决这一问题。 企业用户只需在总部内部署…

【移动端vant 地址选择滑动不了】

分析&#xff1a; H5页面直接在浏览器打开是没有任何问题的&#xff0c;但是内嵌到小程序中就会出现&#xff0c;目前已出现在抖音&#xff0c;快手&#xff0c;小程序中&#xff0c;其他的没有试 大致看了一下&#xff0c;滑动不了的原因&#xff0c;可能是页面禁止滑动或滚动…

《使用ThinkPHP6开发项目》 - 设置项目环境变量

《使用ThinkPHP6开发项目》 - 安装ThinkPHP框架-CSDN博客 在上一编我们讲了ThinkPHP6框架的创建&#xff0c;创建完成ThinkPHP6框架后&#xff0c;我们这里就可以开始设置我们的环境变量了。 安装完成ThinkPHP6框架生成的项目文件 修改项目配置我们修改项目config文件夹里的对…

MySQL 数据库如何实现 XA 规范?

本文我们来讨论 MySQL 的 XA 规范有哪些应用相关的内容。 MySQL 为我们提供了分布式事务解决方案&#xff0c;在前面的内容中提到过 binlog 的同步&#xff0c;其实是 MySQL XA 规范的一个应用&#xff0c;那么 XA 规范是如何定义的&#xff0c;具体又是如何应用的呢&#xff…

vscode如何为python设置静态类型检测工具:mypy

设置好之后的效果如下图所示&#xff0c;你可以在下方problems一栏看到工作区所有文件存在的问题 安装mypy就像其他插件一样&#xff0c;在extensions中搜索mypy&#xff0c;再install即可。 但是安装以后&#xff0c;我的vscode弹出了以下通知&#xff1a; The mypy daemon e…

使用DockerUI结合内网穿透工具轻松实现公网访问和管理docker容器

文章目录 前言1. 安装部署DockerUI2. 安装cpolar内网穿透3. 配置DockerUI公网访问地址4. 公网远程访问DockerUI5. 固定DockerUI公网地址 前言 DockerUI是一个docker容器镜像的可视化图形化管理工具。DockerUI可以用来轻松构建、管理和维护docker环境。它是完全开源且免费的。基…

【智能家居】六、摄像头安装实现监控功能点、人脸识别(face_recognition的使用)

一、定义及第三方库的说明 OCR &#xff08;光学字符识别&#xff09;文字识别、图像识别mjpg-streamer实时流式传输视频工具树莓派mjpg-streamer Face Recognition人脸识别 Dlib 计算机视觉问题的工具和算法face_recognition库OpenCV 计算机视觉和机器学习的开源库 三、香…