【连接池】-从源码到适配(下),使用dynamic-datasource导致连接池没生效(升级版本)

写在前面

  书接上文,连接池没生效,启用了一个什么默认的连接池。具体是什么,一起来看看源码吧。


目录

  • 写在前面
  • 一、问题描述
  • 二、本地调试
  • 三、升级dynamic-datasource
  • 四、新的问题
    • (一)数据源初始化问题
    • (二)GaussDB updatedTime NULL值问题
  • 五、参考资料
  • 写在后面
  • 系列文章


一、问题描述

  连接池没生效,无外乎就是 yml 的配置没读取到、连接池没创建或者创建失败了。因为没报错,所以极大的可能是 yml 配置没读取到。


二、本地调试

启动项目,debug一下,果然用的是Tomcat的连接池。
在这里插入图片描述

这里,默认根据 DATA_SOURCE_TYPE_NAMES 加载,发现 classpath 中有哪个就用哪个。

package org.springframework.boot.autoconfigure.jdbc;
public class DataSourceBuilder {private static final String[] DATA_SOURCE_TYPE_NAMES = new String[] {"org.apache.tomcat.jdbc.pool.DataSource","com.zaxxer.hikari.HikariDataSource","org.apache.commons.dbcp.BasicDataSource", // deprecated"org.apache.commons.dbcp2.BasicDataSource" };public DataSource build() {Class<? extends DataSource> type = getType();DataSource result = BeanUtils.instantiate(type);maybeGetDriverClassName();bind(result);return result;}public Class<? extends DataSource> findType() {if (this.type != null) {return this.type;}for (String name : DATA_SOURCE_TYPE_NAMES) {try {// 遍历到第一个就返回了,第一个就是tomcat的return (Class<? extends DataSource>) ClassUtils.forName(name,this.classLoader);}catch (Exception ex) {}}return null;}}

那就再找一下连接池初始化数量,进一步确认。果然也正如料想的那样,初始化数量是10个。

package org.apache.tomcat.jdbc.pool;
public class PoolProperties implements PoolConfiguration, Cloneable, Serializable {private volatile int initialSize = 10;
}

那现在就很明确了,是不是添加完dynamic-datasource之后影响了原有的连接池加载?其实这个时候从properties文件中就已经看出问题了:压根没有dbcp2的节点,貌似也不支持dbcp2连接池(只有druid 和 hikari)

以下是 DynamicDataSourceProperties 和 DataSourceProperty(yml的对应实体)。

package com.baomidou.dynamic.datasource.spring.boot.autoconfigure;/*** DynamicDataSourceProperties** @author TaoYu Kanyuxia* @see DataSourceProperties* @since 1.0.0*/
@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;/*** 是否使用 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;
}package com.baomidou.dynamic.datasource.spring.boot.autoconfigure;/*** @author TaoYu* @since 1.2.0*/
@Slf4j
@Data
@Accessors(chain = true)
public class DataSourceProperty {/*** 加密正则*/private static final Pattern ENC_PATTERN = Pattern.compile("^ENC\\((.*)\\)$");/*** 连接池名称(只是一个名称标识)</br> 默认是配置文件上的名称*/private String pollName;/*** 连接池类型,如果不设置自动查找 Druid > HikariCp*/private Class<? extends DataSource> type;/*** JDBC driver*/private String driverClassName;/*** JDBC url 地址*/private String url;/*** JDBC 用户名*/private String username;/*** JDBC 密码*/private String password;/*** jndi数据源名称(设置即表示启用)*/private String jndiName;/*** 自动运行的建表脚本*/private String schema;/*** 自动运行的数据脚本*/private String data;/*** 错误是否继续 默认 true*/private boolean continueOnError = true;/*** 分隔符 默认 ;*/private String separator = ";";/*** Druid参数配置*/@NestedConfigurationPropertyprivate DruidConfig druid = new DruidConfig();/*** HikariCp参数配置*/@NestedConfigurationPropertyprivate HikariCpConfig hikari = new HikariCpConfig();/*** 解密公匙(如果未设置默认使用全局的)*/private String publicKey;public String getUrl() {return decrypt(url);}public String getUsername() {return decrypt(username);}public String getPassword() {return decrypt(password);}/*** 字符串解密*/private String decrypt(String cipherText) {if (StringUtils.hasText(cipherText)) {Matcher matcher = ENC_PATTERN.matcher(cipherText);if (matcher.find()) {try {return CryptoUtils.decrypt(publicKey, matcher.group(1));} catch (Exception e) {log.error("DynamicDataSourceProperties.decrypt error ", e);}}}return cipherText;}
}

咋整?难道不用这个动态数据源,自己写一套?项目紧急,时间上不允许呀!
另外,不可能不支持dbcp2连接池额,走,去官网GitHub上看看。


三、升级dynamic-datasource

官网链接 https://github.com/baomidou/dynamic-datasource/tree/v3.3.4/src/main

不看不知道,一看吓一跳。
源码版本直接从V1.1.0直接到V3.3.0,中间的版本没有了。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
查看源码,确实从3.3.4版本开始支持dbcp2版本数据源(目前用的是2.5.7),那就升级到3.5.2(4.x之后结构又发生了很大变化)

在这里插入图片描述

升级完,yml中配置的连接池生效了 ~

❗️ 技巧:
这也给我们提了一个醒,在引入一个新的框架时,
一定要先去对应的GitHub仓库看源码,不能只依赖于maven仓库。成熟的产品,大都高版本是兼容低版本的。低版本不行,就去试试高版本~

四、新的问题

(一)数据源初始化问题

好用是好用,但是存在一个新的问题:dynamic-datasource会在项目启动时,加载所有的数据源并进行连接,然后通过@DS来动态切换数据源。

显然,这和我们的需求不太一样。我们并不想项目一启动就把所有的数据源都加载了,只想primary配置成哪个,就加载哪个数据源。

看源码,找到数据源加载的位置,确实这里把所有数据源全加载了。

以下是加载数据源的源码部分:

public abstract class AbstractDataSourceProvider implements DynamicDataSourceProvider {@Autowiredprivate DataSourceCreator dataSourceCreator;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 pollName = dataSourceProperty.getPoolName();if (pollName == null || "".equals(pollName)) {pollName = item.getKey();}dataSourceProperty.setPoolName(pollName);dataSourceMap.put(pollName, dataSourceCreator.createDataSource(dataSourceProperty));}return dataSourceMap;}
}

那好办,取出primary属性,判断一下就可以了。
修改如下:

package com.baomidou.dynamic.datasource.provider;import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** @author TaoYu*/
@Slf4j
public abstract class AbstractDataSourceProvider implements DynamicDataSourceProvider {@Autowiredprivate DefaultDataSourceCreator defaultDataSourceCreator;@Autowiredprivate DynamicDataSourceProperties dynamicDataSourceProperties;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()) {String dsName = item.getKey();// 只加载 yml配置文件中 primary指定的数据源if (dsName.equals(dynamicDataSourceProperties.getPrimary())) {DataSourceProperty dataSourceProperty = item.getValue();String poolName = dataSourceProperty.getPoolName();if (poolName == null || "".equals(poolName)) {poolName = dsName;}dataSourceProperty.setPoolName(poolName);dataSourceMap.put(dsName, defaultDataSourceCreator.createDataSource(dataSourceProperty));}}return dataSourceMap;}
}

想让源码的文件生效有两种方式:
第一种把jar包中的文件copy出来,在项目下创建相同的包名。
第二种改源码。

因为我们有Nexus私服,这里就采用第二种方式,修改源码,维护一个自己的版本,也方便后期自定义。

构建nexus私服构件,下载源码,在pom中配置,以及maven的settings.xml中的认证,deploy即可到远程私服上查看是否部署成功。

修改依赖版本为自定义构建的版本 3.5.2.companyName。重新部署完项目,发现连接数瞬间变小,由原来的到230多变为40多。

❗️ 注意:这里说一下题外话,项目中还有一个问题,这也是下一个要重构的目标。目前各个项目单独引用SpringBoot(版本可能还不太一样),数据源dbcp2连接池也就被分散到各个项目中,导致无法统一。
因为dbcp2的版本是在springboot中定义的。为什么无法统一?看下面示例
在maven项目,父项目中<dependencyManagement>定义一个jar包的版本1.0,
子项目A定义一个jar包版本2.0,子项目B依赖子项目A。
这个时候子项目B的jar包版本不是2.0,而是1.0。因为子项目A的2.0是会覆盖父项目中的版本,而子项目B只是依赖A,不会覆盖父项目中的1.0。

(二)GaussDB updatedTime NULL值问题

做数据库兼容时,还遇到一个问题:当程序中实体的 updatedTime 日期字段设置为null进行操作时,GaussDB数据库不支持自动更新。

GaussDB没有MySQL的这种 ON UPDATE功能,它认为你传的updatedTime 的值就是NULL,导致数据库NOT NULL 报错。

updatedTime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

对于这个问题,你可以像Oracle一样通过触发器实现,但是加触发器也需要加很多(还会影响性能)。

我之前有用过MyBatis-plugins的属性填充,那我是否可以为MyBatis自定义一个属性填充?了解MyBatis的我们知道,可以通过拦截器实现,搞定。

以下为属性填充的拦截器:

package com.zhht.mybatis.interceptor;import com.alibaba.fastjson.JSON;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Objects;
import java.util.Properties;/*** 拦截SQL,进行对象属性填充* 解决GaussDB数据库,表字段不支持 ON UPDATE CURRENT_TIMESTAMP*/
@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
)
public class MetaObjectInterceptor implements Interceptor {private static final Logger LOGGER = LoggerFactory.getLogger(MetaObjectInterceptor.class);private static final String TABLE_FIELD_CREATE_TIME = "createTime";private static final String TABLE_FIELD_CREATED_TIME = "createdTime";private static final String TABLE_FIELD_GMT_CREATE = "gmtCreate";private static final String TABLE_FIELD_UPDATE_TIME = "updateTime";private static final String TABLE_FIELD_UPDATED_TIME = "updatedTime";private static final String TABLE_FIELD_GMT_MODIFIED = "gmtModified";@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement ms = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];LOGGER.info("MetaObjectInterceptor - intercept [method: {}] start!", ms.getId());if (!Objects.isNull(parameter)) {SqlCommandType sqlCommandType = ms.getSqlCommandType();if (SqlCommandType.INSERT == sqlCommandType || SqlCommandType.UPDATE == sqlCommandType) {BoundSql boundSql = ms.getBoundSql(parameter);String beforeParameter = JSON.toJSONString(boundSql.getParameterObject());LOGGER.info("MetaObjectInterceptor - intercept [method: {}, before params: {}]", ms.getId(), beforeParameter);Class<?> clazz = parameter.getClass();if (clazz.getSuperclass().isInstance(Object.class)) {fillFields(parameter.getClass().getDeclaredFields(), parameter, sqlCommandType);} else {Class<?> superclass = clazz.getSuperclass();fillFields(superclass.getDeclaredFields(), parameter, sqlCommandType);}String afterParameter = JSON.toJSONString(boundSql.getParameterObject());LOGGER.info("MetaObjectInterceptor - intercept [method: {}, after params: {}]", ms.getId(), afterParameter);}}LOGGER.info("MetaObjectInterceptor - intercept [method: {}] successful!", ms.getId());return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}/*** 填充属性** @param declaredFields 参数字段* @param parameter      参数实体* @param sqlCommandType sql类型* @throws IllegalAccessException*/private void fillFields(Field[] declaredFields, Object parameter, SqlCommandType sqlCommandType) throws IllegalAccessException {for (Field field : declaredFields) {field.setAccessible(true);if (isNeedFill(sqlCommandType, field.getName(), field.get(parameter))) {doFill(field, parameter);}}}/*** 设置值** @param field 字段* @param parameter 参数实体*/private void doFill(Field field, Object parameter) throws IllegalAccessException {if (Date.class == field.getType()) {field.set(parameter, new Date());} else if (Timestamp.class == field.getType()) {field.set(parameter, new Timestamp(System.currentTimeMillis()));} else if (Long.class == field.getType()) {field.set(parameter, System.currentTimeMillis());} else {LOGGER.warn("MetaObjectInterceptor - doFill [type: {} is not support!]", field.getType().getName());}}/*** 判断字段是否需要填充* 逻辑:包含且非空** @param sqlCommandType sql类型* @param fieldName  字段名称* @param filedValue 字段值* @return*/private boolean isNeedFill(SqlCommandType sqlCommandType, String fieldName, Object filedValue) {if (SqlCommandType.INSERT.equals(sqlCommandType)) {// create和update字段,为了效率, 不使用list.containsif (TABLE_FIELD_CREATE_TIME.equals(fieldName)|| TABLE_FIELD_CREATED_TIME.equals(fieldName)|| TABLE_FIELD_GMT_CREATE.equals(fieldName)|| TABLE_FIELD_UPDATE_TIME.equals(fieldName)|| TABLE_FIELD_UPDATED_TIME.equals(fieldName)|| TABLE_FIELD_GMT_MODIFIED.equals(fieldName)) {return Objects.isNull(filedValue);}} else if (SqlCommandType.UPDATE.equals(sqlCommandType)) {// 只考虑update字段,时时更新if (TABLE_FIELD_UPDATE_TIME.equals(fieldName)|| TABLE_FIELD_UPDATED_TIME.equals(fieldName)|| TABLE_FIELD_GMT_MODIFIED.equals(fieldName)) {return true;}}return false;}}/*** MyBatis自动配置** @author qiuxianbao* @date 2023/10/31*/
@Configuration
@EnableConfigurationProperties
public class MyBatisAutoConfiguration {/*** 支持多种数据库产品* @return*/@Bean@ConditionalOnMissingBeanpublic DatabaseIdProvider getDatabaseIdProvider() {VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();databaseIdProvider.setProperties(DatabaseVendorLoadUtils.load());return databaseIdProvider;}/*** 添加属性填充拦截器* @return*/@Beanpublic Interceptor metaObjectInterceptor() {return new MetaObjectInterceptor();}}

至此,项目初步完成改造,后续观察,封版提测。
至于源码的事儿,敬请关注看图说话专栏或者系列文章~


五、参考资料

Github dynamic-datasource


写在后面

  如果本文内容对您有价值或者有启发的话,欢迎点赞、关注、评论和转发。您的反馈和陪伴将促进我们共同进步和成长。


系列文章

【连接池】-从源码到适配(上),你遇到过数据库连接池的问题吗?This connection has been closed
【源码】-MyBatis-如何系统地看源码

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

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

相关文章

公司创建百度百科需要哪些内容?

一个公司或是一个品牌想要让自己更有身份&#xff0c;更有知名度&#xff0c;更有含金量&#xff0c;百度百科词条是必不可少的。通过百度百科展示公司的详细信息&#xff0c;有助于增强用户对公司的信任感&#xff0c;提高企业形象。通过百度百科展示公司的发展历程、领导团队…

ASP.Net实现汽车添加查询(三层架构,含照片)

演示功能&#xff1a; 点击启动生成页面 点击搜索模糊查询 点击添加跳转新界面 此处设置文本框多行 点击Button添加 步骤&#xff1a; 1、建文件 下图是三层架构列表&#xff0c;Models里面有模拟数据库中列的类&#xff0c;DAL中有DBHelper和service,BLL中有BllManager文件…

TiDB 7.5 LTS 发版丨提升规模化场景下关键应用的稳定性和成本的灵活性

互联网时代&#xff0c;数据的迅猛增长给数据库带来了可扩展性的挑战&#xff0c;Gen AI 带来的数据暴增更加剧了这种挑战。传统的数据分片已经不能承载新时代数据暴增的需求&#xff0c;更简单且具有前瞻性的方法则是采用原生分布式数据库来解决扩展性问题。在这种规模化场景的…

SpringValidation自定义注解以及分组校验

SpringValidation的参数校验使用可参考&#xff1a;【SpringMVC应用篇】Spring Validation 参数校验-CSDN博客 目录 1. 引入依赖 2. 自定义注解校验 2.1 创建Validation类 2.2 创建注解对象 2.3 使用注解 3. 分组校验 3.1 实体类内部定义接口 3.2 在参数上指定分组 1. …

CISSP 第1章:实现安全治理的原则和策略

作者&#xff1a;nothinghappend 链接&#xff1a;https://zhuanlan.zhihu.com/p/669881930 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 CIA CIA 三性&#xff1a; 机密性&#xff1a;和数据泄露有关。完整性…

存算分离降本增效,StarRocks 助力聚水潭 SaaS 业务服务化升级

作者&#xff1a;聚水潭数据研发负责人 溪竹 聚水潭是中国领先的 SaaS 软件服务商&#xff0c;核心产品是电商 ERP&#xff0c;协同350余家电商平台&#xff0c;为商家提供综合的信息化、数字化解决方案。公司是偏线下商家侧的 toB 服务商&#xff0c;员工人数超过3500&#xf…

C++初阶------------------入门C++

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

带大家做一个,易上手的家常糖醋白菜

准备 如果是大白菜就一个 小白菜就要两个 因为白菜炒完之后会变少 将白菜叶剥开每叶分成三个小块 整个剥完之后 放入盆中清洗干净 调一个糖醋汁 一勺料酒 两勺生抽 三勺白砂糖 四勺香醋 起锅烧油 放两个干辣椒 辣椒炒一下 然后倒入白菜 翻炒直到油全部融入白菜 然后倒入…

Windows本地如何部署Apache服务器搭配内网穿透实现无公网IP远程访问?

文章目录 前言1.Apache服务安装配置1.1 进入官网下载安装包1.2 Apache服务配置 2.安装cpolar内网穿透2.1 注册cpolar账号2.2 下载cpolar客户端 3. 获取远程桌面公网地址3.1 登录cpolar web ui管理界面3.2 创建公网地址 4. 固定公网地址 前言 Apache作为全球使用较高的Web服务器…

自然语言处理1——探索自然语言处理的基础 - Python入门篇

目录 写在开头1. 介绍自然语言处理的基本概念1.1 NLP的核心目标1.2 常见的NLP任务1.3 应用场景详细介绍1.3.1 医疗保健1.3.2 金融领域1.3.3 教育领域1.3.4 社交媒体分析 2. Python中常用的自然语言处理库简介2.1 NLTK (Natural Language Toolkit)2.2 Spacy2.3 Transformers2.4 …

张量操作与线性回归

一、张量的操作&#xff1a;拼接、切分、索引和变换 &#xff08;1&#xff09;张量拼接与切分 1.1 torch.cat() 功能&#xff1a;将张量按维度dim进行拼接 • tensors: 张量序列 • dim : 要拼接的维度 torch.cat(tensors, dim0, outNone)函数用于沿着指定维度dim将多个张量…

ES6之生成器(Generator)

✨ 专栏介绍 在现代Web开发中&#xff0c;JavaScript已经成为了不可或缺的一部分。它不仅可以为网页增加交互性和动态性&#xff0c;还可以在后端开发中使用Node.js构建高效的服务器端应用程序。作为一种灵活且易学的脚本语言&#xff0c;JavaScript具有广泛的应用场景&#x…

复兴计划01-lc06

StringBuilder和StringBuffer的区别 1. StringBuffer和StringBuilder都是用于字符串动态拼接,但是StringBuffer拼接的函数方法的实现中用了synchornized上锁&#xff0c;效率较低&#xff0c;不过可以用于多线程以此来维护线程安全&#xff1b;相比之下&#xff0c;StringBuil…

理解SQL中not in 与null值的真实含义

A not in B的原理是拿A表值与B表值做是否不等的比较, 也就是a ! b. 在sql中, null是缺失未知值而不是空值。 当你判断任意值a ! null时, 官方说, “You cannot use arithmetic comparison operators such as , <, or <> to test for NULL”, 任何与null值的对比都将返…

Postman使用

Postman使用 Pre-request Script 参考&#xff1a; Scripting in Postman 可以请求、集合或文件夹中添加Pre-request Script&#xff0c;在请求运行之前执行JavaScript 如设置变量值、参数、Header和正文数据&#xff0c;也可以使用Pre-request Script来调试代码&#xff0…

jupyter notebook打开其他盘的文件

jupyter notebook打开其他盘文件 打开jupyter notebook打开terminal输入&#xff1a;jupyter-notebook 路径打开你想打开的工程的文件 打开jupyter notebook 打开terminal 输入&#xff1a;jupyter-notebook 路径 打开你想打开的工程的文件

TDD-LTE 寻呼流程

目录 1. 寻呼成功流程 1.1 空闲态寻呼 1.2 连接态寻呼 2. 寻呼失败流程 2.1 Paging消息不可达 2.2 RRC建立失败 2.3 eNodeB未上发Initial UE message或达到超时 1. 寻呼成功流程 1.1 空闲态寻呼 寻呼成功&#xff1a;MME发起寻呼&#xff08;S1 接口发送Paing 消息&…

【docker实战】安装tomcat并连接mysql数据库

本节用docker来安装tomcat&#xff0c;并用这个tomcat连接我们上一节安装好的mysql数据库 一、拉取镜像 我们安装8.5.69版本 先搜索一下 [rootlocalhost ~]# docker search tomcat NAME DESCRIPTION …

SpringBoot快速集成多数据源(自动版)

有些人因为看见所以相信&#xff0c;有些人因为相信所以看见 有目录&#xff0c;不迷路 前期准备实现演示参考 最近研究了一下多数据源&#xff0c;这篇博客讲的是简单模式&#xff0c;下篇博客预计写自动切换模式 前期准备 本篇博客基于SpringBoot整合MyBatis-plus&#xff0…

【办公软件】Excel双坐标轴图表

在工作中整理测试数据&#xff0c;往往需要一个图表展示两个差异较大的指标。比如共有三个数据&#xff0c;其中两个是要进行对比的温度值&#xff0c;另一个指标是两个温度的差值&#xff0c;这个差值可能很小。 举个实际的例子&#xff1a;数据如下所示&#xff0c;NTC检测温…