SpringBoot整合MybatisPlus多数据源
相信在很多使用MybatisPlus框架的小伙伴都会遇到多数据源的配置问题,并且官网也给出了推荐使用多数据源 (dynamic-datasource-spring-boot-starter) 组件来实现。由于最近项目也在使用这个组件来实现多数据源切换,因此想了解一下该组件是如何运行的,经过自己的调试,简单记录一下这个组件的实现,也以便日后组件如果出问题了或者某些地方需要开次开发时有个参考。
1 简单实现数据源切换
1.1 数据库demo
本例子使用的是同一个MYSQL服务,不同数据库来进行调试的,具体如图所示

建表语句如下:
CREATE TABLE `class_t` (`name` varchar(30) DEFAULT NULL,`number` varchar(30) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE `user_t` (`id` int(11) NOT NULL AUTO_INCREMENT,`user_name` varchar(30) DEFAULT NULL,`user_sex` varchar(30) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1.2 SpringBoot demo
1.2.1 添加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.4.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.20</version><scope>provided</scope></dependency>
1.2.2 配置YML文件
spring:datasource:type: com.zaxxer.hikari.HikariDataSourcehikari:minimum-idle: 5maximum-pool-size: 15idle-timeout: 30000max-lifetime: 1800000connection-timeout: 30000pool-name: OasisHikariCPconnection-test-query: SELECT 1dynamic:primary: db1 #默认主数据源datasource:db1: #配置主数据源url: jdbc:mysql://127.0.0.1:3306/test?useSSL=true&requireSSL=false&serverTimezone=UTC&characterEncoding=UTF-8username: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driverdb2: #配置其他数据源url: jdbc:mysql://127.0.0.1:3306/test2?useSSL=true&requireSSL=false&serverTimezone=UTC&characterEncoding=UTF-8username: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
1.2.3 实体层
UserEntity.java
/*** @description: DB1中的实体* @date: 2021/7/13 13:38 <br>* @author: wuKeFan <br>* @version: 1.0 <br>*/
@TableName("user_t")
public class UserEntity {private long id;private String userName;private String userSex;public long getId() {return id;}public void setId(long id) {this.id = id;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public String getUserSex() {return userSex;}public void setUserSex(String userSex) {this.userSex = userSex;}
}
ClassEntity.java
/*** @description: DB2中的实体* @date: 2021/7/13 13:40 <br>* @author: wuKeFan <br>* @version: 1.0 <br>*/
@TableName("class_t")
public class ClassEntity {private String name;private String number;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getNumber() {return number;}public void setNumber(String number) {this.number = number;}
}
1.2.4 mapper层
UserMapper.java(使用默认数据源)
/*** @description: UserMapper <br>* @date: 2021/7/13 13:41 <br>* @author: wuKeFan <br>* @version: 1.0 <br>*/
public interface UserMapper extends BaseMapper<UserEntity> {
}
ClassMapper.java(使用另外一个数据源)
/*** @description: ClassMapper <br>* @date: 2021/7/13 13:41 <br>* @author: wuKeFan <br>* @version: 1.0 <br>*/
@DS("db2") //使用另外一个数据源
public interface ClassMapper extends BaseMapper<ClassEntity> {
}
1.2.5 单元测试

结果已经是可以完美运行多数据源。
2 源码解析
在我们搞项目中,不仅要学会用这些组件,更重要的是 要知其所以然,知道他是如何实现的,其实原理也就是网上能搜到的基于切面的代理处理方式,但是其中有些内容还是值得去学习。
2.1 自动装配
首先我们从 dynamic-datasource组件的自动装配开始

接下来让我们来看一下 这个自动装配类,所装配的Bean
@Slf4j
@Configuration
//启动SpringBoot 自动装配 DynamicDataSourceProperties外部化配置
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
//声明装配加载顺序,在 DataSourceAutoConfiguration 之前加载
@AutoConfigureBefore(value = DataSourceAutoConfiguration.class, name = "com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure")
//当自动装配时,引入并自动装配下列三个自动装配类
@Import(value = {DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class, DynamicDataSourceHealthCheckConfiguration.class})
//自动装配加载条件 当 spring.datasource.dynamic = true时 进行自动装配的加载,默认缺省为true
@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class DynamicDataSourceAutoConfiguration implements InitializingBean {//注入外部化配置private final DynamicDataSourceProperties properties;private final List<DynamicDataSourcePropertiesCustomizer> dataSourcePropertiesCustomizers;//构造函数注入public DynamicDataSourceAutoConfiguration(DynamicDataSourceProperties properties,ObjectProvider<List<DynamicDataSourcePropertiesCustomizer>> dataSourcePropertiesCustomizers) {this.properties = properties;this.dataSourcePropertiesCustomizers = dataSourcePropertiesCustomizers.getIfAvailable();}//多数据源加载接口,默认的实现为从yml信息中加载所有数据源 @Beanpublic DynamicDataSourceProvider ymlDynamicDataSourceProvider() {return new YmlDynamicDataSourceProvider(properties.getDatasource());}//实现DataSource JAVA JNDI 后期Spring 容器中 所有的数据库连接都从该实现Bean 中获取@Bean@ConditionalOnMissingBeanpublic DataSource dataSource() {DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();dataSource.setPrimary(properties.getPrimary());dataSource.setStrict(properties.getStrict());dataSource.setStrategy(properties.getStrategy());dataSource.setP6spy(properties.getP6spy());dataSource.setSeata(properties.getSeata());return dataSource;}//设置动态数据源转换切换配置器@Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)@Beanpublic Advisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(properties.isAllowedPublicOnly(), dsProcessor);DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);advisor.setOrder(properties.getOrder());return advisor;}//数据库事务的切面配置类@Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "seata", havingValue = "false", matchIfMissing = true)@Beanpublic Advisor dynamicTransactionAdvisor() {AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression("@annotation(com.baomidou.dynamic.datasource.annotation.DSTransactional)");return new DefaultPointcutAdvisor(pointcut, new DynamicLocalTransactionAdvisor());}//DynamicDataSourceAnnotationInterceptor 切面配置器中所需要的执行链,//主要用来确定使用哪个数据源@Bean@ConditionalOnMissingBeanpublic DsProcessor dsProcessor(BeanFactory beanFactory) {DsHeaderProcessor headerProcessor = new DsHeaderProcessor();DsSessionProcessor sessionProcessor = new DsSessionProcessor();DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();spelExpressionProcessor.setBeanResolver(new BeanFactoryResolver(beanFactory));headerProcessor.setNextProcessor(sessionProcessor);sessionProcessor.setNextProcessor(spelExpressionProcessor);return headerProcessor;}//Bean注入后所执行的方法,本Demo中目前暂无使用@Overridepublic void afterPropertiesSet() {if (!CollectionUtils.isEmpty(dataSourcePropertiesCustomizers)) {for (DynamicDataSourcePropertiesCustomizer customizer : dataSourcePropertiesCustomizers) {customizer.customize(properties);}}}}
大体上的自动装配已经介绍完了,接下来我们逐个将重要的代码段或者类来进行解释
2.2 DynamicDataSourceCreatorAutoConfiguration类分析
这个类主要是进行数据源加载的 主要代码如下
@Slf4j
@Configuration
@AllArgsConstructor
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
public class DynamicDataSourceCreatorAutoConfiguration {//描述Bean的 注入顺序public static final int JNDI_ORDER = 1000;public static final int DRUID_ORDER = 2000;public static final int HIKARI_ORDER = 3000;public static final int BEECP_ORDER = 4000;public static final int DBCP2_ORDER = 5000;public static final int DEFAULT_ORDER = 6000;private final DynamicDataSourceProperties properties;//默认的数据源创造器@Primary@Bean@ConditionalOnMissingBeanpublic DefaultDataSourceCreator dataSourceCreator(List<DataSourceCreator> dataSourceCreators) {DefaultDataSourceCreator defaultDataSourceCreator = new DefaultDataSourceCreator();defaultDataSourceCreator.setProperties(properties);defaultDataSourceCreator.setCreators(dataSourceCreators);return defaultDataSourceCreator;}//省略部分代码/*** 存在Hikari数据源时, 加入创建器*/@ConditionalOnClass(HikariDataSource.class)@Configurationpublic class HikariDataSourceCreatorConfiguration {@Bean@Order(HIKARI_ORDER)@ConditionalOnMissingBeanpublic HikariDataSourceCreator hikariDataSourceCreator() {return new HikariDataSourceCreator(properties.getHikari());}}//省略部分代码}
当Spring 容器注入 DefaultDataSourceCreator 实例后 ,接下来就被 DynamicDataSourceProvider 这个类所使用。
2.3 DynamicDataSourceProvider 分析
@Slf4j
@AllArgsConstructor
public class YmlDynamicDataSourceProvider extends AbstractDataSourceProvider {/*** 所有数据源*/private final Map<String, DataSourceProperty> dataSourcePropertiesMap;//通过构造函数注入所有的 数据源 然后调用该父类方法创建数据源集合@Overridepublic Map<String, DataSource> loadDataSources() {return createDataSourceMap(dataSourcePropertiesMap);}
}
@Slf4j
public abstract class AbstractDataSourceProvider implements DynamicDataSourceProvider {//从Spring 容器中获取注入好的 DefaultDataSourceCreator @Autowiredprivate DefaultDataSourceCreator defaultDataSourceCreator;//创建数据源集合protected Map<String, DataSource> createDataSourceMap(Map<String, DataSourceProperty> dataSourcePropertiesMap) {Map<String, DataSource> dataSourceMap = new HashMap<>(dataSourcePropertiesMap.size() * 2);for (Map.Entry<String, DataSourceProperty> item : dataSourcePropertiesMap.entrySet()) {DataSourceProperty dataSourceProperty = item.getValue();String poolName = dataSourceProperty.getPoolName();if (poolName == null || "".equals(poolName)) {poolName = item.getKey();}dataSourceProperty.setPoolName(poolName);dataSourceMap.put(poolName, defaultDataSourceCreator.createDataSource(dataSourceProperty));}return dataSourceMap;}
}
2.4 DynamicDataSourceAnnotationAdvisor 分析
这个其实就是Spring AOP的切面配置器 主要代码如下
public class DynamicDataSourceAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {//切面增强方法private final Advice advice;private final Pointcut pointcut;//构造方法注入public DynamicDataSourceAnnotationAdvisor(@NonNull DynamicDataSourceAnnotationInterceptor dynamicDataSourceAnnotationInterceptor) {this.advice = dynamicDataSourceAnnotationInterceptor;this.pointcut = buildPointcut();}@Overridepublic Pointcut getPointcut() {return this.pointcut;}@Overridepublic Advice getAdvice() {return this.advice;}//省略部分代码//当有类或者方法中有 DS.class 注解时 进行 切面增强private Pointcut buildPointcut() {Pointcut cpc = new AnnotationMatchingPointcut(DS.class, true);Pointcut mpc = new AnnotationMethodPoint(DS.class);return new ComposablePointcut(cpc).union(mpc);}//省略部分代码
}
2.5 DynamicDataSourceAnnotationInterceptor 分析
该类为切面增强,即当上面的DynamicDataSourceAnnotationAdvisor 拦截到类或者方法中有 DS.class 注解时 ,调用该增强类进行处理
public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {/*** The identification of SPEL.*/private static final String DYNAMIC_PREFIX = "#";private final DataSourceClassResolver dataSourceClassResolver;private final DsProcessor dsProcessor;public DynamicDataSourceAnnotationInterceptor(Boolean allowedPublicOnly, DsProcessor dsProcessor) {dataSourceClassResolver = new DataSourceClassResolver(allowedPublicOnly);this.dsProcessor = dsProcessor;}//AOP拦截后进行 切面增强方法@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {//选择数据源String dsKey = determineDatasourceKey(invocation);//使用基于ThreadLocal的实现切换数据源DynamicDataSourceContextHolder.push(dsKey);try {return invocation.proceed();} finally {DynamicDataSourceContextHolder.poll();}}//通过调用 DsProcessor 来链式调用进行 数据源的确认private String determineDatasourceKey(MethodInvocation invocation) {String key = dataSourceClassResolver.findDSKey(invocation.getMethod(), invocation.getThis());return (!key.isEmpty() && key.startsWith(DYNAMIC_PREFIX)) ? dsProcessor.determineDatasource(invocation, key) : key;}
}
2.6 DefaultPointcutAdvisor 分析
该切面增强为事务增强,设置此增强类后,不能与Spring 源事务或者 @Transactional 注解共用。
@Slf4j
public class DynamicLocalTransactionAdvisor implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {if (!StringUtils.isEmpty(TransactionContext.getXID())) {return methodInvocation.proceed();}boolean state = true;Object o;String xid = UUID.randomUUID().toString();TransactionContext.bind(xid);try {o = methodInvocation.proceed();} catch (Exception e) {state = false;throw e;} finally {ConnectionFactory.notify(state);TransactionContext.remove();}return o;}
}
2.7 DynamicDataSourceContextHolder 核心切换类
public final class DynamicDataSourceContextHolder {/*** 为什么要用链表存储(准确的是栈)* <pre>* 为了支持嵌套切换,如ABC三个service都是不同的数据源* 其中A的某个业务要调B的方法,B的方法需要调用C的方法。一级一级调用切换,形成了链。* 传统的只设置当前线程的方式不能满足此业务需求,必须使用栈,后进先出。* </pre>*/private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new NamedThreadLocal<Deque<String>>("dynamic-datasource") {@Overrideprotected Deque<String> initialValue() {return new ArrayDeque<>();}};private DynamicDataSourceContextHolder() {}/*** 获得当前线程数据源** @return 数据源名称*/public static String peek() {return LOOKUP_KEY_HOLDER.get().peek();}/*** 设置当前线程数据源* <p>* 如非必要不要手动调用,调用后确保最终清除* </p>** @param ds 数据源名称*/public static String push(String ds) {String dataSourceStr = StringUtils.isEmpty(ds) ? "" : ds;LOOKUP_KEY_HOLDER.get().push(dataSourceStr);return dataSourceStr;}/*** 清空当前线程数据源* <p>* 如果当前线程是连续切换数据源 只会移除掉当前线程的数据源名称* </p>*/public static void poll() {Deque<String> deque = LOOKUP_KEY_HOLDER.get();deque.poll();if (deque.isEmpty()) {LOOKUP_KEY_HOLDER.remove();}}/*** 强制清空本地线程* <p>* 防止内存泄漏,如手动调用了push可调用此方法确保清除* </p>*/public static void clear() {LOOKUP_KEY_HOLDER.remove();}
}
大致核心的代码已经介绍完了,接下来我们逐步debugger,摸清其执行流程。
2.8 数据源切换执行流程
现在当我们执行上面的SpringBoot demo中的 调用注解 @DS("db2") 的 Mapper 查询数据库时,他的顺序如下(只给出涉及到该组件的相关类)
ClassMapper#selectList() : 执行Mybatis查询操作
DynamicDataSourceAnnotationInterceptor#invoke() : Spring AOP 拦截到带有 @DS("db2") 并执行代理增强操作
DataSourceClassResolver#findDSKey() : 查找有注解@DS() 的 类或方法,获取对应的数据源Key 值 也就是 db2。
DynamicDataSourceContextHolder#push() : 设置当前线程数据源
DynamicRoutingDataSource#getConnection(): 调用父类方法获取数据库连接 这里两种处理方式 如下所示
public Connection getConnection() throws SQLException {String xid = TransactionContext.getXID();//无事务时 即当前操作为 查询if (StringUtils.isEmpty(xid)) {return determineDataSource().getConnection();} else {//有事物时 ,先从 之前DynamicDataSourceContextHolder 中获取数据源 先进先出原则String ds = DynamicDataSourceContextHolder.peek();ds = StringUtils.isEmpty(ds) ? "default" : ds;ConnectionProxy connection = ConnectionFactory.getConnection(ds);//创建数据源return connection == null ? getConnectionProxy(ds, determineDataSource().getConnection()) : connection;}}
DynamicRoutingDataSource#getDataSource 设置数据源
public DataSource getDataSource(String ds) {//如果当前无 数据源声明 则使用默认数据源if (StringUtils.isEmpty(ds)) {return determinePrimaryDataSource();} else if (!groupDataSources.isEmpty() && groupDataSources.containsKey(ds)) {log.debug("dynamic-datasource switch to the datasource named [{}]", ds);return groupDataSources.get(ds).determineDataSource();} else if (dataSourceMap.containsKey(ds)) {//如果当前存在数据源则取出该数据源返回log.debug("dynamic-datasource switch to the datasource named [{}]", ds);return dataSourceMap.get(ds);}if (strict) {throw new CannotFindDataSourceException("dynamic-datasource could not find a datasource named" + ds);}return determinePrimaryDataSource();}
执行剩余的数据库操作至结束。
3 小结
大体上写的略微混乱,但是只要我们知道其自动装配时 ,实例化了哪些Bean,并且知道这些Bean 是干什么的 ,合适调用的,根据执行流程逐步Debugger调试,就可以明白dynamic-datasource组件是如何进行数据源切换的,在流程中我认为比较经典也是比较核心的地方已经标注出源码。我们可以借鉴 DynamicDataSourceContextHolder 这个公共类的思想,扩展和优化我们现有的项目中某些跨资源调用的问题。
相关文章:

SpringBoot整合MybatisPlus多数据源
相信在很多使用MybatisPlus框架的小伙伴都会遇到多数据源的配置问题,并且官网也给出了推荐使用多数据源 (dynamic-datasource-spring-boot-starter) 组件来实现。由于最近项目也在使用这个组件来实现多数据源切换,因此想了解一下该组件是如何运行的&…...
【教程】如何使用Java生成PDF文档?
在如今数字化时代,越来越多的人使用PDF文档进行信息传递和共享。而使用Java生成PDF文档也成为了一个非常重要的技能,因为Java作为一种通用的编程语言,可以在不同的操作系统和平台上运行。下面,我们将为您介绍如何使用Java生成PDF文…...

I.MX6ULL内核开发13:pinctrl子系统和gpio子系统-led实验
目录 一、pinctrl子系统 1.1 pinctrl子系统编写格式以及引脚属性介绍 1.1.1 iomux节点介绍 1.1.2 pinctrl子节点编写格式 1.1.3 引脚配置信息介绍 1.2 将RGB灯引脚添加到pinctrl子系统 1.2.1 查找RGB灯使用的引脚 1.2.2找到引脚宏定义 1.2.3 设置引脚属性 1.2.4 在…...

Linux系列 使用vi文本编辑器
作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页 目录 前言 一.vi文本编辑器 1.使用vi文本编辑器 2.vi编辑器的工作模式 3.命令模式中的…...

【java基础】接口(interface)
文章目录基础介绍接口的定义关于接口字段和方法的说明使用接口抽象类和接口接口方法冲突的一些说明方法相同名称和参数,返回值相同方法名称相同,参数不同,返回值相同方法返回值不同,名称参数相同方法完全相同,一个有默…...

ChatGPT(GPT3.5) OpenAI官方API正式发布
OpenAI社区今天凌晨4点多发送的邮件,介绍了ChatGPT官方API的发布。官方介绍文档地址为“OpenAI API”和“OpenAI API”。 ChatGPT(GPT3.5)官方API模型名称为“gpt-3.5-turbo”和“gpt-3.5-turbo-0301”。API调用价格比GPT text-davinci-003模型便宜10倍。调用费用为…...

CAD中如何将图形对象转换为三维实体?
有些小伙伴在CAD绘制完图纸后,想要将图纸中的某些图形对象转换成三维实体,但却不知道该如何操作,其实很简单,本节CAD绘图教程就和小编一起来了解一下浩辰CAD软件中将符合条件的对象转换为三维实体的相关操作步骤吧! 将…...

【K8S笔记】Kubernetes 集群架构与组件介绍
K8S 官方文档 https://kubernetes.io/zh/docs/home ##注重关注 概念和任务 板块。 K8S 集群架构 K8S也是运用了分布式集群架构: 管理节点/Master 整个集群的管理,任务协作。工作节点/Node 容器运行、删除。 K8S 组件介绍 管理节点/Master 相关组件 …...

9 怎么登录VNC
1)首先在ssh登录后启动vncserver。登陆后输入下面的指令来创建自己的VNC。 命令vncserver :16 –geometry 1900x1000 其中:16是分配的端口号,1900x1000是分辨率。如果没有响应,建议执行下面操作后再次重复上面操作。 命令…...

MPI ubuntu安装,mpicc,mpicxx,mpif90的区别
介绍 MPI是并行计算的一个支持库,支持对C、C、fortran语言进行并行计算。 安装基础环境 ubuntu进行gcc/g/gfortran的安装: gcc: ubuntu下自带gcc编译器。可以通过gcc -v命令来查看是否安装。 g: sudo apt-get install buil…...

移动端笔记
目录 一、移动端基础 二、视口 三、二倍图/多倍图 四、移动端开发 (一)开发选择 (二)常见布局 (三)移动端技术解决方案 五、移动WEB开发之flex布局 六、移动WEB开发之rem适配布局 #END(…...

操作系统笔记、面试八股(一)—— 进程、线程、协程
文章目录1. 进程、线程、协程1.1 进程1.1.1 进程间的通信方式1.1.2 进程同步方式1.1.3 进程的调度算法1.1.4 优先级反转1.1.5 进程状态1.1.6 PCB进程控制块1.1.7 进程的创建和撤销过程1.1.8 为什么要有进程1.2 线程1.2.1 为什么要有线程1.2.2 线程间的同步方式1.3 协程1.3.1 什…...

Python每日一练(20230302)
目录 1. 字符串统计 2. 合并两个有序链表 3. 下一个排列 附录 Python字典内置方法 增 删 改 查 其它 1. 字符串统计 从键盘输入一个包含有英文字母、数字、空格和其它字符的字符串,并分别实现下面的功能:统计字符串中出现2次的英文字母&#…...
Numpy课后练习
Numpy课后练习 文章目录 Numpy课后练习一、前言二、题目及答案一、前言 答案仅供参考,谢谢大家! 二、题目及答案 导入Numpy包并设置随机数种子为666 import numpy as np np.random.seed(666)创建并输出一个包含12个元素的随机整数数组r1,元素的取值范围在[30,100)之间 r1 …...
动态规划dp中的子序列、子数组问题总结
目录 定义dp数组 初始化dp数组 状态转移方程 最终结果 题目 定义dp数组 这类问题的共性是会提供两个数组,寻找他们共同的子序列、子数组。设第一个数组为s,第二个数组为t。则可以设二维dp数组,其大小为len(s + 1)*len(t + 1) dp[i][j]表示 s 前 i 个长度,...

Zookeeper3.5.7版本——Zookeeper的概述、工作机制、特点、数据结构及应用场景
目录一、Zookeeper的概述二、Zookeeper的工作机制三、Zookeeper的特点四、Zookeeper的数据结构五、Zookeeper的应用场景5.1、统一命名服务5.2、统一配置管理5.3、统一集群管理5.4、服务器动态上下线5.5、软负载均衡一、Zookeeper的概述 Zookeeper 是一个开源的分布式的&#x…...

安卓逆向学习及APK抓包(二)--Google Pixel一代手机的ROOT刷入面具
注意:本文仅作参考勿跟操作,root需谨慎,本次测试用的N手Pixel,因参考本文将真机刷成板砖造成的损失与本人无关 1 Google Pixel介绍 1.1手机 google Pixel 在手机选择上,优先选择谷歌系列手机,Nexus和Pixel系列&…...

线程池的基本认识与使用
线程池的基本认识与使用线程池线程池工作原理:优点:传统的创建线程方式线程池创建线程使用线程池 池化思想:线程池、字符串常量池、数据库连接池可以提高资源的利用率 线程池工作原理: 预先创建多个线程对象 放入线程池种&#…...

小家电品牌私域增长解决方案来了
小家电品牌的私域优势 01、行业线上化发展程度高 相对于大家电动辄上千上万元的价格,小家电的客单价较低。而且与大家电偏刚需属性不同的是,小家电的消费需求侧重场景化,用户希望通过购买小家电来提高自身的生活品质。这就决定了用户的决策…...

什么是让ChatGPT爆火的大语言模型(LLM)
什么是让ChatGPT爆火的大语言模型(LLM) 更多精彩内容: https://www.nvidia.cn/gtc-global/?ncidref-dev-876561 文章目录什么是让ChatGPT爆火的大语言模型(LLM)大型语言模型有什么用?大型语言模型如何工作?大型语言模型的热门应用在哪里可以找到大型语言…...

利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...

嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...