当前位置: 首页 > news >正文

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 查询数据库时,他的顺序如下(只给出涉及到该组件的相关类)

  1. ClassMapper#selectList() : 执行Mybatis查询操作

  1. DynamicDataSourceAnnotationInterceptor#invoke() : Spring AOP 拦截到带有 @DS("db2") 并执行代理增强操作

  1. DataSourceClassResolver#findDSKey() : 查找有注解@DS() 的 类或方法,获取对应的数据源Key 值 也就是 db2。

  1. DynamicDataSourceContextHolder#push() : 设置当前线程数据源

  1. 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;}}
  1. 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();}
  1. 执行剩余的数据库操作至结束。

3 小结

大体上写的略微混乱,但是只要我们知道其自动装配时 ,实例化了哪些Bean,并且知道这些Bean 是干什么的 ,合适调用的,根据执行流程逐步Debugger调试,就可以明白dynamic-datasource组件是如何进行数据源切换的,在流程中我认为比较经典也是比较核心的地方已经标注出源码。我们可以借鉴 DynamicDataSourceContextHolder 这个公共类的思想,扩展和优化我们现有的项目中某些跨资源调用的问题。

相关文章:

SpringBoot整合MybatisPlus多数据源

相信在很多使用MybatisPlus框架的小伙伴都会遇到多数据源的配置问题&#xff0c;并且官网也给出了推荐使用多数据源 (dynamic-datasource-spring-boot-starter) 组件来实现。由于最近项目也在使用这个组件来实现多数据源切换&#xff0c;因此想了解一下该组件是如何运行的&…...

【教程】如何使用Java生成PDF文档?

在如今数字化时代&#xff0c;越来越多的人使用PDF文档进行信息传递和共享。而使用Java生成PDF文档也成为了一个非常重要的技能&#xff0c;因为Java作为一种通用的编程语言&#xff0c;可以在不同的操作系统和平台上运行。下面&#xff0c;我们将为您介绍如何使用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文本编辑器

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

【java基础】接口(interface)

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

ChatGPT(GPT3.5) OpenAI官方API正式发布

OpenAI社区今天凌晨4点多发送的邮件&#xff0c;介绍了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绘制完图纸后&#xff0c;想要将图纸中的某些图形对象转换成三维实体&#xff0c;但却不知道该如何操作&#xff0c;其实很简单&#xff0c;本节CAD绘图教程就和小编一起来了解一下浩辰CAD软件中将符合条件的对象转换为三维实体的相关操作步骤吧&#xff01; 将…...

【K8S笔记】Kubernetes 集群架构与组件介绍

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

9 怎么登录VNC

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

MPI ubuntu安装,mpicc,mpicxx,mpif90的区别

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

移动端笔记

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

操作系统笔记、面试八股(一)—— 进程、线程、协程

文章目录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. 字符串统计 从键盘输入一个包含有英文字母、数字、空格和其它字符的字符串&#xff0c;并分别实现下面的功能&#xff1a;统计字符串中出现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刷入面具

注意:本文仅作参考勿跟操作&#xff0c;root需谨慎&#xff0c;本次测试用的N手Pixel&#xff0c;因参考本文将真机刷成板砖造成的损失与本人无关 1 Google Pixel介绍 1.1手机 google Pixel 在手机选择上&#xff0c;优先选择谷歌系列手机&#xff0c;Nexus和Pixel系列&…...

线程池的基本认识与使用

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

小家电品牌私域增长解决方案来了

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

什么是让ChatGPT爆火的大语言模型(LLM)

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

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

有限自动机到正规文法转换器v1.0

1 项目简介 这是一个功能强大的有限自动机&#xff08;Finite Automaton, FA&#xff09;到正规文法&#xff08;Regular Grammar&#xff09;转换器&#xff0c;它配备了一个直观且完整的图形用户界面&#xff0c;使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

嵌入式学习笔记DAY33(网络编程——TCP)

一、网络架构 C/S &#xff08;client/server 客户端/服务器&#xff09;&#xff1a;由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序&#xff0c;负责提供用户界面和交互逻辑 &#xff0c;接收用户输入&#xff0c;向服务器发送请求&#xff0c;并展示服务…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.

ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #&#xff1a…...

STM32---外部32.768K晶振(LSE)无法起振问题

晶振是否起振主要就检查两个1、晶振与MCU是否兼容&#xff1b;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容&#xff08;CL&#xff09;与匹配电容&#xff08;CL1、CL2&#xff09;的关系 2. 如何选择 CL1 和 CL…...

Chrome 浏览器前端与客户端双向通信实战

Chrome 前端&#xff08;即页面 JS / Web UI&#xff09;与客户端&#xff08;C 后端&#xff09;的交互机制&#xff0c;是 Chromium 架构中非常核心的一环。下面我将按常见场景&#xff0c;从通道、流程、技术栈几个角度做一套完整的分析&#xff0c;特别适合你这种在分析和改…...

以太网PHY布局布线指南

1. 简介 对于以太网布局布线遵循以下准则很重要&#xff0c;因为这将有助于减少信号发射&#xff0c;最大程度地减少噪声&#xff0c;确保器件作用&#xff0c;最大程度地减少泄漏并提高信号质量。 2. PHY设计准则 2.1 DRC错误检查 首先检查DRC规则是否设置正确&#xff0c;然…...

多模态大语言模型arxiv论文略读(112)

Assessing Modality Bias in Video Question Answering Benchmarks with Multimodal Large Language Models ➡️ 论文标题&#xff1a;Assessing Modality Bias in Video Question Answering Benchmarks with Multimodal Large Language Models ➡️ 论文作者&#xff1a;Jea…...

设计模式域——软件设计模式全集

摘要 软件设计模式是软件工程领域中经过验证的、可复用的解决方案&#xff0c;旨在解决常见的软件设计问题。它们是软件开发经验的总结&#xff0c;能够帮助开发人员在设计阶段快速找到合适的解决方案&#xff0c;提高代码的可维护性、可扩展性和可复用性。设计模式主要分为三…...