当前位置: 首页 > news >正文

Spring Boot业务代码中使用@Transactional事务失效踩坑点总结

1.概述

接着之前我们对Spring AOP以及基于AOP实现事务控制的上文,今天我们来看看平时在项目业务开发中使用声明式事务@Transactional的失效场景,并分析其失效原因,从而帮助开发人员尽量避免踩坑。

我们知道 Spring 声明式事务功能提供了极其方便的事务配置方式,配合 Spring Boot 的自动配置,大多数 Spring Boot 项目只需要在方法上标记 @Transactional 注解,即可一键开启方法的事务性配置。当然后端开发人员对数据库事务这个概念并不陌生,也知道如果整体考虑多个数据库操作要么成功要么失败时,需要通过数据库事务来实现多个操作的一致性和原子性。如下所示:

    @Override@Transactional(rollbackFor = Exception.class)public void addUser(UserParam param) {User user = PtcBeanUtils.copy(param, User.class);userDAO.insert(user);if (!CollectionUtils.isEmpty(param.getRoleIds())) {userRoleService.addUserRole(user.getId(), param.getRoleIds());}}

新增用户的同时还添加了用户角色,这里就是使用@Transactional来控制事务保证一致性的。但大多数开发仅限于为方法标记 @Transactional来开启声明式事务,认为就可以高枕无忧了,不会去关注事务是否有效、出错后事务是否正确回滚,也不会考虑复杂的业务代码中涉及多个子业务逻辑时,怎么正确处理事务。事务没有被正确处理,一般来说不会过于影响正常流程,也不容易在测试阶段被发现。但当系统越来越复杂、压力越来越大之后,就会带来大量的数据不一致问题,随后就是大量的人工介入查看和修复数据。

正是因为声明式事务@Transactional使用简单,所以很多开发人员不注重细节点,但是@Transactional条条框框还蛮多的,可谓是细节点拉满,如果不注意也不小心就会掉进坑里,今天就让我们一起来了解使用细节,把坑填平咯。

2.@Transactional

话不多说,先看看该注解定义

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {@AliasFor("transactionManager")String value() default "";@AliasFor("value")String transactionManager() default "";Propagation propagation() default Propagation.REQUIRED;Isolation isolation() default Isolation.DEFAULT;int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;boolean readOnly() default false;Class<? extends Throwable>[] rollbackFor() default {};String[] rollbackForClassName() default {};Class<? extends Throwable>[] noRollbackFor() default {};String[] noRollbackForClassName() default {};}

从上面看出@Transactional既可以作用于类上,也可以作用于方法上,**作用于类:表示所有该类的public**方法都配置相同的事务属性信息。接下来再看看其属性:

propagation: 设置事务的传播行为,主要解决是A方法调用B方法时,事务的传播方式问题的,默认值为 Propagation.REQUIRED,其他属性值信息如下:

事务传播行为解释
REQUIRED(默认值)A调用B,B需要事务,如果A有事务B就加入A的事务中,如果A没有事务,B就自己创建一个事务
REQUIRED_NEWA调用B,B需要新事务,如果A有事务就挂起,B自己创建一个新的事务
SUPPORTSA调用B,B有无事务无所谓,A有事务就加入到A事务中,A无事务B就以非事务方式执行
NOT_SUPPORTSA调用B,B以无事务方式执行,A如有事务则挂起
NEVERA调用B,B以无事务方式执行,A如有事务则抛出异常
MANDATORYA调用B,B要加入A的事务中,如果A无事务就抛出异常
NESTEDA调用B,B创建一个新事务,A有事务就作为嵌套事务存在,A没事务就以创建的新事务执行

**isolation :**事务的隔离级别,默认值为 Isolation.DEFAULT。指定事务的隔离级别,事务并发存在三大问题:脏读、不可重复读、幻读/虚读。可以通过设置事务的隔离级别来保证并发问题的出现,常用的是READ_COMMITTED 和REPEATABLE_READ

isolation属性解释
DEFAULT默认隔离级别,取决于当前数据库隔离级别,例如MySQL默认隔离级别是REPEATABLE_READ
READ_UNCOMMITTEDA事务可以读取到B事务尚未提交的事务记录,不能解决任何并发问题,安全性最低,性能最高
READ_COMMITTEDA事务只能读取到其他事务已经提交的记录,不能读取到未提交的记录。可以解决脏读问题,但是不能解决不可重复读和幻读
REPEATABLE_READA事务多次从数据库读取某条记录结果一致,可以解决不可重复读,不可以解决幻读
SERIALIZABLE串行化,可以解决任何并发问题,安全性最高,但是性能最低

**timeout :**事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

**readOnly:**指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

**rollbackFor:**用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

**noRollbackFor:**抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址:https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent

微信公众号Shepherd进阶笔记

交流探讨qun:Shepherd_126

3.@Transactional失效场景、原因及修正方式

3.1 同一个类中的方法通过this调用导致失效

    public void addUser(UserParam param) {User user = PtcBeanUtils.copy(param, User.class);// 新增用户userDAO.insert(user);// 添加用户角色this.addUserRole(user.getId(), param.getRoleIds());log.info("执行结束了");}@Transactional(rollbackFor = Exception.class)public void addUserRole(Long userId, List<Long> roleIds) {if (CollectionUtils.isEmpty(roleIds)) {return;}List<UserRole> userRoles = new ArrayList<>();roleIds.forEach(roleId -> {UserRole userRole = new UserRole();userRole.setUserId(userId);userRole.setRoleId(roleId);userRoles.add(userRole);});userRoleDAO.insertBatch(userRoles);throw new RuntimeException("发生异常咯");}

执行#addUser()会发现事务控制失效,发生异常事务并没有回滚,用户和角色绑定都插入成功了。

这里,我给出@Transactional 生效原则 1,必须通过代理过的类从外部调用目标方法才能生效.

Spring 是通过 AOP 技术对方法进行增强实现事务控制的,要调用增强过的方法必然是调用代理后的对象,而这里this是原生对象,并不是代理,自然就没有事务控制了。

修正方式:①:将this换成代理的userService, 可以自己注入自己@Resource private UserService userService,当然也可以不用注入,直接在Spring容器中获取userService这个bean ②将#addUser()方法开启事务即加上@Transactional(rollbackFor = Exception.class),这里本就该开启,只是为了演示失效情况没加上,因为在#addUser()里面有插入用户的操作涉及到事务的所以本要开启。当然如果#addUser()只是做一些判断、逻辑处理不涉及到数据库事务操作,那么这样解决就显得有点不太合适,而且容易导致另一种事务失效的情况,即因为没有正确处理异常,导致事务即便生效也不一定能回滚。

3.2 异常被catch“吃掉了”导致@Transactional失效

如下所示:

    @Transactional(rollbackFor = Exception.class)public void addUser(UserParam param) {try {User user = PtcBeanUtils.copy(param, User.class);// 完成一些逻辑处理.......// 添加用户角色this.addUserRole(user.getId(), param.getRoleIds());log.info("执行结束了");} catch (Exception e) {log.error(e.getMessage());}}@Transactional(rollbackFor = Exception.class)public void addUserRole(Long userId, List<Long> roleIds) {if (CollectionUtils.isEmpty(roleIds)) {return;}List<UserRole> userRoles = new ArrayList<>();roleIds.forEach(roleId -> {UserRole userRole = new UserRole();userRole.setUserId(userId);userRole.setRoleId(roleId);userRoles.add(userRole);});userRoleDAO.insertBatch(userRoles);throw new RuntimeException("发生异常咯");}

@Transactional生效原则2:只有异常传播出了标记了 @Transactional 注解的方法,事务才能回滚。之前我们总结过 基于AOP事务控制实现原理说过在 Spring的 TransactionAspectSupport 里有个 invokeWithinTransaction 方法,里面就是处理事务的逻辑。可以看到,只有捕获到异常才能进行后续事务处理:

	protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {......try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exception// 捕获到异常,进行回滚操作,如果我们在业务方法已经捕获掉异常,这里就捕获不到了,自然就不会回滚了completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}......return result;}}

可以看到,只有捕获到异常时才进行回滚操作,如果我们在业务方法已经捕获掉异常,这里就捕获不到了,自然就不会回滚了。

修正方式:就是对异常捕获尽量做到局部针对操作,不要笼统把整个方法的代码逻辑都包括进行,这样异常就抛出去了。

3.3 @Transactional 属性 rollbackFor 设置错误,导致异常不满足回滚条件

直接看代码:

    @Transactionalpublic void addUser(UserParam param) {User user = PtcBeanUtils.copy(param, User.class);.......// 添加用户角色this.addUserRole(user.getId(), param.getRoleIds());log.info("执行结束了");}public void addUserRole(Long userId, List<Long> roleIds) throws Exception {if (CollectionUtils.isEmpty(roleIds)) {return;}List<UserRole> userRoles = new ArrayList<>();roleIds.forEach(roleId -> {UserRole userRole = new UserRole();userRole.setUserId(userId);userRole.setRoleId(roleId);userRoles.add(userRole);});userRoleDAO.insertBatch(userRoles);throw new Exception("发生异常咯");}

这里#addUser()使用@transactional,但没有设置rollbackFor属性,且#addUserRole()抛出的异常是exception,不是RuntimeException,这样事务也失效了,因为默认情况下,出现 RuntimeException(非受检异常)或 Error 的时候,Spring才会回滚事务

从上面3.2小节的completeTransactionAfterThrowing(txInfo, ex);进去完成回滚操作会判断异常类型是否满足规定,DefaultTransactionAttribute 类能看到如下代码块,可以发现相关证据,通过注释也能看到 Spring 这么做的原因,大概的意思是受检异常一般是业务异常,或者说是类似另一种方法的返回值,出现这样的异常可能业务还能完成,所以不会主动回滚;而Error 或 RuntimeException 代表了非预期的结果,应该回滚:

	public boolean rollbackOn(Throwable ex) {return (ex instanceof RuntimeException || ex instanceof Error);}

修正方法:设置rollbackFor@Transactional(rollbackFor = Exception.class)

3.4 @Transactional 应用在非 public 修饰的方法上

    @Transactional(rollbackFor = Exception.class)private void addUserRole(Long userId, List<Long> roleIds) {if (CollectionUtils.isEmpty(roleIds)) {return;}List<UserRole> userRoles = new ArrayList<>();roleIds.forEach(roleId -> {UserRole userRole = new UserRole();userRole.setUserId(userId);userRole.setRoleId(roleId);userRoles.add(userRole);});userRoleDAO.insertBatch(userRoles);throw new RuntimeException("发生异常咯");}

idea也会提示爆红:

Spring通过CGLIB动态代理来增强生产代理对象,CGLIB 通过继承方式实现代理类,private 方法在子类不可见,自然也就无法进行事务增强。s在基于AOP事务控制实现原理一文中也分析过,会调用到AbstractFallbackTransactionAttributeSourcecomputeTransactionAttribute()方法

 @Nullableprotected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {// Don't allow no-public methods as required.if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}......}

修正方式:自然是改成public

3.5 @Transactional 注解传播属性 propagation 设置错误

如上面我们新增的用户的同时要添加用户角色,但是假如我们希望即使添加角色错误了,还可以正常新增用户。

 public void addUser(UserParam param) {String username = param.getUsername();checkUsernameUnique(username);User user = PtcBeanUtils.copy(param, User.class);// 添加用户userDAO.insert(user);// 添加用户角色userRoleService.addUserRole(user.getId(), param.getRoleIds());}

#userRoleService.addUserRole()

  @Transactional(rollbackFor = Exception.class)private void addUserRole(Long userId, List<Long> roleIds) {if (CollectionUtils.isEmpty(roleIds)) {return;}List<UserRole> userRoles = new ArrayList<>();roleIds.forEach(roleId -> {UserRole userRole = new UserRole();userRole.setUserId(userId);userRole.setRoleId(roleId);userRoles.add(userRole);});userRoleDAO.insertBatch(userRoles);throw new RuntimeException("发生异常咯");}

你会发现只会同时插入失败,无法实现上面所说的。这时候你可能会想到,既然addUserRole()抛出了异常不能插入用户角色,但是addUser()不想受影响,正常添加用户,那么何不在addUser()里面对userRoleService.addUserRole()进行异常捕获,不就可以解决问题了吗?真是如此吗,就让我们来验证一下:

    @Transactional(rollbackFor = Exception.class)public void addUser(UserParam param) {User user = PtcBeanUtils.copy(param, User.class);// 添加用户userDAO.insert(user);// 添加用户角色try {userRoleService.addUserRole(user.getId(), param.getRoleIds());} catch (Exception e) {log.error(e.getMessage());}}

执行会发现,用户同样没有添加成功,看日志报错:

[1689568520410750976] [ERROR] [2023-08-10 17:25:02.023] [http-nio-18888-exec-1@56682]  com.plasticene.fast.service.impl.UserServiceImpl addUser : 发生异常咯
[1689568520410750976] [ERROR] [2023-08-10 17:25:02.097] [http-nio-18888-exec-1@56682]  com.plasticene.boot.web.core.global.GlobalExceptionHandler exceptionHandler : 【系统异常】
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-onlyat org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:870)

可以看到发生异常咯是我们在addUser()中捕获到输出的,但是紧接着下一行发现有报出一个异常UnexpectedRollbackException

原因是,主方法添加用户的逻辑和子方法添加用户角色的逻辑是同一个事务,子逻辑标记了事务需要回滚,主逻辑自然也不能提交了。

修正方式:其实要想新增用户角色失败不影响添加用户,只需要让新增用户角色单独开启一个新事务即可。

   @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)public void addUserRole(Long userId, List<Long> roleIds) {List<UserRole> userRoles = new ArrayList<>();roleIds.forEach(roleId -> {UserRole userRole = new UserRole();userRole.setUserId(userId);userRole.setRoleId(roleId);userRoles.add(userRole);});userRoleDAO.insertBatch(userRoles);throw new RuntimeException("发生异常啦!");}

3.6 @Transactional长事务导致生产事故

很多开发都觉得Spring的声明式事务使用非常简单,即@Transactional,所以从来不注重细节。当 Spring 遇到该注解时,会自动从数据库连接池中获取 connection,并开启事务然后绑定到 ThreadLocal 上,对于@Transactional注解包裹的整个方法都是使用同一个connection连接。如果我们出现了耗时的操作,比如第三方接口调用、业务逻辑复杂、大批量数据处理等就会导致我们我们占用这个connection的时间会很长,数据库连接一直被占用不释放。一旦类似操作过多,就会导致数据库连接池耗尽。这就是典型的长事务问题

长事务引发的常见危害有:

  1. 数据库连接池被占满,应用无法获取连接资源;
  2. 容易引发数据库死锁;
  3. 数据库回滚时间长;
  4. 在主从架构中会导致主从延时变大。

服务系统开始出现故障:数据库监控平台一直收到告警短信,数据库连接不足,出现大量死锁;日志显示调用流程引擎接口出现大量超时;同时一直提示CannotGetJdbcConnectionException,数据库连接池连接占满。

要想解决这个问题其实也不难,只需要对方法进行拆分,将不需要事务管理的逻辑与事务操作分开,这样就可以有效控制事务的时长从而避免长事务。当然对一个方法逻辑拆分成多个子方法很有可能造成上面叙述的事务不生效的情况,不过我相信你看到上面的总结肯定没问题啦。

4.总结

Spring的声明式事务使用@Transactional注解在开发时确实很方便,但是稍有不慎使用不当就会导致事务失效数据不一致、甚至是系统数据库性能问题。所以上面满满的干货总结都是出自日常工作中碰到的,有效帮你避坑。

相关文章:

Spring Boot业务代码中使用@Transactional事务失效踩坑点总结

1.概述 接着之前我们对Spring AOP以及基于AOP实现事务控制的上文&#xff0c;今天我们来看看平时在项目业务开发中使用声明式事务Transactional的失效场景&#xff0c;并分析其失效原因&#xff0c;从而帮助开发人员尽量避免踩坑。 我们知道 Spring 声明式事务功能提供了极其…...

知识体系总结(九)设计原则、设计模式、分布式、高性能、高可用

文章目录 架构设计为什么要进行技术框架的设计 六大设计原则一、单一职责原则二、开闭原则三、依赖倒置原则四、接口分离原则五、迪米特法则&#xff08;又称最小知道原则&#xff09;六、里氏替换原则案例诠释 常见设计模式构造型单例模式工厂模式简单工厂工厂方法 生成器模式…...

Springboot 集成Beetl模板

一、在启动类下的pom.xml中导入依赖&#xff1a; <!--beetl模板引擎--><dependency><groupId>com.ibeetl</groupId><artifactId>beetl</artifactId><version>2.9.8</version></dependency> 二、 配置 beetl需要的Beetl…...

RabbitMQ查询队列使用情况和消费者详情实现

spring-boot-starter-amqp spring-boot-starter-amqp是Spring Boot框架中与AMQP(高级消息队列协议)相关的自动配置启动器。它提供了使用AMQP进行消息传递和异步通信的功能。 以下是spring-boot-starter-amqp的主要特性和功能: 自动配置:spring-boot-starter-amqp通过自动…...

Spark第二课RDD的详解

1.前言 RDD JAVA中的IO 1.小知识点穿插 1. 装饰者设计模式 装饰者设计模式:本身功能不变,扩展功能. 举例&#xff1a; 数据流的读取 一层一层的包装&#xff0c;进而将功能进行进一步的扩展 2.sleep和wait的区别 本质区别是字体不一样,sleep斜体,wait正常 斜体是静态方法…...

人工智能学习框架—飞桨Paddle人工智能

1.人工智能框架 机器学习的三要素&#xff1a;模型、学习策略、优化算法。 当我们用机器学习来解决一些模式识别任务时&#xff0c;一般的流程包含以下几个步骤&#xff1a; 1.1.浅层学习和深度学习 浅层学习(Shallow Learning)&#xff1a;不涉及特征学习&#xff0c;其特征…...

SElinux 导致 Keepalived 检测脚本无法执行

哈喽大家好&#xff0c;我是咸鱼 今天我们来看一个关于 Keepalived 检测脚本无法执行的问题 一位粉丝后台私信我&#xff0c;说他部署的 keepalived 集群 vrrp_script 模块中的脚本执行失败了&#xff0c;但是手动执行这个脚本却没有任何问题 这个问题也是咸鱼第一次遇到&…...

2022年电赛C题——小车跟随行驶系统——做题记录以及经验分享

前言 自己打算将做过的电赛真题&#xff0c;主要包含控制组的&#xff0c;近几年出现的小车控制题目&#xff0c;自己做过的真题以及在准备电赛期间刷真题出现的问题以及经验分享给大家 这次带来的是22年电赛C题——小车跟随行驶系统&#xff0c;这道题目指定使用的是TI的单片…...

vscode + python

序 参考链接&#xff1a; 【教程】VScode中配置Python运行环境_哔哩哔哩_bilibili Python部分 Python Releases for Windows | Python.org vscode部分 Visual Studio Code - Code Editing. Redefined 一路next&#xff0c;全部勾上&#xff1a; 就可以了&#xff1a; 安装插…...

badgerdb里面的事务

事务的ACID A 原子性&#xff08;Atomicity&#xff09; 多步骤操作&#xff0c;只能是两种状态&#xff0c;要么所有的步骤都成功执行&#xff0c;要么所有的步骤都不执行&#xff0c;举例说明就是小明向小红转账30元的场景&#xff0c;拆分成两个步骤&#xff0c;步骤1&#…...

C# this.Invoke(new Action(() => { /* some code */ }))用法说明

在 C# 中&#xff0c;this.Invoke(new Action(() > { /* some code */ })) 是一种用于在 UI 线程上执行代码的方法&#xff0c;通常用于在后台线程中更新 UI 控件的值或执行其他需要在 UI 线程上执行的操作。 在 Windows Forms 或 WPF 等图形界面应用程序中&#xff0c;UI …...

MongoDB:MySQL,Redis,ES,MongoDB的应用场景

简单明了说明MySQL,ES,MongoDB的各自特点,应用场景,以及MongoDB如何使用的第一章节. 一. SQL与NoSQL SQL被称为结构化查询语言.是传统意义上的数据库,数据之间存在很明确的关联关系,例如主外键关联,这种结构可以确保数据的完整性(数据没有缺失并且正确).但是正因为这种严密的结…...

leetcode每日一题_2682.找出转圈游戏输家

2682.找出转圈游戏输家 题目: n 个朋友在玩游戏。这些朋友坐成一个圈&#xff0c;按 顺时针方向 从 1 到 n 编号。从第 i 个朋友的位置开始顺时针移动 1 步会到达第 (i 1) 个朋友的位置&#xff08;1 < i < n&#xff09;&#xff0c;而从第 n 个朋友的位置开始顺时针移…...

OpenCV之薄板样条插值(ThinPlateSpline)

官方文档&#xff1a;OpenCV: cv::ThinPlateSplineShapeTransformer Class Reference 使用方法&#xff1a; 头文件&#xff1a;#include <opencv2/shape/shape_transformer.hpp> &#xff08;1&#xff09;点匹配 一般根据有多少个样本&#xff08;或者点&#xff09;…...

034_小驰私房菜_[问题复盘] Qcom平台,某些三方相机拍照旋转90度

全网最具价值的Android Camera开发学习系列资料~ 作者:8年Android Camera开发,从Camera app一直做到Hal和驱动~ 欢迎订阅,相信能扩展你的知识面,提升个人能力~ 【一、问题】 某些三方相机,预览正常,拍照旋转90度 【二、问题排查】 1 ) HAL这边Jpeg编码数据在哪个地方…...

【TI-CCS笔记】工程编译配置 bin文件的编译和生成 各种架构的Post-build配置汇总

【TI-CCS笔记】工程编译配置 bin文件的编译和生成 各种架构的Post-build配置汇总 TI编译器分类 在CCS按照目录下 有个名为${CG_TOOL_ROOT}的目录 其下就是当前工程的编译器 存放目录为&#xff1a; C:\ti\ccs1240\ccs\tools\compiler按类型分为五种&#xff1a; ti-cgt-arm…...

深入探索Java中的File类与IO操作:从路径到文件的一切

文章目录 1. File类的作用与构造方法2. File类常用方法&#xff1a;获取、判断和创建2.1 获取功能方法2.2 判断功能方法2.3 创建和删除功能方法2.4 目录的遍历方法 3. 递归&#xff1a;探索更深的层次代码示例&#xff1a;递归遍历文件夹 结论 &#x1f389;欢迎来到Java学习路…...

Python 处理 Excel 表格的 14 个常用操作

目录 1. 安装依赖库 2. 导入库 3. 读取Excel文件 4. 写入Excel文件 5. 创建工作表 6. 访问工作表 7. 读取单元格数据 8. 写入单元格数据 9. 获取行数和列数 10. 过滤数据 11. 排序数据 12. 添加新行 13. 删除行或列 14. 计算汇总统计 总结 无论是数据分析师、财…...

PyQt有哪些主要组件?

这是一个非常强大的跨平台GUI库&#xff0c;可以让你用Python语言创建美观且功能强大的桌面应用程序。让我们先来了解一下它的主要组件。 首先&#xff0c;我们要介绍的是窗口。窗口是PyQt应用程序的基本元素&#xff0c;所有的GUI元素都放置在窗口中。你可以创建主窗口、模态…...

力推C语言必会题目终章(完结篇)

W...Y的主页 &#x1f60a; 代码仓库分享 &#x1f495; 今天是分享C语言必会题目最终章&#xff0c;全部都是硬货&#xff0c;大家都坐好准备开始喽&#xff01;&#xff01;&#xff01; 编写一个函数&#xff0c;计算字符串中含有的不同字符的个数。字符在 ASCII 码范围内…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

Docker 运行 Kafka 带 SASL 认证教程

Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明&#xff1a;server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具&#xff0c;可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件&#xff0c;也不需要在线上传文件&#xff0c;保护您的隐私。 工具截图 主要特点 &#x1f680; 快速转换&#xff1a;本地转换&#xff0c;无需等待上…...

GitFlow 工作模式(详解)

今天再学项目的过程中遇到使用gitflow模式管理代码&#xff0c;因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存&#xff0c;无论是github还是gittee&#xff0c;都是一种基于git去保存代码的形式&#xff0c;这样保存代码…...

R 语言科研绘图第 55 期 --- 网络图-聚类

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…...

第7篇:中间件全链路监控与 SQL 性能分析实践

7.1 章节导读 在构建数据库中间件的过程中&#xff0c;可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中&#xff0c;必须做到&#xff1a; &#x1f50d; 追踪每一条 SQL 的生命周期&#xff08;从入口到数据库执行&#xff09;&#…...

uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)

UniApp 集成腾讯云 IM 富媒体消息全攻略&#xff08;地理位置/文件&#xff09; 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型&#xff0c;核心实现方式&#xff1a; 标准消息类型&#xff1a;直接使用 SDK 内置类型&#xff08;文件、图片等&#xff09;自…...