当前位置: 首页 > 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了!

总结

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • WHAT - React useEffect 依赖的 Object.is
  • 【C语言】指针(1):入门理解篇
  • Docker 部署 OpenVPN 与 OpenVPN 基本用法
  • CTFShow的RE题(二)
  • Gradient Descent
  • windows下编译ffmpeg 最详细教程
  • 加装德国进口高精度主轴 智能手机壳「高质量高效率」钻孔铣槽
  • Unity之创建与导出PDF
  • 第一次面试的经历(java开发实习生)
  • IPSS模块怎么安装到VOS服务器的,到底有没有效果,是不是能大幅度提升VOS3000安全性呢
  • pdfplumber vs PyMuPDF:PDF文本、图像和表格识别的比较
  • Python面试题:在 Python 中,如何处理异常?
  • 开发个人Go-ChatGPT--1 项目介绍
  • 简单分享下python多态
  • idea Git操作
  • 【Leetcode】104. 二叉树的最大深度
  • 2017年终总结、随想
  • android 一些 utils
  • E-HPC支持多队列管理和自动伸缩
  • JavaScript创建对象的四种方式
  • Java反射-动态类加载和重新加载
  • k8s如何管理Pod
  • Linux快速复制或删除大量小文件
  • React的组件模式
  • 笨办法学C 练习34:动态数组
  • 持续集成与持续部署宝典Part 2:创建持续集成流水线
  • 和 || 运算
  • 基于web的全景—— Pannellum小试
  • 如何利用MongoDB打造TOP榜小程序
  • 使用SAX解析XML
  • 一、python与pycharm的安装
  • 译米田引理
  • 正则表达式小结
  • ​Spring Boot 分片上传文件
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (附源码)计算机毕业设计SSM保险客户管理系统
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (十一)手动添加用户和文件的特殊权限
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (一)基于IDEA的JAVA基础12
  • (转)GCC在C语言中内嵌汇编 asm __volatile__
  • ***linux下安装xampp,XAMPP目录结构(阿里云安装xampp)
  • .MyFile@waifu.club.wis.mkp勒索病毒数据怎么处理|数据解密恢复
  • .NET 4.0中使用内存映射文件实现进程通讯
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .NET 将混合了多个不同平台(Windows Mac Linux)的文件 目录的路径格式化成同一个平台下的路径
  • .NET程序员迈向卓越的必由之路
  • .NET实现之(自动更新)
  • .net中应用SQL缓存(实例使用)
  • @JoinTable会自动删除关联表的数据
  • @Not - Empty-Null-Blank
  • @property python知乎_Python3基础之:property
  • [023-2].第2节:SpringBoot中接收参数相关注解