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官方文档,欢迎访问官网,查看更多深度文章。 本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。 Apache Seata透过源码解决SeataAT模式整合Mybatis-Plus失去MP特性的问题 透过源码解决SeataAT…...
1.2 如何让机器说人话?万字长文回顾自然语言处理(NLP)的前世今生 —— 《带你自学大语言模型》系列
本系列目录 《带你自学大语言模型》系列部分目录及计划,完整版目录见:带你自学大语言模型系列 —— 前言 第一部分 走进大语言模型(科普向) 第一章 走进大语言模型 1.1 从图灵机到GPT,人工智能经历了什么࿱…...
【QT】按钮类控件
按钮类控件 按钮类控件1. PushButton2. Radio Button3. Check Box4. Tool Button 按钮类控件 1. PushButton 使⽤ QPushButton 表示⼀个按钮,这也是当前我们最熟悉的⼀个控件了. QPushButton 继承⾃ QAbstractButton . 这个类是⼀个抽象类. 是其他按钮的⽗类. 在…...
RedHat运维-Linux软件包管理基础-RHEL9软件包管理基础
Linux软件包管理基础-RHEL9 1. 对于RHEL9来说,软件包管理基础分为增、删、改、查四个部分。对于增来说,有:增加一个仓库的信息文件、启用一个仓库的信息文件、安装rpm包、解压rpm包、安装软件、安装软件组、更新软件。在这里先讲软件包管理中…...
uniapp----- 判断小程序版本有没有更新
const updateManager uni.getUpdateManager();// 当向小程序后台请求完新版本信息,会进行回调updateManager.onCheckForUpdate(function (res) {console.log(是否有新版本, res.hasUpdate);});// 当新版本下载完成,会进行回调updateManager.onUpdateRea…...
Spring Boot的无缝衔接:深入解析与实践
欢迎来到 破晓的历程的 博客 ⛺️不负时光,不负己✈️ 🚀The begin🚗点点关注,收藏不迷路🚩 引言 在快速迭代的软件开发环境中,无缝衔接是提升开发效率、降低维护成本、增强系统稳定性的关键。Spring Boo…...
在Linux上查找文件的2个好用的命令
在Linux上查找文件,两个非常好用的命令是find和locate。 find命令 find命令非常强大,可以在指定目录下查找符合条件的文件。你可以根据文件名、文件类型、大小、修改日期等多种条件来查找文件。例如,要在当前目录及其子目录下查找所有扩展名…...
实现WebSocket聊天室功能
实现WebSocket聊天室功能 什么是WebSocket?WebSocket的工作原理服务器端实现客户端实现 在现代Web开发中,实时通信已经变得越来越重要。传统的HTTP协议由于其无状态和单向通信的特点,无法很好地满足实时通信的需求。而WebSocket协议则应运而生…...
qt opencv 应用举例
在Qt中使用OpenCV可以实现各种图像处理和计算机视觉任务。以下是一些Qt与OpenCV联合应用的具体举例: 1. 图像读取与显示 读取图像:使用OpenCV的imread函数可以方便地读取各种格式的图像文件,如.bmp、.jpg、.png等。这个函数返回一个Mat对象…...
QT5.12环境搭建与源码编译
一、概述 QT版本:QT5.12.10 Qt网址:http://download.qt.io/archive/qt/ 编译平台 ubuntu18.04 二、安装交叉编译工具链 1、获取交叉编译工具链 一般如果是编译系统如果有对应的gcc 就是用这个就可以了 比如rk3128 lin…...
Android中android.fg线程和android.ui线程分别代表什么?
Android中android.fg线程和android.ui线程分别代表什么? android.fg线程(FgThread): FgThread是Android系统中一个特殊的线程,其类定义大致为public final class FgThread extends ServiceThread。它主要用于提供一个…...
MATLAB 2024b 更新了些什么?
MATLAB 2024b版本已经推出了预览版,本期介绍一些MATLAB部分的主要的更新内容。 帮助浏览器被移除 在此前的版本,当我们从MATLAB中访问帮助文档时,默认会通过MATLAB的帮助浏览器(Help browser)。 2024b版本开始&…...
SSM高校教师教学质量评估系统-计算机毕业设计源码03344
摘要 在高等教育中,教学质量是培养优秀人才的关键。为了提高教学质量,高校需要建立一套科学、有效的教师教学质量评估系统。本研究采用 SSM技术框架,旨在开发一款高校教师教学质量评估系统。 SSM框架作为一种成熟的Java开发框架,具…...
【Linux进阶】文件系统5——ext2文件系统(inode)
1.再谈inode (1) 理解inode,要从文件储存说起。 文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节(相当于0.5KB)。操作系统读取硬盘的时候,不会一个个…...
华为云简介
前言 华为云是华为的云服务品牌,将华为30多年在ICT领域的技术积累和产品解决方案开放给客户,致力于提供稳定可靠、安全可信、可持续创新的云服务,赋能应用、使能数据、做智能世界的“黑土地”,推进实现“用得起、用得好、用得放心…...
Doris数据库---建表、调整表结构操作
一、简介 本文章主讲创建 Doris 自维护的表的语法,以下为本人最近为数据中台接入doris所踩的坑及其解决方案,欢迎点评。 二、doris建表语法: 官网建表语法网址链接:CREATE-TABLE - Apache Doris 官网建表语法如图所示…...
《昇思 25 天学习打卡营第 11 天 | ResNet50 图像分类 》
《昇思 25 天学习打卡营第 11 天 | ResNet50 图像分类 》 活动地址:https://xihe.mindspore.cn/events/mindspore-training-camp 签名:Sam9029 计算机视觉-图像分类,很感兴趣 且今日精神颇佳,一个字,学啊 上一节&…...
实现多数相加,但是传的参不固定
一、情景 一般实现的加法和减法等简单的相加减函数的话。一般都是写好固定传的参数。比如: function add(a,b) {return a b;} 这是固定的传入俩个,如果是三个呢,有人说当然好办! 这样写不就行了! function add(a…...
Windows环境安装Redis和Redis Desktop Manager图文详解教程
版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl Redis概述 Redis是一个开源的高性能键值对数据库,以其卓越的读写速度而著称,广泛用于数据库、缓存和消息代理。它主要将数据存储在内存中࿰…...
SQL Server 2022的组成
《SQL Server 2022从入门到精通(视频教学超值版)》图书介绍-CSDN博客 SQL Server 2022主要由4部分组成,分别是数据库引擎、分析服务、集成服务和报表服务。本节将详细介绍这些内容。 1.2.1 SQL Server 2022的数据库引擎 SQL Server 2022的…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...
论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...
基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...
