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

Apache Seata透过源码解决SeataAT模式整合Mybatis-Plus失去MP特性的问题

本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。
本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。
Apache Seata透过源码解决SeataAT模式整合Mybatis-Plus失去MP特性的问题

透过源码解决SeataAT模式整合Mybatis-Plus失去MP特性的问题

项目地址:https://gitee.com/itCjb/springboot-dubbo-mybatisplus-seata

本文作者:FUNKYE(陈健斌),杭州某互联网公司主程。

介绍

Mybatis-Plus:MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

MP配置:

<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/>
</bean>

Seata:Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

AT模式机制:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

分析原因

​ 1.首先我们通过介绍,可以看到,mp是需要注册sqlSessionFactory,注入数据源,而Seata是通过代理数据源来保证事务的正常回滚跟提交。

​ 2.我们来看基于seata的官方demo提供的SeataAutoConfig的代码

package org.test.config;import javax.sql.DataSource; import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;import io.seata.rm.datasource.DataSourceProxy;
import io.seata.spring.annotation.GlobalTransactionScanner;@Configuration
public class SeataAutoConfig {@Autowired(required = true)private DataSourceProperties dataSourceProperties;private final static Logger logger = LoggerFactory.getLogger(SeataAutoConfig.class);@Bean(name = "dataSource") // 声明其为Bean实例@Primary // 在同样的DataSource中,首先使用被标注的DataSourcepublic DataSource druidDataSource() {DruidDataSource druidDataSource = new DruidDataSource();logger.info("dataSourceProperties.getUrl():{}",dataSourceProperties.getUrl());druidDataSource.setUrl(dataSourceProperties.getUrl());druidDataSource.setUsername(dataSourceProperties.getUsername());druidDataSource.setPassword(dataSourceProperties.getPassword());druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());druidDataSource.setInitialSize(0);druidDataSource.setMaxActive(180);druidDataSource.setMaxWait(60000);druidDataSource.setMinIdle(0);druidDataSource.setValidationQuery("Select 1 from DUAL");druidDataSource.setTestOnBorrow(false);druidDataSource.setTestOnReturn(false);druidDataSource.setTestWhileIdle(true);druidDataSource.setTimeBetweenEvictionRunsMillis(60000);druidDataSource.setMinEvictableIdleTimeMillis(25200000);druidDataSource.setRemoveAbandoned(true);druidDataSource.setRemoveAbandonedTimeout(1800);druidDataSource.setLogAbandoned(true);logger.info("装载dataSource........");return druidDataSource;}/*** init datasource proxy* * @Param: druidDataSource datasource bean instance* @Return: DataSourceProxy datasource proxy*/@Beanpublic DataSourceProxy dataSourceProxy(DataSource dataSource) {logger.info("代理dataSource........");return new DataSourceProxy(dataSource);}@Beanpublic SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();factory.setDataSource(dataSourceProxy);factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml"));return factory.getObject();}/*** init global transaction scanner** @Return: GlobalTransactionScanner*/@Beanpublic GlobalTransactionScanner globalTransactionScanner() {logger.info("配置seata........");return new GlobalTransactionScanner("test-service", "test-group");}
}

首先看到我们的seata配置数据源的类里,我们配置了一个数据源,然后又配置了一个seata代理datasource的bean,这时候.

然后我们如果直接启动mp整合seata的项目会发现,分页之类的插件会直接失效,连扫描mapper都得从代码上写,这是为什么呢?

通过阅读以上代码,是因为我们另外的配置了一个sqlSessionFactory,导致mp的sqlSessionFactory失效了,这时候我们发现了问题的所在了,即使我们不配置sqlSessionFactoryl,也会因为mp所使用的数据源不是被seata代理过后的数据源,导致分布式事务失效.但是如何解决这个问题呢?

这时候我们需要去阅读mp的源码,找到他的启动类,一看便知

/** Copyright (c) 2011-2020, baomidou (jobob@qq.com).* <p>* Licensed under the Apache License, Version 2.0 (the "License"); you may not* use this file except in compliance with the License. You may obtain a copy of* the License at* <p>* https://www.apache.org/licenses/LICENSE-2.0* <p>* Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the* License for the specific language governing permissions and limitations under* the License.*/
package com.baomidou.mybatisplus.autoconfigure;import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;import javax.sql.DataSource;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;/*** {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a* {@link SqlSessionFactory} and a {@link SqlSessionTemplate}.* <p>* If {@link org.mybatis.spring.annotation.MapperScan} is used, or a* configuration file is specified as a property, those will be considered,* otherwise this auto-configuration will attempt to register mappers based on* the interface definitions in or under the root auto-configuration package.* </p>* <p> copy from {@link org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration}</p>** @author Eddú Meléndez* @author Josh Long* @author Kazuki Shimizu* @author Eduardo Macarrón*/
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisPlusAutoConfiguration implements InitializingBean {private static final Logger logger = LoggerFactory.getLogger(MybatisPlusAutoConfiguration.class);private final MybatisPlusProperties properties;private final Interceptor[] interceptors;private final TypeHandler[] typeHandlers;private final LanguageDriver[] languageDrivers;private final ResourceLoader resourceLoader;private final DatabaseIdProvider databaseIdProvider;private final List<ConfigurationCustomizer> configurationCustomizers;private final List<MybatisPlusPropertiesCustomizer> mybatisPlusPropertiesCustomizers;private final ApplicationContext applicationContext;public MybatisPlusAutoConfiguration(MybatisPlusProperties properties,ObjectProvider<Interceptor[]> interceptorsProvider,ObjectProvider<TypeHandler[]> typeHandlersProvider,ObjectProvider<LanguageDriver[]> languageDriversProvider,ResourceLoader resourceLoader,ObjectProvider<DatabaseIdProvider> databaseIdProvider,ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mybatisPlusPropertiesCustomizerProvider,ApplicationContext applicationContext) {this.properties = properties;this.interceptors = interceptorsProvider.getIfAvailable();this.typeHandlers = typeHandlersProvider.getIfAvailable();this.languageDrivers = languageDriversProvider.getIfAvailable();this.resourceLoader = resourceLoader;this.databaseIdProvider = databaseIdProvider.getIfAvailable();this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();this.mybatisPlusPropertiesCustomizers = mybatisPlusPropertiesCustomizerProvider.getIfAvailable();this.applicationContext = applicationContext;}@Overridepublic void afterPropertiesSet() {if (!CollectionUtils.isEmpty(mybatisPlusPropertiesCustomizers)) {mybatisPlusPropertiesCustomizers.forEach(i -> i.customize(properties));}checkConfigFileExists();}private void checkConfigFileExists() {if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());Assert.state(resource.exists(),"Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");}}@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")@Bean@ConditionalOnMissingBeanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {// TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBeanMybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();factory.setDataSource(dataSource);factory.setVfs(SpringBootVFS.class);if (StringUtils.hasText(this.properties.getConfigLocation())) {factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));}applyConfiguration(factory);if (this.properties.getConfigurationProperties() != null) {factory.setConfigurationProperties(this.properties.getConfigurationProperties());}if (!ObjectUtils.isEmpty(this.interceptors)) {factory.setPlugins(this.interceptors);}if (this.databaseIdProvider != null) {factory.setDatabaseIdProvider(this.databaseIdProvider);}if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());}if (this.properties.getTypeAliasesSuperType() != null) {factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());}if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());}if (!ObjectUtils.isEmpty(this.typeHandlers)) {factory.setTypeHandlers(this.typeHandlers);}if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {factory.setMapperLocations(this.properties.resolveMapperLocations());}// TODO 对源码做了一定的修改(因为源码适配了老旧的mybatis版本,但我们不需要适配)Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();if (!ObjectUtils.isEmpty(this.languageDrivers)) {factory.setScriptingLanguageDrivers(this.languageDrivers);}Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);// TODO 自定义枚举包if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());}// TODO 此处必为非 NULLGlobalConfig globalConfig = this.properties.getGlobalConfig();// TODO 注入填充器if (this.applicationContext.getBeanNamesForType(MetaObjectHandler.class,false, false).length > 0) {MetaObjectHandler metaObjectHandler = this.applicationContext.getBean(MetaObjectHandler.class);globalConfig.setMetaObjectHandler(metaObjectHandler);}// TODO 注入主键生成器if (this.applicationContext.getBeanNamesForType(IKeyGenerator.class, false,false).length > 0) {IKeyGenerator keyGenerator = this.applicationContext.getBean(IKeyGenerator.class);globalConfig.getDbConfig().setKeyGenerator(keyGenerator);}// TODO 注入sql注入器if (this.applicationContext.getBeanNamesForType(ISqlInjector.class, false,false).length > 0) {ISqlInjector iSqlInjector = this.applicationContext.getBean(ISqlInjector.class);globalConfig.setSqlInjector(iSqlInjector);}// TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBeanfactory.setGlobalConfig(globalConfig);return factory.getObject();}// TODO 入参使用 MybatisSqlSessionFactoryBeanprivate void applyConfiguration(MybatisSqlSessionFactoryBean factory) {// TODO 使用 MybatisConfigurationMybatisConfiguration configuration = this.properties.getConfiguration();if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {configuration = new MybatisConfiguration();}if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {for (ConfigurationCustomizer customizer : this.configurationCustomizers) {customizer.customize(configuration);}}factory.setConfiguration(configuration);}@Bean@ConditionalOnMissingBeanpublic SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {ExecutorType executorType = this.properties.getExecutorType();if (executorType != null) {return new SqlSessionTemplate(sqlSessionFactory, executorType);} else {return new SqlSessionTemplate(sqlSessionFactory);}}/*** This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use* {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box,* similar to using Spring Data JPA repositories.*/public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {private BeanFactory beanFactory;@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {if (!AutoConfigurationPackages.has(this.beanFactory)) {logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");return;}logger.debug("Searching for mappers annotated with @Mapper");List<String> packages = AutoConfigurationPackages.get(this.beanFactory);if (logger.isDebugEnabled()) {packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));}BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);builder.addPropertyValue("processPropertyPlaceHolders", true);builder.addPropertyValue("annotationClass", Mapper.class);builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);Stream.of(beanWrapper.getPropertyDescriptors())// Need to mybatis-spring 2.0.2+.filter(x -> x.getName().equals("lazyInitialization")).findAny().ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());}@Overridepublic void setBeanFactory(BeanFactory beanFactory) {this.beanFactory = beanFactory;}}/*** If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan* mappers based on the same component-scanning path as Spring Boot itself.*/@Configuration@Import(AutoConfiguredMapperScannerRegistrar.class)@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {@Overridepublic void afterPropertiesSet() {logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");}}
}

看到mp启动类里的sqlSessionFactory方法了吗,他也是一样的注入一个数据源,这时候大家应该都知道解决方法了吧?

没错,就是把被代理过的数据源给放到mp的sqlSessionFactory中.

很简单,我们需要稍微改动一下我们的seata配置类就行了

package org.test.config;import javax.sql.DataSource;import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;import com.alibaba.druid.pool.DruidDataSource;import io.seata.rm.datasource.DataSourceProxy;
import io.seata.spring.annotation.GlobalTransactionScanner;@Configuration
@MapperScan("com.baomidou.springboot.mapper*")
public class SeataAutoConfig {@Autowired(required = true)private DataSourceProperties dataSourceProperties;private final static Logger logger = LoggerFactory.getLogger(SeataAutoConfig.class);private DataSourceProxy dataSourceProxy;@Bean(name = "dataSource") // 声明其为Bean实例@Primary // 在同样的DataSource中,首先使用被标注的DataSourcepublic DataSource druidDataSource() {DruidDataSource druidDataSource = new DruidDataSource();logger.info("dataSourceProperties.getUrl():{}", dataSourceProperties.getUrl());druidDataSource.setUrl(dataSourceProperties.getUrl());druidDataSource.setUsername(dataSourceProperties.getUsername());druidDataSource.setPassword(dataSourceProperties.getPassword());druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());druidDataSource.setInitialSize(0);druidDataSource.setMaxActive(180);druidDataSource.setMaxWait(60000);druidDataSource.setMinIdle(0);druidDataSource.setValidationQuery("Select 1 from DUAL");druidDataSource.setTestOnBorrow(false);druidDataSource.setTestOnReturn(false);druidDataSource.setTestWhileIdle(true);druidDataSource.setTimeBetweenEvictionRunsMillis(60000);druidDataSource.setMinEvictableIdleTimeMillis(25200000);druidDataSource.setRemoveAbandoned(true);druidDataSource.setRemoveAbandonedTimeout(1800);druidDataSource.setLogAbandoned(true);logger.info("装载dataSource........");dataSourceProxy = new DataSourceProxy(druidDataSource);return dataSourceProxy;}/*** init datasource proxy* * @Param: druidDataSource datasource bean instance* @Return: DataSourceProxy datasource proxy*/@Beanpublic DataSourceProxy dataSourceProxy() {logger.info("代理dataSource........");return dataSourceProxy;}/*** init global transaction scanner** @Return: GlobalTransactionScanner*/@Beanpublic GlobalTransactionScanner globalTransactionScanner() {logger.info("配置seata........");return new GlobalTransactionScanner("test-service", "test-group");}
}

看代码,我们去掉了自己配置的sqlSessionFactory,直接让DataSource bean返回的是一个被代理过的bean,并且我们加入了@Primary,导致mp优先使用我们配置的数据源,这样就解决了mp因为seata代理了数据源跟创建了新的sqlSessionFactory,导致mp的插件,组件失效的bug了!

总结

踩到坑不可怕,主要又耐心的顺着每个组件实现的原理,再去思考,查找对应冲突的代码块,你一定能找到个兼容二者的方法。

相关文章:

Apache Seata透过源码解决SeataAT模式整合Mybatis-Plus失去MP特性的问题

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Apache Seata透过源码解决SeataAT模式整合Mybatis-Plus失去MP特性的问题 透过源码解决SeataAT…...

1.2 如何让机器说人话?万字长文回顾自然语言处理(NLP)的前世今生 —— 《带你自学大语言模型》系列

本系列目录 《带你自学大语言模型》系列部分目录及计划&#xff0c;完整版目录见&#xff1a;带你自学大语言模型系列 —— 前言 第一部分 走进大语言模型&#xff08;科普向&#xff09; 第一章 走进大语言模型 1.1 从图灵机到GPT&#xff0c;人工智能经历了什么&#xff1…...

【QT】按钮类控件

按钮类控件 按钮类控件1. PushButton2. Radio Button3. Check Box4. Tool Button 按钮类控件 1. PushButton 使⽤ QPushButton 表示⼀个按钮&#xff0c;这也是当前我们最熟悉的⼀个控件了. QPushButton 继承⾃ QAbstractButton . 这个类是⼀个抽象类. 是其他按钮的⽗类. 在…...

RedHat运维-Linux软件包管理基础-RHEL9软件包管理基础

Linux软件包管理基础-RHEL9 1. 对于RHEL9来说&#xff0c;软件包管理基础分为增、删、改、查四个部分。对于增来说&#xff0c;有&#xff1a;增加一个仓库的信息文件、启用一个仓库的信息文件、安装rpm包、解压rpm包、安装软件、安装软件组、更新软件。在这里先讲软件包管理中…...

uniapp----- 判断小程序版本有没有更新

const updateManager uni.getUpdateManager();// 当向小程序后台请求完新版本信息&#xff0c;会进行回调updateManager.onCheckForUpdate(function (res) {console.log(是否有新版本, res.hasUpdate);});// 当新版本下载完成&#xff0c;会进行回调updateManager.onUpdateRea…...

Spring Boot的无缝衔接:深入解析与实践

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ &#x1f680;The begin&#x1f697;点点关注&#xff0c;收藏不迷路&#x1f6a9; 引言 在快速迭代的软件开发环境中&#xff0c;无缝衔接是提升开发效率、降低维护成本、增强系统稳定性的关键。Spring Boo…...

在Linux上查找文件的2个好用的命令

在Linux上查找文件&#xff0c;两个非常好用的命令是find和locate。 find命令 find命令非常强大&#xff0c;可以在指定目录下查找符合条件的文件。你可以根据文件名、文件类型、大小、修改日期等多种条件来查找文件。例如&#xff0c;要在当前目录及其子目录下查找所有扩展名…...

实现WebSocket聊天室功能

实现WebSocket聊天室功能 什么是WebSocket&#xff1f;WebSocket的工作原理服务器端实现客户端实现 在现代Web开发中&#xff0c;实时通信已经变得越来越重要。传统的HTTP协议由于其无状态和单向通信的特点&#xff0c;无法很好地满足实时通信的需求。而WebSocket协议则应运而生…...

qt opencv 应用举例

在Qt中使用OpenCV可以实现各种图像处理和计算机视觉任务。以下是一些Qt与OpenCV联合应用的具体举例&#xff1a; 1. 图像读取与显示 读取图像&#xff1a;使用OpenCV的imread函数可以方便地读取各种格式的图像文件&#xff0c;如.bmp、.jpg、.png等。这个函数返回一个Mat对象…...

QT5.12环境搭建与源码编译

一、概述 QT版本&#xff1a;QT5.12.10 Qt网址&#xff1a;http://download.qt.io/archive/qt/ 编译平台 ubuntu18.04 二、安装交叉编译工具链 1、获取交叉编译工具链 一般如果是编译系统如果有对应的gcc 就是用这个就可以了 比如rk3128 lin…...

Android中android.fg线程和android.ui线程分别代表什么?

Android中android.fg线程和android.ui线程分别代表什么&#xff1f; android.fg线程&#xff08;FgThread&#xff09;&#xff1a; FgThread是Android系统中一个特殊的线程&#xff0c;其类定义大致为public final class FgThread extends ServiceThread。它主要用于提供一个…...

MATLAB 2024b 更新了些什么?

MATLAB 2024b版本已经推出了预览版&#xff0c;本期介绍一些MATLAB部分的主要的更新内容。 帮助浏览器被移除 在此前的版本&#xff0c;当我们从MATLAB中访问帮助文档时&#xff0c;默认会通过MATLAB的帮助浏览器&#xff08;Help browser&#xff09;。 2024b版本开始&…...

SSM高校教师教学质量评估系统-计算机毕业设计源码03344

摘要 在高等教育中&#xff0c;教学质量是培养优秀人才的关键。为了提高教学质量&#xff0c;高校需要建立一套科学、有效的教师教学质量评估系统。本研究采用 SSM技术框架&#xff0c;旨在开发一款高校教师教学质量评估系统。 SSM框架作为一种成熟的Java开发框架&#xff0c;具…...

【Linux进阶】文件系统5——ext2文件系统(inode)

1.再谈inode (1) 理解inode&#xff0c;要从文件储存说起。 文件储存在硬盘上&#xff0c;硬盘的最小存储单位叫做"扇区"&#xff08;Sector&#xff09;。每个扇区储存512字节&#xff08;相当于0.5KB&#xff09;。操作系统读取硬盘的时候&#xff0c;不会一个个…...

华为云简介

前言 华为云是华为的云服务品牌&#xff0c;将华为30多年在ICT领域的技术积累和产品解决方案开放给客户&#xff0c;致力于提供稳定可靠、安全可信、可持续创新的云服务&#xff0c;赋能应用、使能数据、做智能世界的“黑土地”&#xff0c;推进实现“用得起、用得好、用得放心…...

Doris数据库---建表、调整表结构操作

一、简介 本文章主讲创建 Doris 自维护的表的语法&#xff0c;以下为本人最近为数据中台接入doris所踩的坑及其解决方案&#xff0c;欢迎点评。 二、doris建表语法&#xff1a; 官网建表语法网址链接&#xff1a;CREATE-TABLE - Apache Doris 官网建表语法如图所示&#xf…...

《昇思 25 天学习打卡营第 11 天 | ResNet50 图像分类 》

《昇思 25 天学习打卡营第 11 天 | ResNet50 图像分类 》 活动地址&#xff1a;https://xihe.mindspore.cn/events/mindspore-training-camp 签名&#xff1a;Sam9029 计算机视觉-图像分类&#xff0c;很感兴趣 且今日精神颇佳&#xff0c;一个字&#xff0c;学啊 上一节&…...

实现多数相加,但是传的参不固定

一、情景 一般实现的加法和减法等简单的相加减函数的话。一般都是写好固定传的参数。比如&#xff1a; function add(a,b) {return a b;} 这是固定的传入俩个&#xff0c;如果是三个呢&#xff0c;有人说当然好办&#xff01; 这样写不就行了&#xff01; function add(a…...

Windows环境安装Redis和Redis Desktop Manager图文详解教程

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl Redis概述 Redis是一个开源的高性能键值对数据库&#xff0c;以其卓越的读写速度而著称&#xff0c;广泛用于数据库、缓存和消息代理。它主要将数据存储在内存中&#xff0…...

SQL Server 2022的组成

《SQL Server 2022从入门到精通&#xff08;视频教学超值版&#xff09;》图书介绍-CSDN博客 SQL Server 2022主要由4部分组成&#xff0c;分别是数据库引擎、分析服务、集成服务和报表服务。本节将详细介绍这些内容。 1.2.1 SQL Server 2022的数据库引擎 SQL Server 2022的…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…...

跨链模式:多链互操作架构与性能扩展方案

跨链模式&#xff1a;多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈&#xff1a;模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展&#xff08;H2Cross架构&#xff09;&#xff1a; 适配层&#xf…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

linux 下常用变更-8

1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行&#xff0c;YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID&#xff1a; YW3…...