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

数据库-MySQL-Dynamic-Datasource源码解析

文章目录

  • 前言
  • 一、简介
  • 二、整体流程
  • 三、核心解析
  • 四、总结

前言

多数据源的应用在日常项目中也是很常见的场景。

dynamic-datasource的功能,用起来的确很方便,只需要一个@DS注解,加上一些简单的配置即可完成多数据源的切换。究竟是怎么做到的呢,底层是怎么实现呢?带着这个疑问,一起研究了一下源码。

由于框架本身功能点比较多,只关心核心的功能,就是多数据源的切换。

一、简介

Dynamic-Datasource是一款动态数据源的轻量级实现,封装成Spring Starter方式引入,支持Mybatis、MybatisPlus读写分离,支持通过注解动态切换切换数据源。

@DS:指定使用数据库跟配置文件中配置的对应,可以注解在方法上和类上,同时存在方法注解优先于类上注解,强烈建议注解在service实现或mapper接口方法上。

二、整体流程

想研究透彻一点,建议大家自己打开IDEA,参考我写的去研究一下。这里我画个整体的流程图,能有个大概的思路:
DS类图

三、核心解析

首先我们都记得,一开始需要引入spring-boot-starter:

<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.3.0</version>
</dependency>

一般starter自动配置,都是从 META-INF/spring.factories文件中指定自动配置类:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration

接着打开这个类:

/*** 动态数据源核心自动配置类** @author TaoYu Kanyuxia* @see DynamicDataSourceProvider* @see DynamicDataSourceStrategy* @see DynamicRoutingDataSource* @since 1.0.0*/
@Slf4j
@Configuration
@AllArgsConstructor
//以spring.datasource.dynamic为前缀读取配置
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
//在SpringBoot注入DataSourceAutoConfiguration的bean自动配置之前,先加载注入当前这个类的bean到容器中
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
//引入了Druid的autoConfig和各种数据源连接池的Creator
@Import(value = {DruidDynamicDataSourceConfiguration.class, DynamicDataSourceCreatorAutoConfiguration.class})
//条件加载,当前缀是"spring.datasource.dynamic"配置的时候启用这个autoConfig
@ConditionalOnProperty(prefix = DynamicDataSourceProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class DynamicDataSourceAutoConfiguration {private final DynamicDataSourceProperties properties;//读取多数据源配置,注入到spring容器中@Bean@ConditionalOnMissingBeanpublic DynamicDataSourceProvider dynamicDataSourceProvider() {Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();return new YmlDynamicDataSourceProvider(datasourceMap);}//注册自己的动态多数据源DataSource@Bean@ConditionalOnMissingBeanpublic DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();dataSource.setPrimary(properties.getPrimary());dataSource.setStrict(properties.getStrict());dataSource.setStrategy(properties.getStrategy());dataSource.setProvider(dynamicDataSourceProvider);dataSource.setP6spy(properties.getP6spy());dataSource.setSeata(properties.getSeata());return dataSource;}//AOP切面,对DS注解过的方法进行增强,达到切换数据源的目的@Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)@Bean@ConditionalOnMissingBeanpublic DynamicDataSourceAnnotationAdvisor 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 DynamicTransactionAdvisor());}//动态参数解析器链@Bean@ConditionalOnMissingBeanpublic DsProcessor dsProcessor() {DsHeaderProcessor headerProcessor = new DsHeaderProcessor();DsSessionProcessor sessionProcessor = new DsSessionProcessor();DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();headerProcessor.setNextProcessor(sessionProcessor);sessionProcessor.setNextProcessor(spelExpressionProcessor);return headerProcessor;}}

我们可以发现,在使用的时候配置的前缀为spring.datasource.dynamic的配置都会被读取到DynamicDataSourceProperties类,作为一个Bean注入到Spring容器。其实这种读取配置文件信息的方式在日常开发中也是很常见的。

@Slf4j
@Getter
@Setter
@ConfigurationProperties(prefix = DynamicDataSourceProperties.PREFIX)
public class DynamicDataSourceProperties {public static final String PREFIX = "spring.datasource.dynamic";public static final String HEALTH = PREFIX + ".health";/*** 必须设置默认的库,默认master*/private String primary = "master";/*** 是否启用严格模式,默认不启动. 严格模式下未匹配到数据源直接报错, 非严格模式下则使用默认数据源primary所设置的数据源*/private Boolean strict = false;/*** 是否使用p6spy输出,默认不输出*/private Boolean p6spy = false;/*** 是否使用开启seata,默认不开启*/private Boolean seata = false;/*** seata使用模式,默认AT*/private SeataMode seataMode = SeataMode.AT;/*** 是否使用 spring actuator 监控检查,默认不检查*/private boolean health = false;/*** 每一个数据源*/private Map<String, DataSourceProperty> datasource = new LinkedHashMap<>();/*** 多数据源选择算法clazz,默认负载均衡算法*/private Class<? extends DynamicDataSourceStrategy> strategy = LoadBalanceDynamicDataSourceStrategy.class;/*** aop切面顺序,默认优先级最高*/private Integer order = Ordered.HIGHEST_PRECEDENCE;/*** Druid全局参数配置*/@NestedConfigurationPropertyprivate DruidConfig druid = new DruidConfig();/*** HikariCp全局参数配置*/@NestedConfigurationPropertyprivate HikariCpConfig hikari = new HikariCpConfig();/*** 全局默认publicKey*/private String publicKey = CryptoUtils.DEFAULT_PUBLIC_KEY_STRING;/*** aop 切面是否只允许切 public 方法*/private boolean allowedPublicOnly = true;
}

但是读取到配置文件怎么让这些配置文件信息跟spring的DataSource结合起来呢?我们利用反向思维,从结果往回推,要整合一个数据源到spring,是需要实现DataSource接口,那么Mybatis-Plus的动态数据源也是有实现的,就是这个:

/*** 抽象动态获取数据源** @author TaoYu* @since 2.2.0*/
public abstract class AbstractRoutingDataSource extends AbstractDataSource {//抽象方法,由子类实现,让子类决定最终使用的数据源protected abstract DataSource determineDataSource();//重写getConnection()方法,实现切换数据源的功能@Overridepublic Connection getConnection() throws SQLException {//这里xid涉及分布式事务的处理String xid = TransactionContext.getXID();if (StringUtils.isEmpty(xid)) {//不使用分布式事务,就是直接返回一个数据连接return determineDataSource().getConnection();} else {String ds = DynamicDataSourceContextHolder.peek();ConnectionProxy connection = ConnectionFactory.getConnection(ds);return connection == null ? getConnectionProxy(ds, determineDataSource().getConnection()) : connection;}}
}

上面的源码如果学过模板模式肯定都熟悉,他把获取DataSource的行为延伸到子类去实现了,所以关键在于看子类的实现:

@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource implements InitializingBean, DisposableBean {private static final String UNDERLINE = "_";/*** 所有数据库*/private final Map<String, DataSource> dataSourceMap = new ConcurrentHashMap<>();/*** 分组数据库*/private final Map<String, GroupDataSource> groupDataSources = new ConcurrentHashMap<>();@Setterprivate DynamicDataSourceProvider provider;@Setterprivate Class<? extends DynamicDataSourceStrategy> strategy = LoadBalanceDynamicDataSourceStrategy.class;@Setterprivate String primary = "master";@Setterprivate Boolean strict = false;@Setterprivate Boolean p6spy = false;@Setterprivate Boolean seata = false;@Overridepublic DataSource determineDataSource() {return getDataSource(DynamicDataSourceContextHolder.peek());}private DataSource determinePrimaryDataSource() {log.debug("dynamic-datasource switch to the primary datasource");return groupDataSources.containsKey(primary) ? groupDataSources.get(primary).determineDataSource() : dataSourceMap.get(primary);}@Overridepublic void afterPropertiesSet() throws Exception {// 检查开启了配置但没有相关依赖checkEnv();// 添加并分组数据源Map<String, DataSource> dataSources = provider.loadDataSources();for (Map.Entry<String, DataSource> dsItem : dataSources.entrySet()) {addDataSource(dsItem.getKey(), dsItem.getValue());}// 检测默认数据源是否设置if (groupDataSources.containsKey(primary)) {log.info("dynamic-datasource initial loaded [{}] datasource,primary group datasource named [{}]", dataSources.size(), primary);} else if (dataSourceMap.containsKey(primary)) {log.info("dynamic-datasource initial loaded [{}] datasource,primary datasource named [{}]", dataSources.size(), primary);} else {throw new RuntimeException("dynamic-datasource Please check the setting of primary");}}
}

他实现了InitializingBean接口,这个接口需要实现afterPropertiesSet()方法,这是一个Bean的生命周期函数,在Bean初始化的时候做一些操作。

这里做的操作就是检查配置,然后通过调用provider.loadDataSources()方法获取到关于DataSource的Map集合,Key是数据源的名称,Value则是DataSource。

@Slf4j
@AllArgsConstructor
public class YmlDynamicDataSourceProvider extends AbstractDataSourceProvider {/*** 所有数据源*/private final Map<String, DataSourceProperty> dataSourcePropertiesMap;@Overridepublic Map<String, DataSource> loadDataSources() {//调AbstractDataSourceProvider的createDataSourceMap()方法return createDataSourceMap(dataSourcePropertiesMap);}
}@Slf4j
public abstract class AbstractDataSourceProvider implements DynamicDataSourceProvider {@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;}
}

这里的defaultDataSourceCreator.createDataSource()方法使用到适配器模式。

因为每种配置数据源创建的DataSource实现类都不一定相同的,所以需要根据配置的数据源类型进行具体的DataSource创建。

@Override
public DataSource createDataSource(DataSourceProperty dataSourceProperty, String publicKey) {DataSourceCreator dataSourceCreator = null;//this.creators是所有适配的DataSourceCreator实现类for (DataSourceCreator creator : this.creators) {//根据配置匹配对应的dataSourceCreatorif (creator.support(dataSourceProperty)) {//如果匹配,则使用对应的dataSourceCreatordataSourceCreator = creator;break;}}if (dataSourceCreator == null) {throw new IllegalStateException("creator must not be null,please check the DataSourceCreator");}//然后再调用createDataSource方法进行创建对应DataSourceDataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty, publicKey);this.runScrip(dataSource, dataSourceProperty);return wrapDataSource(dataSource, dataSourceProperty);
}

对应的全部实现类是放在creator包下:

我们看其中一个实现类就:

@Data
@AllArgsConstructor
public class HikariDataSourceCreator extends AbstractDataSourceCreator implements DataSourceCreator {private static Boolean hikariExists = false;static {try {Class.forName(HIKARI_DATASOURCE);hikariExists = true;} catch (ClassNotFoundException ignored) {}}private HikariCpConfig hikariCpConfig;//创建HikariCp数据源@Overridepublic DataSource createDataSource(DataSourceProperty dataSourceProperty, String publicKey) {if (StringUtils.isEmpty(dataSourceProperty.getPublicKey())) {dataSourceProperty.setPublicKey(publicKey);}HikariConfig config = dataSourceProperty.getHikari().toHikariConfig(hikariCpConfig);config.setUsername(dataSourceProperty.getUsername());config.setPassword(dataSourceProperty.getPassword());config.setJdbcUrl(dataSourceProperty.getUrl());config.setPoolName(dataSourceProperty.getPoolName());String driverClassName = dataSourceProperty.getDriverClassName();if (!StringUtils.isEmpty(driverClassName)) {config.setDriverClassName(driverClassName);}return new HikariDataSource(config);}//判断是否是HikariCp数据源@Overridepublic boolean support(DataSourceProperty dataSourceProperty) {Class<? extends DataSource> type = dataSourceProperty.getType();return (type == null && hikariExists) || (type != null && HIKARI_DATASOURCE.equals(type.getName()));}
}

再回到之前的,当拿到DataSource的Map集合之后,再做什么呢?

接着调addDataSource()方法,这个方法是根据下划线"_"对数据源进行分组,最后放到groupDataSources成员变量里面。

/*** 新数据源添加到分组** @param ds         新数据源的名字* @param dataSource 新数据源*/
private void addGroupDataSource(String ds, DataSource dataSource) {if (ds.contains(UNDERLINE)) {String group = ds.split(UNDERLINE)[0];GroupDataSource groupDataSource = groupDataSources.get(group);if (groupDataSource == null) {try {//顺便设置负载均衡策略,strategy默认是LoadBalanceDynamicDataSourceStrategygroupDataSource = new GroupDataSource(group, strategy.getDeclaredConstructor().newInstance());groupDataSources.put(group, groupDataSource);} catch (Exception e) {throw new RuntimeException("dynamic-datasource - add the datasource named " + ds + " error", e);}}groupDataSource.addDatasource(ds, dataSource);}
}

分组的时候,会顺便把负载均衡策略也一起设置进去。这个负载均衡是做什么呢?

比如一个组master里有三个数据源(A、B、C),需要合理地分配使用的频率,不可能全都使用某一个,那么这就需要负载均衡策略,默认是轮询,对应的类是:

public class LoadBalanceDynamicDataSourceStrategy implements DynamicDataSourceStrategy {/*** 负载均衡计数器*/private final AtomicInteger index = new AtomicInteger(0);@Overridepublic DataSource determineDataSource(List<DataSource> dataSources) {return dataSources.get(Math.abs(index.getAndAdd(1) % dataSources.size()));}
}

获取数据源的时候就通过:

@Override
public DataSource determineDataSource() {return getDataSource(DynamicDataSourceContextHolder.peek());
}/*** 获取数据源** @param ds 数据源名称* @return 数据源*/
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 RuntimeException("dynamic-datasource could not find a datasource named" + ds);}return determinePrimaryDataSource();
}

那么上面的DynamicDataSourceContextHolder这个类是干嘛的呢?注解@DS的值又是怎么传进来的呢?

回到最开始的自动配置类,其中有一个是配置DynamicDataSourceAnnotationAdvisor的,还设置了一个拦截器:

@Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
@ConditionalOnMissingBean
public DynamicDataSourceAnnotationAdvisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {//创建拦截器DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(properties.isAllowedPublicOnly(), dsProcessor);DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);advisor.setOrder(properties.getOrder());return advisor;
}

DynamicDataSourceAnnotationAdvisor是用于AOP切面编程的,针对注解@DS的切面进行处理:

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;}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {if (this.advice instanceof BeanFactoryAware) {((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);}}private Pointcut buildPointcut() {//类上面添加了注解Pointcut cpc = new AnnotationMatchingPointcut(DS.class, true);//方法上添加了注解Pointcut mpc = new AnnotationMethodPoint(DS.class);//方法优于类return new ComposablePointcut(cpc).union(mpc);}
}

切入点我们都清楚了,是@DS注解。那么做了什么处理,主要看advice,也就是传进来的那个拦截器

DynamicDataSourceAnnotationInterceptor

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;}@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {//找到@DS注解的属性值,也就是数据源名称String dsKey = determineDatasourceKey(invocation);//把数据源名称push到当前线程的栈DynamicDataSourceContextHolder.push(dsKey);try {//执行当前方法return invocation.proceed();} finally {//从栈里释放数据源DynamicDataSourceContextHolder.poll();}}//这个是使用责任链模式进行一些处理,可以先不管他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;}
}

这里也有一个DynamicDataSourceContextHolder,这样就跟前面获取数据连接关联起来了,最后我们看一下这个类的源码:

/*** 核心基于ThreadLocal的切换数据源工具类** @author TaoYu Kanyuxia* @since 1.0.0*/
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 void push(String ds) {LOOKUP_KEY_HOLDER.get().push(StringUtils.isEmpty(ds) ? "" : ds);}/*** 清空当前线程数据源* <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();}
}

这里为什么使用栈,主要是会存在嵌套切换数据源的情况,也就是最里面那层数据源应该先释放,最外面那层的数据源应该最后释放,所以需要用栈的数据结构。

四、总结

这篇文章介绍的这个框架的源码解析只是涉及核心代码,所以不是很难。

源码解析能提高读代码的能力,读代码的能力是很重要的,当我们来到新的环境,对项目不熟悉,那么就需要从文档,代码上面去了解项目。读懂代码才能去修改、扩展。


参考文章
Dynamic-Datasource源码解析


相关文章:

数据库-MySQL-Dynamic-Datasource源码解析

文章目录 前言一、简介二、整体流程三、核心解析四、总结 前言 多数据源的应用在日常项目中也是很常见的场景。 dynamic-datasource的功能&#xff0c;用起来的确很方便&#xff0c;只需要一个DS注解&#xff0c;加上一些简单的配置即可完成多数据源的切换。究竟是怎么做到的…...

uniapp+vue2+uview2.0导航栏组件二次封装

样式 代码 <template><view class"navBar"><u-navbar :title"title" :titleColor"titleColor" :bgColor"bgColor" :safeAreaInsetTop"safeAreaInsetTop":autoBack"true" leftClick"leftClic…...

【Git 工具】用 IntelliJ IDEA 玩转 Git 分支与版本管理

文章目录 一、使用 IDEA 配置和操作 Git1.1 查看 Idea 中的 Git 配置1.2 克隆 Github 项目到本地 二、版本管理2.1 提交并推送修改2.2 拉取远程仓库2.3 查看历史2.4 版本回退 三、分支管理3.1 新建分支3.2 切换分支3.2 合并分支3.4 Cherry-Pick 参考资料 一、使用 IDEA 配置和操…...

异或-java-leetcode

1486.数组异或操作 给你两个整数&#xff0c;n 和 start 。 数组 nums 定义为&#xff1a;nums[i] start 2*i&#xff08;下标从 0 开始&#xff09;且 n nums.length 。 请返回 nums 中所有元素按位异或&#xff08;XOR&#xff09;后得到的结果。 示例 1&#xff1a; 输入…...

Jmeter中的测试片段和非测试原件

1&#xff09;测试片段 1--测试片段 功能特点 重用性&#xff1a;将常用的测试元素组合成一个测试片段&#xff0c;便于在多个线程组中重用。模块化&#xff1a;提高测试计划的模块化程度&#xff0c;使测试计划更易于管理和维护。灵活性&#xff1a;可以通过模块控制器灵活地…...

NeurIPS 2024 有效投稿达 15,671 篇,数据集版块内容丰富

NeurIPS&#xff0c;全称 Neural Information Processing Systems Conference&#xff0c;是神经信息处理系统的年度学术会议。该会议始于 1987 年&#xff0c;当时名为 NIPS。随着人工智能领域的快速发展&#xff0c;其影响力逐渐扩大&#xff0c;被越来越多的研究者和企业关注…...

力扣101. 对称二叉树

给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 提示&#xff1a; 树中节点数目在范围 [1, 1000] 内-100 < Node.val < 100 进阶&#xff1a;你可以运用递归和迭代两种方法解决这个问题吗&#xff1f; 代码&#xff1a; /*** Definition for a binary …...

Ubuntu 环境下的 C/C++ 编译与调试配置

详细教学文档&#xff1a;Ubuntu 环境下的 C/C 编译与调试配置 本文档将手把手引导你完成在 Ubuntu 环境中设置 VS Code 的 tasks.json 和 launch.json&#xff0c;实现以下目标&#xff1a; 统一管理输出文件&#xff1a;将所有编译生成的可执行文件统一存放到项目的 build …...

『VUE』36. Vue的应用概念(分析流程)

目录 创建应用挂载应用可执行文件公共资源总结 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 创建应用 首先引入createApp ,然后借助createApp 创造一个app对象 main.js import { createApp } from "vue"; import A…...

《基于FPGA的便携式PWM方波信号发生器》论文分析(三)——数码管稳定显示与系统调试

一、论文概述 基于FPGA的便携式PWM方波信号发生器是一篇由任青颖、庹忠曜、黄洵桢、李智禺和张贤宇 等人发表的一篇期刊论文。该论文主要研究了一种新型的信号发生器&#xff0c;旨在解决传统PWM信号发生器在移动设备信号调控中存在的精准度低和便携性差的问题 。其基于现场可编…...

mac上的建议xftp 工具

mac上的建议xftp 工具 最近使用mac比较频繁了&#xff0c;但是第一次重度使用mac里面有很多的工具都是新的&#xff0c;有的window版本的工具无法使用。 xftp 的平替 Cyberduck 从它的官网上下载是免费的&#xff0c;但是如果使用 Apple store 要花费198呢。这不就剩下一大笔…...

Hive | Hive 表如何查看所有分区

文章目录 概述使用 SHOW PARTITIONS 命令查看特定分区的信息获取详细的分区信息总结 概述 Apache Hive 是一个构建在 Hadoop 之上的数据仓库工具&#xff0c;它提供了 SQL 类似的查询语言&#xff08;称为 HiveQL&#xff09;&#xff0c;使得用户能够更容易地进行大数据处理和…...

MySQL系列之数据类型(Numeric)

导览 前言一、数值类型综述二、数值类型详解1. NUMERIC1.1 UNSIGNED或SIGNED1.2 数据类型划分 2. Integer类型取值和存储要求3. Fixed-Point类型取值和存储要求4. Floating-Point类型取值和存储要求 结语精彩回放 前言 MySQL系列最近三篇均关注了和我们日常工作或学习密切相关…...

4.6 JMeter HTTP信息头管理器

欢迎大家订阅【软件测试】 专栏&#xff0c;开启你的软件测试学习之旅&#xff01; 文章目录 前言1 HTTP信息头管理器的位置2 常见的HTTP请求头3 添加 HTTP 信息头管理器4 应用场景 前言 在 JMeter 中&#xff0c;HTTP信息头管理器&#xff08;HTTP Header Manager&#xff09…...

fpga 综合与优化设计

目录 Quartus Prime优化设置 1. 分析与综合设置 2. 物理综合优化 3. 适配设置 描述方法对综合的影响 1. 操作符的应用差异 2. 条件语句和分支语句的应用差异 3. 描述方式对综合电路的影响 优化设计方法 1. FPGA设计的基本原则 2. 常用优化设计方法 优化主要包括 4 个…...

计算机毕业设计Hadoop+Spark音乐推荐系统 音乐预测系统 音乐可视化大屏 音乐爬虫 HDFS hive数据仓库 机器学习 深度学习 大数据毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…...

C嘎嘎探索篇:栈与队列的交响:C++中的结构艺术

C嘎嘎探索篇&#xff1a;栈与队列的交响&#xff1a;C中的结构艺术 前言&#xff1a; 小编在之前刚完成了C中栈和队列&#xff08;stack和queue&#xff09;的讲解&#xff0c;忘记的小伙伴可以去我上一篇文章看一眼的&#xff0c;今天小编将会带领大家吹奏栈和队列的交响&am…...

摄像头原始数据读取——opencv(cv::VideoCapture)

摄像头原始数据读取——opencv(cv::VideoCapture) 测试代码test.cpp #include <iostream> #include <string>#include <opencv2/opencv.hpp>std::string pixeformatcodec2string(int codec) {char pixeformat_name[5] { (char)((codec >> 0) & …...

unreal engine5中多个摄像机切换

UE5系列文章目录 文章目录 UE5系列文章目录前言一、思路二、具体实现 前言 unreal engine5中使用蓝图实现多个相机切换 一、思路 在Unreal Engine 5中&#xff0c;如果你想要在控件蓝图&#xff08;Widget Blueprint&#xff09;中获取场景摄像机的信息&#xff0c;可以按照…...

ensp静态路由实验

一、实验目的 1、熟练掌握交换机的基本配置命令 2、熟练掌握静态路由的使用方法 3. 熟练掌握交换机端口模式 二、实验内容 需求&#xff1a; 根据要求利用现有实验设备组建小型局域网 实验设备&#xff1a; 交换机S37002台&#xff1b;PC机2台&#xff1b;路由器2台。 …...

【在Linux世界中追寻伟大的One Piece】多线程(二)

目录 1 -> 分离线程 2 -> Linux线程互斥 2.1 -> 进程线程间的互斥相关背景概念 2.2 -> 互斥量mutex 2.3 -> 互斥量的接口 2.4 -> 互斥量实现原理探究 3 -> 可重入VS线程安全 3.1 -> 概念 3.2 -> 常见的线程不安全的情况 3.3 -> 常见的…...

Git中HEAD、工作树和索引的区别

在Git版本控制系统中&#xff0c;HEAD、工作树&#xff08;Working Tree&#xff09;和索引&#xff08;Index&#xff09;是三个非常重要的概念&#xff0c;它们分别代表了不同的状态或区域&#xff0c;下面我将对这三个概念进行详细的解释。 HEAD 定义&#xff1a;HEAD是一…...

洛谷 P1156 垃圾陷阱(搜索 DFS)

题目传送门https://www.luogu.com.cn/problem/P1156 解题思路 本题数据过水&#xff0c;可以使用 dfs。 对于每个垃圾&#xff0c;吃掉或者堆着。 然后考虑剪枝&#xff1a; 如果已经等不到下一个垃圾的到来&#xff0c;直接 return&#xff1b; 如果当前状态已经搜过&am…...

nodepad配置c/c++ cmd快速打开创建项目文件

前提:下载MinGw,并且配置环境变量 点击阅读次篇文章配置MinGw 无论是哪个编译器&#xff0c;执行c文件都是经历以下步骤: 编译文件生成exe文件执行该exe文件 我们先手动完成这两部 手动编译文件使用指令 gcc {你的c文件} -o {生成文件名}生成exe文件 第二步运行exe直接点击该文…...

Excel与PPT:职场两大软件的应用比拼

在职场环境中&#xff0c;Excel和PPT无疑是两款最为常用的办公软件。它们各自承担着不同的职责&#xff0c;为职场人士提供了强大的数据处理和演示功能。然而&#xff0c;哪款软件应用得更多&#xff0c;却并非一概而论&#xff0c;而是取决于个人的工作性质、岗位需求以及个人…...

春秋云境 CVE 复现

CVE-2022-4230 靶标介绍 WP Statistics WordPress 插件13.2.9之前的版本不会转义参数&#xff0c;这可能允许经过身份验证的用户执行 SQL 注入攻击。默认情况下&#xff0c;具有管理选项功能 (admin) 的用户可以使用受影响的功能&#xff0c;但是该插件有一个设置允许低权限用…...

文件系统的作用

在一个完整的嵌入式系统中&#xff0c;进行一个简单的操作&#xff08;如读取传感器数据并保存到文件&#xff09;通常会涉及多个步骤。这些步骤包括硬件初始化、数据采集、处理、存储以及与外部系统交互。以下是一个通用的操作流程及文件系统在其中的作用。 嵌入式系统的操作流…...

mysql低版本中update_time不自动更新问题

在mysql低版本时&#xff0c;update_time只有插入时才会自动插入当前时间&#xff0c;更新不会改变。 MySQL 5.7 及以上版本对于TIMESTAMP类型字段有自动更新功能相关特性在 MySQL 5.7以上版本 中&#xff0c;如果将一个TIMESTAMP类型的列设置为ON UPDATE CURRENT_TIMESTAMP属…...

【二叉树】【2.1遍历二叉树】【刷题笔记】【灵神题单】

关注二叉树的三个问题&#xff1a; 什么情况适合自顶向下&#xff1f;什么时候适合用自底向上&#xff1f;一般来说&#xff0c;DFS的递归边界是空节点&#xff0c;什么情况下要额外把叶子节点作为递归边界&#xff1f;在什么情况下&#xff0c;DFS需要有返回值&#xff1f;什…...

【深度学习】【RKNN】【C++】模型转化、环境搭建以及模型部署的详细教程

【深度学习】【RKNN】【C】模型转化、环境搭建以及模型部署的详细教程 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【深度学习】【RKNN】【C】模型转化、环境搭建以及模型部署的详细教程前言模型转换--pytorch转rknnpytorch转onnxonnx转rkn…...

刷题日常(移动零,盛最多水的容器,三数之和,无重复字符的最长子串)

移动零 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 俩种情况&#xff1a; 1.当nums[i]为0的时候 直接i 2.当nums[i]不为0的时候 此时 …...

【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录

背景 Jetbrain IDE 支持生成 Test 类&#xff0c;其中选择JUnit5 和 JUnit&#xff0c;但是感觉这不是标准的单元测试&#xff0c;因为接口命名吧。 差异对比 两者生成的单测API名称同原API&#xff0c;没加test前缀的。使用差异主要表现在&#xff1a; setUp &#xff06; …...

LangChain——HTML文本分割 多种文本分割

Text Splitters 文本分割器 加载文档后&#xff0c;您通常会想要对其进行转换以更好地适合您的应用程序。最简单的例子是&#xff0c;您可能希望将长文档分割成更小的块&#xff0c;以适合模型的上下文窗口。 LangChain 有许多内置的文档转换器&#xff0c;可以轻松地拆分、组…...

Spring事务和事务传播机制

博主主页: 码农派大星. 数据结构专栏:Java数据结构 数据库专栏:数据库 JavaEE专栏:JavaEE 软件测试专栏:软件测试 关注博主带你了解更多知识 目录 1.事务 1.1 什么是事务? 1.2 为什么需要事务? 1.3 事务的操作 2. Spring 中的事务 1. 编程式事务 2. 声明式事务Tra…...

STM32F103外部中断配置

一、外部中断 在上一节我们介绍了STM32f103的嵌套向量中断控制器&#xff0c;其中包括中断的使能、失能、中断优先级分组以及中断优先级配置等内容。 1.1 外部中断/事件控制器 在STM32f103支持的60个可屏蔽中断中&#xff0c;有一些比较特殊的中断&#xff1a; 中断编号13 EXTI…...

ChatGPT的应用场景:开启无限可能的大门

ChatGPT的应用场景&#xff1a;开启无限可能的大门 随着人工智能技术的快速发展&#xff0c;自然语言处理领域迎来了前所未有的突破。其中&#xff0c;ChatGPT作为一款基于Transformer架构的语言模型&#xff0c;凭借其强大的语言理解和生成能力&#xff0c;在多个行业和场景中…...

计算机毕业设计 | SpringBoot+vue社区医院管理系统(附源码+论文)

1&#xff0c;绪论 1.1 研究背景 互联网概念的产生到如今的蓬勃发展&#xff0c;用了短短的几十年时间就风靡全球&#xff0c;使得全球各个行业都进行了互联网的改造升级&#xff0c;标志着互联网浪潮的来临。在这个新的时代&#xff0c;各行各业都充分考虑互联网是否能与本行…...

直播实时美颜平台开发详解:基于视频美颜SDK的技术路径

视频美颜SDK作为实现实时美颜的关键技术&#xff0c;为开发者提供了高效、灵活的解决方案。本篇文章&#xff0c;小编将以“基于视频美颜SDK的技术路径”为主题&#xff0c;深入解析直播实时美颜平台的开发要点。 一、视频美颜SDK的作用与优势 视频美颜SDK是一种集成化的开发工…...

一个专为云原生环境设计的高性能分布式文件系统

大家好&#xff0c;今天给大家分享一款开源创新的分布式 POSIX 文件系统JuiceFS&#xff0c;旨在解决海量云存储与各类应用平台&#xff08;如大数据、机器学习、人工智能等&#xff09;之间高效对接的问题。 项目介绍 JuiceFS 是一款面向云原生设计的高性能分布式文件系统&am…...

【Elasticsearch】开启大数据分析的探索与预处理之旅

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…...

【算法】欧几里得与拓展欧几里得算法

目录 一、欧几里得算法 二、拓展欧几里得算法 2.1 裴蜀定理 2.2 拓展欧几里得算法 2.3 例题 三、线性同余方程 3.1 概念 3.2 例题 一、欧几里得算法 欧几里得算法又称辗转相除法&#xff0c;可用于求解两个数的最大公约数 其思路&#xff1a; gcd(a, b) gcd(b, a%b…...

组合数的求法

1.如果是多组查询的话&#xff0c;需要用数组去储存阶乘的值 n!/(m!(n-m)!) P4071 [SDOI2016] 排列计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<cstdio> #include<iostream> #include<map> #include<cstring> #include<cmath&g…...

【环境搭建】更新Docker Compose到v2.x版本以支持--profile选项

Docker版本陈旧也是搭建的环境起不来的一个重要原因&#xff0c;比如 --profile 选项是 Docker 20.10.0 版本及以上版本才开始支持的&#xff0c;在 Docker Compose v2.1&#xff08;及以上版本&#xff09;中引入用于对服务进行分组和按需启动。 更新 Docker Compose 到 v2.x…...

解决 java -jar 报错:xxx.jar 中没有主清单属性

问题复现 在使用 java -jar xxx.jar 命令运行 Java 应用程序时&#xff0c;遇到了以下错误&#xff1a; xxx.jar 中没有主清单属性这个错误表示 JAR 文件缺少必要的启动信息&#xff0c;Java 虚拟机无法找到应用程序的入口点。本文将介绍该错误的原因以及如何通过修改 pom.xm…...

AIGC-----AIGC在虚拟现实中的应用前景

AIGC在虚拟现实中的应用前景 引言 随着人工智能生成内容&#xff08;AIGC&#xff09;的快速发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术的应用也迎来了新的契机。AIGC与VR的结合为创造沉浸式体验带来了全新的可能性&#xff0c;这种组合不仅极大地降低了VR内容的…...

【博主推荐】C#的winfrom应用中datagridview常见问题及解决方案汇总

文章目录 1.datagridview绘制出现鼠标悬浮数据变空白2.datagridview在每列前动态添加序号2.1 加载数据集完成后绘制序号2.2 RowPostPaint事件绘制 3.datagridview改变行样式4.datagridview后台修改指定列数据5.datagridview固定某个列宽6.datagridview某个列的显示隐藏7.datagr…...

Selenium 自动化测试demo

场景描述&#xff1a; 模拟用户登录页面操作&#xff0c;包括输入用户名、密码、验证码。验证码为算数运算&#xff0c;如下&#xff1a; 使用到的工具和依赖&#xff1a; 1. Selenium&#xff1a;pip install selenium 2. 需要安装浏览器驱动&#xff1a;这里使用的是Edge 3…...

深度神经网络模型压缩学习笔记二:离线量化算法和工具、实现原理和细节

文章目录 一、离线量化基础概念二、离线量化难点三、离线量化算法介绍四、离线量化工具介绍五、离线量化工具整体设计结构六、离线量化工具代码解读七、实践&#xff1a;Dipoorlet量化MobileNet 一、离线量化基础概念 二、离线量化难点 三、离线量化算法介绍 四、离线量化工…...

uni-app运行 安卓模拟器 MuMu模拟器

最近公司开发移动端系统&#xff0c;使用真机时每次调试的时候换来换去的麻烦&#xff0c;所以使用模拟器来调试方便。记录一下安装和连接的过程 一、安装MuMu模拟器 百度搜索MuMu模拟器并打开官网或者点这里MuMu模拟器官网 点击下载模拟器 安装模拟器&#xff0c;如果系统…...

网络安全,文明上网(6)网安相关法律

列举 1. 《中华人民共和国网络安全法》&#xff1a; - 这是中国网络安全的基本法律&#xff0c;于2017年6月1日开始实施。该法律明确了网络运营者的安全保护义务&#xff0c;包括采取数据分类、重要数据备份和加密等措施。 2. 《中华人民共和国数据安全法》&#xff1a; …...