SpringBoot从数据库读取数据数据源配置信息,动态切换数据源

准备多个数据库

        首先准备多个数据库,主库smiling-datasource,其它库test1、test2、test3

        接下来,我们在主库smiling-datasource中,创建表databasesource,用于存储多数据源相关信息。表结构设计如下

        创建好表之后,向表databasesource中存储test1、test2、test3三个数据库的相关配置信息

        在主库的表databasesource中存储好数据源信息后,接下来分别在test1、test2、test3以及主库smiling-datasource中都创建sys_user数据表,用于后续测试数据源切换是否正常。sys_user的表结构如下所示

        创建好sys_user之后,分别向sys_user表中添加一条数据,添加的数据中分表用不同的内容表示属于哪个库,方便后续测试。

依赖

        该实现主要用到JDK1.8、spring-boot-starter-parent的版本为2.2.0.RELEASE,除此之外需要用到mysql、druid以及mybatis-plus的依赖

配置

        在配置文件application.yml中主要配置主库的数据源信息,配置如下

spring:aop:proxy-target-class: true   #true为使用CGLIB代理datasource:url: jdbc:mysql://localhost:3306/smiling-datasource?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNullusername: rootpassword: zhangpei@123driver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourcedruid:initial-size: 5 #初始化大小min-idle: 5 #最小连接数max-active: 20  #最大连接数time-between-eviction-runs-millis: 60000  #配置间隔多久进行一次检测,检测需要关闭的空闲连接,单位毫秒min-evictable-idle-time-millis: 300000  #配置一个连接在池中最小生存的时间,单位毫秒validation-query: SELECT 1 FROM DUALtest-while-idle: truetest-on-borrow: falsetest-on-return: falsepool-prepared-statements: truemax-pool-prepared-statement-per-connection-size: 20filters: stat,wall,log4j  #配置监控统计拦截的filters,去掉后监控界面sql无法统计,wall用于防火墙connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 #通过该属性打开mergeSQl功能:慢sql记录功能use-global-data-source-stat: true #合并多个DruidDataSource的监控数据max-wait: 60000 #配置获取连接等待超时时间
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmapper-locations: classpath*:/mapping/*.xml

基类DataSource

        创建类DataSource,用于数据源信息的装配

@Data
@ToString
public class DataSource {String datasourceId;String url;String username;String password;String code;String databasetype;String driverclass;String key;}

DruidDBConfig配置类

        该配置类主要用于配置默认的主数据源信息,配置Druid数据库连接池,配置sql工厂加载mybatis的文件,扫描实体类等

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;/*** 配置默认数据源* 配置Druid数据库连接池* 配置sql工厂加载mybatis的文件,扫描实体等** @author smiling* @date 2023/10/7 19:04*/
@Slf4j
@Configuration
@EnableTransactionManagement
public class DruilDBConfig {@Value("${spring.datasource.url}")private String dbUrl;@Value("${spring.datasource.username}")private String username;@Value("${spring.datasource.password}")private String password;@Value("${spring.datasource.driver-class-name}")private String driverClassName;// 连接池连接信息@Value("${spring.datasource.druid.initial-size}")private int initialSize;@Value("${spring.datasource.druid.min-idle}")private int minIdle;@Value("${spring.datasource.druid.max-active}")private int maxActive;@Value("${spring.datasource.druid.max-wait}")private int maxWait;@Bean@Primary@Qualifier("mainDataSource")public DataSource dataSource() throws SQLException {DruidDataSource dataSource = new DruidDataSource();// 基础连接信息dataSource.setUrl(this.dbUrl);dataSource.setUsername(username);dataSource.setPassword(password);dataSource.setDriverClassName(driverClassName);// 连接池连接信息dataSource.setInitialSize(initialSize);dataSource.setMinIdle(minIdle);dataSource.setMaxActive(maxActive);dataSource.setMaxWait(maxWait);dataSource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);//  datasource.setConnectionProperties("oracle.net.CONNECT_TIMEOUT=6000;oracle.jdbc.ReadTimeout=60000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒dataSource.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒dataSource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用dataSource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。String validationQuery = "select 1 from dual";dataSource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。dataSource.setFilters("stat,wall");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:walldataSource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒dataSource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000dataSource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断dataSource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。dataSource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时dataSource.setLogAbandoned(true); //移除泄露连接发生是是否记录日志return dataSource;}/*** 注册一个statViewServlet druid监控页面配置* 账号密码配置** @return*/@Beanpublic ServletRegistrationBean druidStatViewServlet() {ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");servletRegistrationBean.addInitParameter("loginUsername", "admin");servletRegistrationBean.addInitParameter("loginPassword", "123456");servletRegistrationBean.addInitParameter("resetEnable", "false");return servletRegistrationBean;}/*** 注册一个:filterRegistrationBean   druid监控页面配置2-允许页面正常浏览** @return filter registration bean*/@Beanpublic FilterRegistrationBean druidStatFilter() {FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());// 添加过滤规则.filterRegistrationBean.addUrlPatterns("/*");// 添加不需要忽略的格式信息.filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");return filterRegistrationBean;}@Bean(name = "dynamicDataSource")@Qualifier("dynamicDataSource")public DynamicDataSource dynamicDataSource() throws SQLException {DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setDebug(false);//配置缺省的数据源// 默认数据源配置 DefaultTargetDataSourcedynamicDataSource.setDefaultTargetDataSource(dataSource());Map<Object, Object> targetDataSources = new HashMap<Object, Object>();//额外数据源配置 TargetDataSourcestargetDataSources.put("mainDataSource", dataSource());dynamicDataSource.setTargetDataSources(targetDataSources);return dynamicDataSource;}@Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dynamicDataSource());//解决手动创建数据源后字段到bean属性名驼峰命名转换失效的问题sqlSessionFactoryBean.setConfiguration(configuration());// 设置mybatis的主配置文件ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();// Resource mybatisConfigXml = resolver.getResource("classpath:mybatis/mybatis-config.xml");//  sqlSessionFactoryBean.setConfigLocation(mybatisConfigXml);// 设置别名包//  sqlSessionFactoryBean.setTypeAliasesPackage("com.testdb.dbsource.pojo");//手动配置mybatis的mapper.xml资源路径,如果单纯使用注解方式,不需要配置该行sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:/mapping/*.xml"));return sqlSessionFactoryBean.getObject();}/*** 读取驼峰命名设置** @return*/@Bean@ConfigurationProperties(prefix = "mybatis-plus.configuration")public org.apache.ibatis.session.Configuration configuration() {return new org.apache.ibatis.session.Configuration();}}

DBContextHolder类

        该类主要用于手动切换数据源

import lombok.extern.slf4j.Slf4j;/*** @author smiling* @date 2023/10/7 19:16*/
@Slf4j
public class DBContextHolder {//对当前线程的操作-线程安全的private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();//调用此方法,切换数据源public static void setDataSource(String dataSource){contextHolder.set(dataSource);log.info("已切换到数据源:{}",dataSource);}//获取数据源public static String getDataSource(){return contextHolder.get();}//删除数据源public static void clearDataSource(){contextHolder.remove();log.info("已切换到主数据源");}}

DynamicDataSource类

        该类实现手动加载默认数据源、创建数据源连接、检查数据源连接、删除数据源连接等

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.stat.DruidDataSourceStatManager;
import com.smiling.datasource.model.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.ObjectUtils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Map;
import java.util.Set;/*** 手动加载默认数据源* 创建数据源连接* 检查数据源连接* 删除数据源连接等** @author smiling* @date 2023/10/8 11:03*/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {private boolean debug = true;private Map<Object, Object> dynamicTargetDataSources;private Object dynamicDefaultTargetDataSource;@Overrideprotected Object determineCurrentLookupKey() {String datasource = DBContextHolder.getDataSource();if (!ObjectUtils.isEmpty(datasource)) {Map<Object, Object> targetDataSource = this.dynamicTargetDataSources;if (targetDataSource.containsKey(datasource)) {log.info("当前数据源:" + datasource);} else {log.info("不存在的数据源");return null;}} else {log.info("当前数据源:默认数据源");}return datasource;}@Overridepublic void setTargetDataSources(Map<Object, Object> targetDataSources) {super.setTargetDataSources(targetDataSources);this.dynamicTargetDataSources = targetDataSources;}//创建数据源public boolean createDataSource(String key, String driveClass, String url, String username, String password, String databasetype) {try {try {Class.forName(driveClass);DriverManager.getConnection(url, username, password);} catch (Exception e) {return false;}@SuppressWarnings("resource")DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setName(key);druidDataSource.setDriverClassName(driveClass);druidDataSource.setUrl(url);druidDataSource.setUsername(username);druidDataSource.setPassword(password);druidDataSource.setInitialSize(1); //初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时druidDataSource.setMaxActive(20); //最大连接池数量druidDataSource.setMaxWait(60000); //获取连接时最大等待时间,单位毫秒。当链接数已经达到了最大链接数的时候,应用如果还要获取链接就会出现等待的现象,等待链接释放并回到链接池,如果等待的时间过长就应该踢掉这个等待,不然应用很可能出现雪崩现象druidDataSource.setMinIdle(5); //最小连接池数量String validationQuery = "select 1 from dual";druidDataSource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用druidDataSource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。druidDataSource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。druidDataSource.setFilters("stat");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:walldruidDataSource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒druidDataSource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000druidDataSource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断druidDataSource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。druidDataSource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时druidDataSource.setLogAbandoned(true); //移除泄露连接发生是是否记录日志druidDataSource.init();this.dynamicTargetDataSources.put(key, druidDataSource);setTargetDataSources(this.dynamicTargetDataSources);// 将map赋值给父类的TargetDataSourcessuper.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理log.info(key + "数据源初始化成功");return true;} catch (Exception e) {log.error(e + "");return false;}}//删除数据源public boolean delDatasources(String datasourceid) {Map<Object, Object> targetDataSource = this.dynamicTargetDataSources;if (targetDataSource.containsKey(datasourceid)) {Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();for (DruidDataSource source : druidDataSourceInstances) {if (datasourceid.equals(source.getName())) {targetDataSource.remove(datasourceid);DruidDataSourceStatManager.removeDataSource(source);setTargetDataSources(targetDataSource);// 将map赋值给父类的TargetDataSourcessuper.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理return true;}}return false;} else {return false;}}// 测试数据源连接是否有效public boolean testDatasource(String key, String driveClass, String url, String username, String password) {try {Class.forName(driveClass);DriverManager.getConnection(url, username, password);return true;} catch (Exception e) {return false;}}@Overridepublic void setDefaultTargetDataSource(Object defaultTargetDataSource) {super.setDefaultTargetDataSource(defaultTargetDataSource);this.dynamicDefaultTargetDataSource = defaultTargetDataSource;}public void setDebug(boolean debug) {this.debug = debug;}public boolean isDebug() {return debug;}public Map<Object, Object> getDynamicTargetDataSources() {return dynamicTargetDataSources;}public void setDynamicTargetDataSources(Map<Object, Object> dynamicTargetDataSources) {this.dynamicTargetDataSources = dynamicTargetDataSources;}public Object getDynamicDefaultTargetDataSource() {return dynamicDefaultTargetDataSource;}public void setDynamicDefaultTargetDataSource(Object dynamicDefaultTargetDataSource) {this.dynamicDefaultTargetDataSource = dynamicDefaultTargetDataSource;}public void createDataSourceWithCheck(DataSource dataSource) throws Exception {String datasourceId = dataSource.getDatasourceId();log.info("正在检查数据源:" + datasourceId);Map<Object, Object> targetDataSources = this.dynamicTargetDataSources;if (targetDataSources.containsKey(datasourceId)) {log.info("数据源" + datasourceId + "之前已经创建,准备测试数据源是否正常...");//DataSource druidDataSource = (DataSource) dynamicTargetDataSources2.get(datasourceId);DruidDataSource druidDataSource = (DruidDataSource) targetDataSources.get(datasourceId);boolean rightFlag = true;Connection connection = null;try {log.info(datasourceId + "数据源的概况->当前闲置连接数:" + druidDataSource.getPoolingCount());long activeCount = druidDataSource.getActiveCount();log.info(datasourceId + "数据源的概况->当前活动连接数:" + activeCount);if (activeCount > 0) {log.info(datasourceId + "数据源的概况->活跃连接堆栈信息:" + druidDataSource.getActiveConnectionStackTrace());}log.info("准备获取数据库连接...");connection = druidDataSource.getConnection();log.info("数据源" + datasourceId + "正常");} catch (Exception e) {log.error(e.getMessage(), e); //把异常信息打印到日志文件rightFlag = false;log.info("缓存数据源" + datasourceId + "已失效,准备删除...");if (delDatasources(datasourceId)) {log.info("缓存数据源删除成功");} else {log.info("缓存数据源删除失败");}} finally {if (null != connection) {connection.close();}}if (rightFlag) {log.info("不需要重新创建数据源");return;} else {log.info("准备重新创建数据源...");createDataSource(dataSource);log.info("重新创建数据源完成");}} else {createDataSource(dataSource);}}private void createDataSource(DataSource dataSource) throws Exception {String datasourceId = dataSource.getDatasourceId();log.info("准备创建数据源" + datasourceId);String databasetype = dataSource.getDatabasetype();String username = dataSource.getUsername();String password = dataSource.getPassword();String url = dataSource.getUrl();String driveClass = "com.mysql.cj.jdbc.Driver";if (testDatasource(datasourceId, driveClass, url, username, password)) {boolean result = this.createDataSource(datasourceId, driveClass, url, username, password, databasetype);if (!result) {log.error("数据源" + datasourceId + "配置正确,但是创建失败");}} else {log.error("数据源配置有错误");}}}

切换数据源使用的方法

        首先获取主数据源的表databasesource里面的所有西悉尼,然后根据我们传入对应的数据源id进行数据源的切换

DataSourceMapper

@Mapper
public interface DataSourceMapper {@Select("select * from databasesource")List<DataSource> get();    }

DBChangeService

public interface DBChangeService {List<DataSource> get();boolean changeDb(String datasourceId) throws Exception;}

DBChangeServiceImpl

@Service
public class DBChangeServiceImpl implements DBChangeService {private DataSourceMapper dataSourceMapper;private DynamicDataSource dynamicDataSource;public DBChangeServiceImpl(DataSourceMapper dataSourceMapper,DynamicDataSource dynamicDataSource){this.dataSourceMapper = dataSourceMapper;this.dynamicDataSource = dynamicDataSource;}    @Overridepublic List<DataSource> get() { return dataSourceMapper.get(); }@Overridepublic boolean changeDb(String datasourceId) throws Exception {//默认切换到主数据源,进行整体资源的查找DBContextHolder.clearDataSource();List<DataSource> dataSourceList = dataSourceMapper.get();for(DataSource source : dataSourceList){if(source.getDatasourceId().equals(datasourceId)){System.out.println("需要使用的数据源已经找到,datasourceId是:"+source.getDatasourceId());//创建数据源连接&检查,若存在则不需要重新创建dynamicDataSource.createDataSourceWithCheck(source);//切换到该数据源DBContextHolder.setDataSource(source.getDatasourceId());return true;}}return false;}}

实体类User

UserMapper

UserMapper.xml

DemoController

        通过请求接口,通过控制台日志打印可以看到数据源的切换信息

多数据源事务管理

        实现动态数据源事务,在类DruidDBConfig配置类中将我们实现的动态数据源加载类DynamicDataSource添加到数据事务管理器中

        上面配置还是单数据源事务生效,如果是多个数据源就不会生效。该句话的意思是,一个方法中,如果涉及到一个数据源,则事务生效,如果涉及到多个数据源,则事务不会在多个数据源中生效。

        如果想要多个数据源事务一起生效,解决方案如下(注意是针对mybatis-plus框架)

        找到DruidDBConfig配置类,将其中的SqlSessionFactoryBean调整为MybatisSqlSessionFactoryBean,如下

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

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

相关文章

平面光波导_三层均匀平面光波导_射线分析法

平面光波导_三层均匀平面光波导_射线分析法 三层均匀平面光波导&#xff1a; 折射率沿 x x x 方向有变化&#xff0c;沿 y y y、 z z z 方向没有变化三层&#xff1a;芯区( n 1 n_1 n1​) > > > 衬底( n 2 n_2 n2​) ≥ \geq ≥ 包层( n 3 n_3 n3​)包层通常为空…

Docker容器进入的4种方式(推荐最后一种)

在使用Docker创建了容器之后&#xff0c;大家比较关心的就是如何进入该容器了&#xff0c;其实进入Docker容器有好几多种方式&#xff0c;这里我们就讲一下常用的几种进入Docker容器的方法。 进入Docker容器比较常见的几种做法如下&#xff1a; 使用docker attach使用SSH使用…

【已解决】C语言实现多线程检索数据

本博文源于笔者正在学习的c语言。学习如何用多线程进行检索数据。这里以检索一个数组的数据为例&#xff0c;给出代码&#xff0c;并分析如何进行线程通信&#xff0c;如果检索到&#xff0c;其余就别检索了。 文章目录 问题来源问题解决方案代码实现效果总结 问题来源 想要用…

这可能是最全面的Java集合面试八股文了

内容摘自我的学习网站&#xff1a;topjavaer.cn 常见的集合有哪些&#xff1f; Java集合类主要由两个接口Collection和Map派生出来的&#xff0c;Collection有三个子接口&#xff1a;List、Set、Queue。 Java集合框架图如下&#xff1a; List代表了有序可重复集合&#xff0c…

【VSCode,Python】一劳永逸的成功导入本地模组,PYTHONPATH的修改,与各种踩坑点

【VSCode&#xff0c;Python】一劳永逸的成功导入本地模组&#xff0c;PYTHONPATH的修改 起因查阅发现做法一 &#xff08;推荐做&#xff09;做法二 &#xff08;推荐必做&#xff09;踩坑点&#xff08;必看&#xff09; 起因 文件目录如下&#xff1a; my_project/ |-- pa…

【26 预处理详解】

目录 预定义符号#define定义常量#define定义宏带有副作用的宏参数宏替换的规则宏函数的对比#和##命名约定#undef命令行定义条件编译头文件的包含其他预处理指令 1. 预定义符号 c语言设置了一些预定义符号&#xff0c;可以直接使用&#xff0c;预定义符号也是在预处理期间处理…

LeetCode-1672/1572/54/73

1.最富有客户的资产总量&#xff08;1672&#xff09; 题目描述&#xff1a; 给你一个 m x n 的整数网格 accounts &#xff0c;其中 accounts[i][j] 是第 i​​​​​​​​​​​​ 位客户在第 j 家银行托管的资产数量。返回最富有客户所拥有的 资产总量 。 客户的 资产总…

Python自动化测试框架【生成测试报告】

如何才能让用例自动运行完之后&#xff0c;生成一张直观可看易懂的测试报告呢&#xff1f; 小编使用的是unittest的一个扩展HTMLTestRunner 环境准备 使用之前&#xff0c;我们需要下载HTMLTestRunner.py文件 点击HTMLTestRunner后进入的是一个写满代码的网页&#xff0c;小…

如何正确使用数据库读写分离

背景 在应用系统发展的初期&#xff0c;我们并不知道以后会发展成什么样的规模&#xff0c;所以一开始不会考虑复杂的系统架构&#xff0c;复杂的系统架构费时费力&#xff0c;开发周期长&#xff0c;与系统发展初期这样的一个定位是不吻合的。所以&#xff0c;我们都会采用简…

三菱FX系列PLC定长切割控制(线缆裁切)

三菱PLC绝对定位指令DDRVA实现往复运动控制详细介绍请查看下面文章链接&#xff1a; https://rxxw-control.blog.csdn.net/article/details/135570157https://rxxw-control.blog.csdn.net/article/details/135570157这篇博客我们介绍线缆行业的定长切割控制相关算法。 1、输送…

GSTAE

缺失数据的流量预测:一种多任务学习方法 摘要:基于真实交通数据的交通速度预测是智能交通系统(ITS)中的一个经典问题。大多数现有的交通速度预测模型都是基于交通数据完整或具有罕见缺失值的假设而提出的。然而,由于各种人为和自然因素,在现实场景中收集的此类数据往往是…

MySQl Mybatis

一、MySQL 1.1 概述 1.1.1 MySQL安装 1.1.2 数据模型 1.1.3 SQL简介 1.2 DDL 1.2.1 数据库操作 1.2.2 图形化工具 1.2.3 表结构操作 &#xff08;一&#xff09;创建 &#xff08;二&#xff09;数据类型 &#xff08;1&#xff09;数值类型 age tinyint unsigned——加上…

Page 251~254 Win32 GUI项目

win32_gui 源代码&#xff1a; #if defined(UNICODE) && !defined(_UNICODE)#define _UNICODE #elif defined(_UNICODE) && !defined(UNICODE)#define UNICODE #endif#include <tchar.h> #include <windows.h>/* Declare Windows procedure */…

开源知识库zyplayer-doc部署指南

1.前置条件 docker已经安装 mysql已经安装且数据库zyplayer-doc存在 服务器ip:192.168.168.99/ 数据库账户:root,密码:123456 2.拉取镜像 docker pull zyplayer/zyplayer-doc:latest 3.启动 docker run -d \--restart unless-stopped \--name zyplayer-doc \-p 8083:8083 …

常用的排序算法

该文章笔记结合菜鸟教程的排序算法&#xff0c;如果后面认识有改动或者完善再继续 最近笔试很多题目都考察过了基本的排序算法&#xff0c;尤其是快排、冒泡、选择&#xff0c;大家在这一方面一定要注意下。 一. 总述 1. 时间复杂度 详细介绍 1. 冒泡排序 冒泡排序重复地走…

看完这篇带你了解大学生必考安全证书NISP详解

NISP证书详解 NISP证书介绍&#xff1a;NISP证书等级&#xff1a;NISP&#xff08;一级&#xff09;报名&#xff1a;NISP&#xff08;一级&#xff09;课程大纲&#xff1a;NISP&#xff08;二级&#xff09;报名NISP&#xff08;二级&#xff09;课程大纲NISP二级置换CISP指南…

开源6位半万用表硬件电路分析

开源6位半手持式万用表 这里用的LM399H参考源&#xff0c;单片机是STM32L152&#xff0c;里面还用了MACHXO2-1200FPGA。 万用表由两块PCB组成。 硬件组成部分 电源管理电路 电源用的是6-10V&#xff0c;电源管理部分&#xff0c;首先用来一个ADP5070芯片&#xff08;内部含有…

Container ansible disguises local ansible 【容器 ansible 伪装本地 ansible】

预备条件&#xff1a; ctr & crictl $ nerdctl & containerd install了解 kubespray 是什么 kubespray 包含 ansible、ansible-playbook命令以及通过kubespray项目安装kubernetes集群的介质。 nerdctl pull quay.io/kubespray/kubespray:v2.23.1 nerdctl save -o qu…

Spell.dbc

Spell.dbc&#xff08;3.3.5.12340&#xff09; 因篇幅较长&#xff0c;以表格文件形式展示 链接地址&#xff1a; Spell.dbc&#xff08;3.3.5.12340&#xff09; 附图&#xff1a;

数据结构学习 jz14剪绳子

关键词&#xff1a;数学 动态规划 快速幂 这道题其实是分为两题。 题目一&#xff1a; 这道题我是没有思路的&#xff0c;看了k神的答案才知道有数学的方法。 方法一&#xff1a; 数学&#xff1a;其实中间的推导我没看&#xff0c;我服了。 思路&#xff1a; 复杂度计算&…