【连接池】-从源码到适配(下),使用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.hqwc.cn/news/307373.html

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

相关文章

ubuntu20部署Bringing-Old-Photos-Back-to-Life

环境准备&#xff1a; ubuntu20.04 Python 3.8.10 首先将微软的「Bringing-Old-Photos-Back-to-Life」库 clone 到本地&#xff1a; git clone https://github.com/microsoft/Bringing-Old-Photos-Back-to-Life.git cd Face_Enhancement/models/networks/ git clone https:/…

在Ant Design Vue(v1.7.8)a-table组件中实现余额自动计算

一、目标 在Ant Design Vue <a-table>表格中实现余额自动计算&#xff0c;公式为&#xff1a;剩余量 库存量 - 消耗量 二、二次开发基础 现有一个使用Ant Design Vue <a-table>表格的开源项目&#xff0c;原始表格有“消耗量”列&#xff0c;且带输入框&#xf…

【Maven】<scope>provided</scope>

在Maven中&#xff0c;“provided”是一个常用的依赖范围&#xff0c;它表示某个依赖项在编译和测试阶段是必需的&#xff0c;但在运行时则由外部环境提供&#xff0c;不需要包含在最终的项目包中。下面是对Maven scope “provided”的详细解释&#xff1a; 编译和测试阶段可用…

自定义docker镜像,ubuntu安装命令并导出

文章目录 问题现象解决步骤相关命令详细介绍docker save 与 docker loaddocker import 与 docker exportdocker commit 问题现象 我们的通讯服务&#xff0c;需要监测前端设备的在线情况&#xff08;是否在线、丢包率、延迟等&#xff09;&#xff0c;使用ping命令去实现此功能…

GBASE南大通用-Base 8a集群同步工具超详细指南 手把手带您玩转灾备

1工具介绍 GBase 8a集群间同步工具是基于集群的底层二进制数据同步的工具&#xff0c;其同步的对象是库内的数据&#xff0c;通过解析、对比智能索引中摘要信息的变化&#xff0c;来实现同构集群的同构表的数据复制功能&#xff0c;目前已经支持图形化操作&#xff0c;主要功能…

电脑报错“kernelbase.dll”文件缺失,软件游戏无法启动的解决方法

很多小伙伴留言说&#xff0c;每次自己要游戏或软件的时候&#xff0c;电脑就会弹出报错框&#xff0c;不知道应该怎么办&#xff1f; 其实&#xff0c;Windows报错提示已经说明了&#xff0c;程序找不到名为“kernelbase.dll”的文件&#xff0c;需要重新安装修复这个问题。 …

熊猫目标检测数据集VOC格式1200张

熊猫是中国的国宝&#xff0c;也是世界上最受人喜爱的动物之一。熊猫以其独特的外貌和与生俱来的文化象征意义而闻名于世。它们是一种大型的食草动物&#xff0c;主要分布在中国中部地区的竹林和高山地带。 熊猫的身形圆润笨拙&#xff0c;黑白分明&#xff0c;拥有圆润的脸庞…

Apipost一键压测参数化功能详解

最近更新中Apipost对UI页面进行了一些调整&#xff0c;另外一键压测功能支持参数化&#xff01;本篇文章将详细介绍这些改动&#xff01; API调试页面的细节改动 在请求区填入请求参数或脚本时会有相应的标识 如在Query中填入多个参数时上方会展示数量 在预、后执行脚本中写…

分享一个qml开发的Dialog

一、效果预览 二、源码分享 PopwindowWidget.qml import QtQuick import QtQuick.Controls import QtQuick.LayoutsApplicationWindow {id:selfwidth: 470height: 250visible: falsecolor: "#00000000"flags: Qt.Tool | Qt.FramelessWindowHint|Qt.MSWindowsFixedSiz…

REHL_用yum/dnf管理软件包

使用rpm安装包时经常会遇到一个问题就是包依赖&#xff0c;如下所示。 [rootRedHat ~]# rpm -ivh /mnt/AppStream/Packages/httpd-2.4.37-41.moduleel8.5.011772c8e0c271.x86_64.rpm 警告&#xff1a;/mnt/AppStream/Packages/httpd-2.4.37-41.moduleel8.5.011772c8e0c271.x86…

机器学习中的偏差和方差

评估机器学习模型的方法有很多种。我们可以使用MSE&#xff08;均方误差&#xff09;进行回归&#xff1b;精确度&#xff0c;召回率和ROC&#xff08;特征接收器&#xff09;用于分类问题。以类似的方式&#xff0c;偏差和方差帮助我们进行参数调整&#xff0c;并在几个构建的…

Linux 内核学习笔记: hlist 的理解

前言 最近阅读 Linux 内核时&#xff0c;遇到了 hlist&#xff0c;这个 hlist 用起来像是普通的链表&#xff0c;但是为何使用 hlist&#xff0c;hlist 是怎么工作的&#xff1f; 相关代码 hlist_add_head(&clk->clks_node, &core->clks); /*** clk_core_link_…