SpringBoot3整合Mybatis-Plus,自定义动态数据源starter

文章目录

  • 前言
  • 正文
    • 一、项目总览
    • 二、核心代码展示
      • 2.1 自定义AbstractRoutingDataSource
      • 2.2 动态数据源DynamicDataSource
      • 2.3 动态数据源自动配置
      • 2.4 动态数据源上下文DynamicDataSourceContextHolder
      • 2.5 动态数据源修改注解定义
      • 2.6 修改切面DynamicDataSourceAspect
      • 2.7 动态数据源工具类
    • 三、start模块&调试
      • 3.1 Mybatis-Plus配置
      • 3.2 application.yml配置
      • 3.3 启动类
      • 3.4 调试
      • 3.5 数据库sql
    • 四、调试结果
      • 4.1 启动项目
      • 4.2 执行请求

前言

本文旨在SpringBoot3整合Mybatis-Plus,实现动态数据源切换。
不使用Mybatis-Plus本身的依赖。自己动手造轮子。

之前有写过一个SpringBoot切换动态数据源的文章:

  • https://blog.csdn.net/FBB360JAVA/article/details/124610140

本次使用了Java17,SpringBoot3.0.2 ,Mybatis-Spring 3版本。并且自定义starter,提供自定义注解,使用切面实现切换数据源。
本文中对应的代码仓库如下:

  • https://gitee.com/fengsoshuai/dynamic-datasource-mp-starter-demo

其中,代码分支master,是多数据源,提供静态切换方法,注解方式切换。
代码分支dev,是动态多数据源,在master的基础上,提供运行时,新增或修改,或删除数据源。

以上,只考虑单机模式(如果是分布式项目,建议使用中间件,如redis等维护数据源信息;或者创建独立项目专门提供数据源必要信息的接口)

正文

一、项目总览

在这里插入图片描述

本次使用聚合maven项目,内部包含两个模块:

  • dynamic-datasource-mp-starter:自定义starter,实现数据源的基本功能,包含切换数据源等。
  • start:启动&测试模块,整合mybatis-plus ,提供代码配置,以及提供测试接口

二、核心代码展示

注意,本节代码展示,只粘贴了dev分支的代码!

2.1 自定义AbstractRoutingDataSource

如果不需要动态新增或修改数据源,则不需要自定义这个类。直接使用spring-jdbc中的该类即可。

package org.feng.datasource.util;import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {@Nullableprivate Map<Object, Object> targetDataSources;@Nullableprivate Object defaultTargetDataSource;private boolean lenientFallback = true;private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();@Nullableprivate Map<Object, DataSource> resolvedDataSources;@Nullableprivate DataSource resolvedDefaultDataSource;public AbstractRoutingDataSource() {}public void setTargetDataSources(Map<Object, Object> targetDataSources) {this.targetDataSources = targetDataSources;}public void setTargetDataSourcesAndRefresh(String newMerchant, DataSource newDataSource) {if (this.targetDataSources == null) {throw new IllegalArgumentException("Property 'targetDataSources' is required");}this.targetDataSources.put(newMerchant, newDataSource);if (this.resolvedDataSources == null) {this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());}Object value = this.targetDataSources.get(newMerchant);Object lookupKey = this.resolveSpecifiedLookupKey(newMerchant);DataSource dataSource = this.resolveSpecifiedDataSource(value);this.resolvedDataSources.put(lookupKey, dataSource);}public void removeDataSourcesByMerchant(String newMerchant) {if (this.targetDataSources == null) {throw new IllegalArgumentException("Property 'targetDataSources' is required");}this.targetDataSources.remove(newMerchant);this.resolvedDataSources.remove(newMerchant);}public void setDefaultTargetDataSource(Object defaultTargetDataSource) {this.defaultTargetDataSource = defaultTargetDataSource;}public void setLenientFallback(boolean lenientFallback) {this.lenientFallback = lenientFallback;}public void setDataSourceLookup(@Nullable DataSourceLookup dataSourceLookup) {this.dataSourceLookup = dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup();}public void afterPropertiesSet() {if (this.targetDataSources == null) {throw new IllegalArgumentException("Property 'targetDataSources' is required");} else {this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());this.targetDataSources.forEach((key, value) -> {Object lookupKey = this.resolveSpecifiedLookupKey(key);DataSource dataSource = this.resolveSpecifiedDataSource(value);this.resolvedDataSources.put(lookupKey, dataSource);});if (this.defaultTargetDataSource != null) {this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);}}}protected Object resolveSpecifiedLookupKey(Object lookupKey) {return lookupKey;}protected DataSource resolveSpecifiedDataSource(Object dataSourceObject) throws IllegalArgumentException {if (dataSourceObject instanceof DataSource dataSource) {return dataSource;} else if (dataSourceObject instanceof String dataSourceName) {return this.dataSourceLookup.getDataSource(dataSourceName);} else {throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSourceObject);}}public Map<Object, DataSource> getResolvedDataSources() {Assert.state(this.resolvedDataSources != null, "DataSources not resolved yet - call afterPropertiesSet");return Collections.unmodifiableMap(this.resolvedDataSources);}@Nullablepublic DataSource getResolvedDefaultDataSource() {return this.resolvedDefaultDataSource;}public Connection getConnection() throws SQLException {return this.determineTargetDataSource().getConnection();}public Connection getConnection(String username, String password) throws SQLException {return this.determineTargetDataSource().getConnection(username, password);}public <T> T unwrap(Class<T> iface) throws SQLException {return iface.isInstance(this) ? (T) this : this.determineTargetDataSource().unwrap(iface);}public boolean isWrapperFor(Class<?> iface) throws SQLException {return iface.isInstance(this) || this.determineTargetDataSource().isWrapperFor(iface);}protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");Object lookupKey = this.determineCurrentLookupKey();DataSource dataSource = this.resolvedDataSources.get(lookupKey);if (dataSource == null && (this.lenientFallback || lookupKey == null)) {dataSource = this.resolvedDefaultDataSource;}if (dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");} else {return dataSource;}}@Nullableprotected abstract Object determineCurrentLookupKey();
}

2.2 动态数据源DynamicDataSource

package org.feng.datasource;import org.feng.datasource.util.AbstractRoutingDataSource;/*** 动态数据源** @author feng*/
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceKey();}
}

2.3 动态数据源自动配置

package org.feng.datasource.config;import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.feng.datasource.DynamicDataSource;
import org.feng.datasource.constant.DataSourceConstant;
import org.feng.datasource.entity.DataSourceProperties;
import org.feng.datasource.entity.SpringDataSourceProperties;
import org.feng.datasource.util.DataSourceUtil;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** 动态数据源自动配置** @author feng*/
@Data
@Slf4j
@AutoConfiguration
@ConfigurationPropertiesScan({"org.feng.datasource.entity"})
public class DynamicDataSourceAutoConfiguration {@Resourceprivate SpringDataSourceProperties springDataSourceProperties;@Primary@Beanpublic DataSource dynamicDataSource() {Map<Object, Object> dataSourceMap = new HashMap<>(16);Map<String, DataSourceProperties> dataSourcePropertiesMap = springDataSourceProperties.getConfigMap();dataSourcePropertiesMap.forEach((merchant, properties) -> dataSourceMap.put(merchant, DataSourceUtil.dataSource(properties)));// 设置动态数据源DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setTargetDataSources(dataSourceMap);// 设置默认数据源dynamicDataSource.setDefaultTargetDataSource(dataSourceMap.get(DataSourceConstant.MASTER));return dynamicDataSource;}@PostConstructprivate void init() {Map<String, DataSourceProperties> configMap = springDataSourceProperties.getConfigMap();configMap.forEach((k, v) -> {log.info("merchantKey = {}, config = {}", k, v);});}
}

2.4 动态数据源上下文DynamicDataSourceContextHolder

package org.feng.datasource;import lombok.extern.slf4j.Slf4j;
import org.feng.datasource.constant.DataSourceConstant;import java.util.Optional;/*** 动态数据源上下文保持类** @version v1.0* @author: fengjinsong* @date: 2022年05月05日 15时19分*/
@Slf4j
public class DynamicDataSourceContextHolder {/*** 动态数据源的上下文*/private static final ThreadLocal<String> DATASOURCE_CONTEXT_MERCHANT_HOLDER = new InheritableThreadLocal<>();/*** 切换数据源** @param merchant 租户Key*/public static void setDataSourceKey(String merchant) {log.info("切换数据源,merchant 为 {}", merchant);DATASOURCE_CONTEXT_MERCHANT_HOLDER.set(merchant);}/*** 获取当前数据源名称** @return 当前数据源名称*/public static String getDataSourceKey() {return Optional.ofNullable(DATASOURCE_CONTEXT_MERCHANT_HOLDER.get()).orElse(DataSourceConstant.MASTER);}/*** 删除当前数据源名称*/public static void removeDataSourceKey() {DATASOURCE_CONTEXT_MERCHANT_HOLDER.remove();}
}

2.5 动态数据源修改注解定义

package org.feng.datasource.annotation;import org.feng.datasource.constant.DataSourceConstant;import java.lang.annotation.*;/*** 改变数据源注解** @author feng*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ChangeDataSource {String merchant() default DataSourceConstant.MASTER;
}

2.6 修改切面DynamicDataSourceAspect

package org.feng.datasource.aop;import jakarta.annotation.PostConstruct;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.feng.datasource.DynamicDataSourceContextHolder;
import org.feng.datasource.annotation.ChangeDataSource;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** 动态数据源切面** @author feng*/
@Aspect
@Component
@Order(1)
@Slf4j
public class DynamicDataSourceAspect {@SneakyThrows@Before("@annotation(org.feng.datasource.annotation.ChangeDataSource)")public void changeDataSource(JoinPoint joinPoint) {log.info("开始切换数据源...");// 获取方法名,参数String methodName = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();Class<?>[] paramsTypes = new Class[args.length];if(args.length > 0) {for (int i = 0; i < args.length; i++) {paramsTypes[i] = args[i].getClass();}}// 获取注解Class<?> aClass = joinPoint.getTarget().getClass();Method currentMethod = aClass.getDeclaredMethod(methodName, paramsTypes);ChangeDataSource changeDataSource = currentMethod.getDeclaredAnnotation(ChangeDataSource.class);// 获取租户String merchant = changeDataSource.merchant();log.info("当前数据源租户切换为:{}", merchant);// 切换数据源DynamicDataSourceContextHolder.setDataSourceKey(merchant);}@After("@annotation(org.feng.datasource.annotation.ChangeDataSource)")public void changeDataSourceOver() {log.info("释放数据源...");DynamicDataSourceContextHolder.removeDataSourceKey();}@PostConstructprivate void init() {log.info("注册动态数据源切换注解");}
}

2.7 动态数据源工具类

提供动态新增数据源,删除数据源的方法。
因为本身是维护map,所以同时支持修改(根据merchant来新增或修改或删除)

package org.feng.datasource.util;import lombok.extern.slf4j.Slf4j;
import org.feng.datasource.DynamicDataSource;
import org.feng.datasource.entity.DataSourceProperties;
import org.springframework.beans.BeansException;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;import javax.sql.DataSource;/*** 数据源工具** @author feng*/
@Slf4j
@Component
public class DataSourceUtil implements ApplicationContextAware {private static ApplicationContext applicationContext;/*** 设置数据源,用于动态新增,修改数据源** @param newMerchant          租户* @param dataSourceProperties 数据源属性对象*/public static void setNewDynamicDataSource(String newMerchant, DataSourceProperties dataSourceProperties) {log.info("merchantKey = {}, config = {}", newMerchant, dataSourceProperties);DynamicDataSource dataSource = applicationContext.getBean(DynamicDataSource.class);dataSource.setTargetDataSourcesAndRefresh(newMerchant, dataSource(dataSourceProperties));}/*** 移除数据源** @param newMerchant 租户*/public static void removeDynamicDataSource(String newMerchant) {log.info("正在移除数据源 merchantKey = {}", newMerchant);DynamicDataSource dataSource = applicationContext.getBean(DynamicDataSource.class);dataSource.removeDataSourcesByMerchant(newMerchant);}@Overridepublic void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {DataSourceUtil.applicationContext = applicationContext;}/*** 构建数据源** @param dataSourceProperties 数据源属性配置* @return 数据源*/public static DataSource dataSource(DataSourceProperties dataSourceProperties) {return DataSourceBuilder.create().driverClassName(dataSourceProperties.getDriverClassName()).url(dataSourceProperties.getJdbcUrl()).username(dataSourceProperties.getUsername()).password(dataSourceProperties.getPassword()).build();}
}

三、start模块&调试

3.1 Mybatis-Plus配置

package org.feng.start.config;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
import lombok.SneakyThrows;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;import javax.sql.DataSource;/*** mybatis-plus配置** @author feng*/
@Configuration
public class MybatisPlusConfig {@Bean@Primary@SneakyThrowspublic SqlSessionFactory sqlSessionFactory(@Autowired DataSource dataSource) {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();MybatisConfiguration configuration = new MybatisConfiguration();sqlSessionFactoryBean.setConfiguration(configuration);// 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射configuration.setMapUnderscoreToCamelCase(true);// 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段configuration.setCallSettersOnNulls(true);// 日志configuration.setLogImpl(org.apache.ibatis.logging.stdout.StdOutImpl.class);// 实体包sqlSessionFactoryBean.setTypeAliasesPackage("org.feng.start.entity");// mapper.xml位置ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();Resource[] resources = resourceResolver.getResources("classpath*:mapper/**Mapper.xml");sqlSessionFactoryBean.setMapperLocations(resources);// 设置数据源sqlSessionFactoryBean.setDataSource(dataSource);// 设置GlobalConfigGlobalConfigUtils.setGlobalConfig(configuration, globalConfig());// 返回SqlSessionFactoryreturn sqlSessionFactoryBean.getObject();}private GlobalConfig globalConfig() {GlobalConfig globalConfig = new GlobalConfig();GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();dbConfig.setIdType(IdType.AUTO);globalConfig.setDbConfig(dbConfig);return globalConfig;}
}

3.2 application.yml配置

spring:datasource:config-map:master:driver-class-name: "com.mysql.cj.jdbc.Driver"jdbc-url: "jdbc:mysql://192.168.110.68:3306/db_master?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8"username: "root"password: "root123456"slave1:driver-class-name: "com.mysql.cj.jdbc.Driver"jdbc-url: "jdbc:mysql://192.168.110.68:3306/db_slave1?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8"username: "root"password: "root123456"
#      slave2:
#        driver-class-name: "com.mysql.cj.jdbc.Driver"
#        jdbc-url: "jdbc:mysql://192.168.110.68:3306/db_slave2?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8"
#        username: "root"
#        password: "root123456"server:port: 80servlet:context-path: /dynamic_datasource

3.3 启动类

package org.feng.start;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.transaction.annotation.EnableTransactionManagement;@MapperScan(basePackages = {"org.feng.start.mapper"})
@EnableAspectJAutoProxy(exposeProxy = true)
@EnableTransactionManagement
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class, scanBasePackages = {"org.feng.datasource", "org.feng.start"})
public class StartApplication {public static void main(String[] args) {SpringApplication.run(StartApplication.class, args);}}

3.4 调试

定义控制器,提供接口。

package org.feng.start.controller;import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.feng.datasource.DynamicDataSourceContextHolder;
import org.feng.datasource.annotation.ChangeDataSource;
import org.feng.datasource.entity.DataSourceProperties;
import org.feng.datasource.util.DataSourceUtil;
import org.feng.start.entity.Student;
import org.feng.start.service.IStudentService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** 学生控制器-测试切换数据源** @author feng*/
@Slf4j
@RequestMapping("/student")
@RestController
public class StudentController {@Resourceprivate IStudentService studentService;@GetMapping("/list/{merchant}")public List<Student> list(@PathVariable String merchant) {try {// 切换数据源DynamicDataSourceContextHolder.setDataSourceKey(merchant);// 查库List<Student> resultList = studentService.list();log.info("查询结果:{}", resultList);return resultList;} finally {// 清除当前数据源DynamicDataSourceContextHolder.removeDataSourceKey();}}@GetMapping("/listStu/master")public List<Student> listStu() {// 查库List<Student> resultList = studentService.list();log.info("查询结果:{}", resultList);return resultList;}@ChangeDataSource(merchant = "slave1")@GetMapping("/listStu/slave1")public List<Student> listStu1() {// 查库List<Student> resultList = studentService.list();log.info("查询结果:{}", resultList);return resultList;}@ChangeDataSource(merchant = "slave2")@GetMapping("/listStu/slave2")public List<Student> listStu2() {// 查库List<Student> resultList = studentService.list();log.info("查询结果:{}", resultList);return resultList;}@GetMapping("/listStu/newDataSource")public List<Student> newDataSource() {String merchant = "slave2";DataSourceProperties dataSourceProperties = newSlave2();DataSourceUtil.setNewDynamicDataSource(merchant, dataSourceProperties);// 切换数据源DynamicDataSourceContextHolder.setDataSourceKey(merchant);// 查库List<Student> resultList = studentService.list();log.info("查询结果:{}", resultList);DynamicDataSourceContextHolder.removeDataSourceKey();return resultList;}private DataSourceProperties newSlave2() {DataSourceProperties dataSourceProperties = new DataSourceProperties();dataSourceProperties.setDriverClassName("com.mysql.cj.jdbc.Driver");dataSourceProperties.setJdbcUrl("jdbc:mysql://192.168.110.68:3306/db_slave2?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8");dataSourceProperties.setUsername("root");dataSourceProperties.setPassword("root123456");return dataSourceProperties;}
}

3.5 数据库sql

按照自己的需要,创建多个数据库,并创建好自己使用的数据表即可。
本文中测试使用的如下:

CREATE TABLE `tb_student` (`student_name` varchar(100) DEFAULT NULL,`id` bigint unsigned NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

四、调试结果

4.1 启动项目

在这里插入图片描述
启动项目,可以看到注册了两个数据源。

4.2 执行请求

  • Get 请求 :http://localhost:80/dynamic_datasource/student/listStu/master
    响应:
[{"id": 1,"studentName": "master_student2312s"}
]
  • Get 请求 :http://localhost:80/dynamic_datasource/student/listStu/slave1
    响应:
[{"id": 1,"studentName": "slave1_studew12321"}
]
  • Get 请求 :http://localhost:80/dynamic_datasource/student/listStu/slave2
    因为此时还没有slave2,所以默认请求的是master的数据源,查询结果如下:
[{"id": 1,"studentName": "master_student2312s"}
]
  • Get 请求 :http://localhost:80/dynamic_datasource/student/listStu/newDataSource
    新增数据源,代码中写的是新增slave2的数据源
    日志如下:
    在这里插入图片描述

查询结果如下:

[{"id": 1,"studentName": "slave2_213dqwa"}
]
  • Get 请求 :http://localhost:80/dynamic_datasource/student/listStu/slave2
    此时已经新增了slave2数据源,因此能够切换到slave2中,查询结果如下:
[{"id": 1,"studentName": "slave2_213dqwa"}
]

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

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

相关文章

多维时序 | MATLAB实现基于CNN-LSSVM卷积神经网络-最小二乘支持向量机多变量时间序列预测

多维时序 | MATLAB实现基于CNN-LSSVM卷积神经网络-最小二乘支持向量机多变量时间序列预测 目录 多维时序 | MATLAB实现基于CNN-LSSVM卷积神经网络-最小二乘支持向量机多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.MATLAB实现基于CNN-LSSVM卷积神经…

vue+vite项目,动态导入静态资源的几种方式

博主的桌面工具软件已经正式开发&#xff0c;获取方式&#xff1a; 可以关注我的小程序【中二少年工具箱】获取。&#xff08;若小程序更新有延迟&#xff0c;可先收藏小程序&#xff09; 通过下载链接 百度网盘: 链接&#xff1a;https://pan.baidu.com/s/15zDnSoEzJGSZLjpD…

跟着pink老师前端入门教程-day20

二、移动WEB开发之flex布局 1、flex 布局体验 1.1 传统布局与flex布局 传统布局&#xff1a;兼容性好、布局繁琐、局限性、不能再移动端很好的布局 flex弹性布局&#xff1a;操作方便&#xff0c;布局极为简单&#xff0c;移动端应用很广泛&#xff1b;PC 端浏览器支持情况…

SpringbootV2.6整合Knife4j 3.0.3 问题记录

参考 https://juejin.cn/post/7249173717749940284 近期由于升级到springboot2.6X&#xff0c;所以服务端很多组件都需要重新导入以及解决依赖问题。 下面就是一个很经典的问题了&#xff0c; springboot2.6与knife4j的整合。 版本对应 springboot2.6与knife4j 3.0.3 坑 …

c++二叉树寒假特训题目(1)

大家好&#xff0c;我是周曦&#xff0c;今天给大家推荐一些二叉树题目。 题目 二叉树存储 这道题是道水题&#xff0c;找找规律ok&#xff0c;本人代码10行。 淘汰赛 这道题推荐使用桶数组 做比较合适&#xff08;就是有点绕&#xff09;。 二叉树深度 这题是一道深搜题&a…

eclipse使用google的Java代码格式

插件下载地址 1.下载eclipse的插件 2.下载的jar包放到eclipse安装目录的dropins文件夹 D:\install_package\STS\sts-4.10.0.RELEASE\dropins&#xff13;.重启后设置 eclipse - windows - preference - java - code style - formatter -

MySQL篇----第十二篇

系列文章目录 文章目录 系列文章目录前言一、可以使用多少列创建索引?二、NOW()和 CURRENT_DATE()有什么区别?三、什么是非标准字符串类型?四、什么是通用 SQL 函数?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转…

【蓝桥杯冲冲冲】Invasion of the Milkweed G

【蓝桥杯冲冲冲】Invasion of the Milkweed G 蓝桥杯备赛 | 洛谷做题打卡day30 文章目录 蓝桥杯备赛 | 洛谷做题打卡day30[USACO09OCT] Invasion of the Milkweed G题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 题解代码我的一些话 [USACO09OCT] Invasion of the Mi…

linux centos 安装teleport

效果 安装 1.创建目录 mkdir -p /opt/teleport/data cd /opt/teleport/2.下载解压文件 wget https://tp4a.com/static/download/teleport-server-linux-x64-3.6.4-b3.tar.gz tar -xvf teleport-server-linux-x64-3.6.4-b3.tar.gz3.安装 cd /opt/teleport/teleport-server-l…

Python学习路线 - Python高阶技巧 - 拓展

Python学习路线 - Python高阶技巧 - 拓展 闭包闭包注意事项 装饰器装饰器的一般写法(闭包写法)装饰器的语法糖写法 设计模式单例模式工厂模式 多线程进程、线程并行执行多线程编程threading模块 网络编程Socket客户端和服务端Socket服务端编程实现服务端并结合客户端进行测试 S…

D音等短视频为什么这么吸引人?长期沉迷刷D音的危害 彻底戒掉刷D音上瘾 占用大量时间 注意力分散 思维浅薄 焦虑、抑郁 干扰睡眠 视力疲劳

这是你吗&#xff1f; 人生最爽的事是&#xff1a;刷痘印。 人生最不爽的事是&#xff1a;刷完&#xff0c;什么也没有得到&#xff0c;事也没做。 吸引法则 1. 内容碎片化&#xff0c;符合快节奏时代需求 短视频的时长通常只有几秒到十几分钟&#xff0c;内容简短精悍&…

苹果macbook电脑删除数据恢复该怎么做?Mac电脑误删文件的恢复方法

苹果电脑删除数据恢复该怎么做&#xff1f;Mac电脑误删文件的恢复方法 如何在Mac上恢复误删除的文件&#xff1f;在日常使用Mac电脑时&#xff0c;无论是工作还是娱乐&#xff0c;我们都会创建和处理大量的文件。然而&#xff0c;有时候可能会不小心删除一些重要的文件&#x…

Spring Boot3,启动时间缩短 10 倍!

前面松哥写了一篇文章和大家聊了 Spring6 中引入的新玩意 AOT&#xff08;见Spring Boot3 新玩法&#xff0c;AOT 优化&#xff01;&#xff09;。 文章发出来之后&#xff0c;有小伙伴问松哥有没有做性能比较&#xff0c;老实说&#xff0c;这个给落下了&#xff0c;所以今天…

Linux的计划任务(crontab)环境变量问题解决

1、背景 新上了个python服务&#xff0c;里面有调用oracle&#xff0c;其中有个需求需要定时去调用&#xff0c;贪方便想用crontab&#xff0c;出现了环境变量问题&#xff0c;于是跟他杠上了&#xff0c;势必要解决它&#xff01; 2、现象 尽管我在计划任务里写全了脚本路径…

使用 KITTI数据集训练YOLOX

1. 现在KITTI集后&#xff0c;首先将数据集转换为COCO数据集格式。 kitti_vis.py import os from pathlib import Path import numpy as np import cv2def anno_vis(img, anno_list):for anno in anno_list:points np.array(anno[4:8], dtypenp.float32)cv2.rectangle(img, (…

疯狂的2023年已过,聊聊我对大模型微调技术几点实践思考

大家好&#xff0c;今天聊聊我对大模型微调技术几点实践看法&#xff0c;喜欢记得收藏、关注、点赞。 更多技术交流&#xff0c;资料&#xff0c;文末加入我们技术群获取。 为什么要对大模型进行微调&#xff08;Fine-tuning&#xff09; 与其说对 LLM 大模型进行微调&#xf…

周期股是什么意思?包括哪些行业?

周期股是什么意思&#xff1f; 所谓的经济周期&#xff0c;又称商业周期或商业循环&#xff0c;是指经济运行中周期性出现的经济扩张与经济紧缩交替更迭&#xff0c;循环往复的一种现象。经济周期是由低迷、复苏、高潮、和衰落构成的&#xff0c;周而复始就形成了所谓的经济景…

HDFS架构 之 服务视图

1 、简介 为实现以上特性,HDFS包含的各个服务模块都是经过精心设计的,HDFS的服务视图如图。 HDFS的服务视图包含三大部分:核心服务、公共服务和拓展服务。 2、 核心服务 1)Namenode。HDFS系统采用中心化设计,即Master/Slave架构。这里的Namenode即是Master,主要作用是管…

影响2024年Web3赛道的三大事件

文章来源Techub News&#xff0c;搜Tehub News下载查看更多Web3资讯。 比特币ETF 2023年12月29日&#xff0c;路透社发布重要消息称&#xff0c;美国证券交易委员会&#xff08;SEC&#xff09;可能会在美国时间的1月2日或1月3日通知申请者他们是否获得了推出现货比特币ETF&a…

momentJs推导日历组件

实现效果: 代码&#xff1a; 引入momentjs然后封装两个函数构建出基本数据结构 import moment from moment;// 某月有多少天 export const getEndDay (m) > m.daysInMonth();/*** description 获取本月空值数据* param { Date } year { } 年度* param { Number } month …