news 2026/6/10 11:19:36

修复seata的HikariCP中加载驱动程序类的问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
修复seata的HikariCP中加载驱动程序类的问题

文章目录

  • 引言
  • 问题引入
  • 问题分析
    • 在druid时
    • 在Hikari时
  • 问题解决
  • 总结

引言

大家好!今天我们一起探讨一下一个在seata 2.5.0版本修复的小bug,如标题所言,是和数据库连接池有关的驱动加载有关的问题,让我们一起来看看吧。

问题引入

在之前的代码中,如果连接池使用的是druid,那就完全没有问题,如果使用的是Hikari,就会报错。

示例seata服务端配置如下:

server:port:8091spring:application:name:seata-servermain:web-application-type:nonelogging:config:classpath:logback-spring.xmlfile:path:${log.home:${user.home}/logs/seata}seata:config:# support: nacos, consul, apollo, zk, etcd3type:fileregistry:# support: nacos, eureka, redis, zk, consul, etcd3, sofatype:filestore:# support: file 、 db 、 redis 、 raftmode:dbsession:mode:dblock:mode:dbdb:# If use druid, it is OK. If change to hikari, and then threw an error.datasource:hikaridb-type:mysqldriver-class-name:com.mysql.cj.jdbc.Driverurl:jdbc:mysql://localhost:3396/db_seata?useUnicode=true&characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=trueuser:admin_seatapassword:123456min-conn:10max-conn:100global-table:global_tablebranch-table:branch_tablelock-table:lock_tabledistributed-lock-table:distributed_lockvgroup-table:vgroup_tablequery-limit:1000max-wait:5000druid:time-between-eviction-runs-millis:120000min-evictable-idle-time-millis:300000test-while-idle:truetest-on-borrow:falsekeep-alive:falsehikari:idle-timeout:600000keepalive-time:120000max-lifetime:1800000validation-timeout:5000# server:# service-port: 8091 #If not configured, the default is '${server.port} + 1000'

如果我使用它datasource: hikari,则会抛出错误

Caused by: java.lang.RuntimeException: Failed to load driver class com.mysql.cj.jdbc.Driver in either of HikariConfig class loader or Thread context classloader

从报错很容易看出来就是连接池初始化时无法加载MYSQL的驱动类。

问题分析

既然issue里面说到使用druid是正常的,而使用HikariCP则不行。肯定这两个进行加载驱动时有所不同。

在druid时

在seata的DruidDataSourceProvider可以看到下面的代码,Druid 正确使用了这个类加载器:setDriverClassLoader(getDriverClassLoader()),所以druid是正常加载驱动的

DruidDataSourceds=newDruidDataSource();ds.setDriverClassName(getDriverClassName());//这里明显加载了数据员的驱动类ds.setDriverClassLoader(getDriverClassLoader());ds.setUrl(getUrl());ds.setUsername(getUser());ds.setPassword(getPassword());ds.setInitialSize(getMinConn());ds.setMaxActive(getMaxConn());ds.setMinIdle(getMinConn());ds.setMaxWait(getMaxWait());

在Hikari时

在seata的HikariDataSourceProvider可以看到下面的代码,可以看到,并没有加载到驱动, 但 HikariConfig 类没有setDriverClassLoader方法,所以 Hikari 无法使用专门的类加载器,导致 Hikari 使用默认类加载器无法找到 MySQL 驱动

HikariConfigconfig=newHikariConfig(properties);config.setDriverClassName(getDriverClassName());config.setJdbcUrl(getUrl());config.setUsername(getUser());config.setPassword(getPassword());

问题解决

经过分析我们可以知道,由于Hikari的设计局限性,我们并不能将驱动直接注册在config里面,难道我们就不能解决这个问题了吗?当然不是,解决方法总是有的。

一般我们要知道,HikariCP 在设置driverClassName时,会尝试通过以下两种 ClassLoader 加载驱动类:

  1. HikariConfig 所在的 ClassLoader
  2. 当前线程的上下文 ClassLoader(Thread Context ClassLoader)

如果两者都找不到com.mysql.cj.jdbc.Driver类,就会抛出这个异常

清楚这个点之后,我们可以尝试解决这个问题。
在seata的所有provider的抽象父类中提供了获取目标驱动的方法

protectedClassLoadergetDriverClassLoader(){returnDRIVER_LOADERS.getOrDefault(getDriverClassName(),this.getClass().getClassLoader());}

我们可以采用一种“保护现场、恢复现场”的典型设计模式,在有关多线程场景经常使用。

我们先通过抽象父类获得目标驱动与驱动名,然后获取当前线程上下文原驱动,再将当前线程的驱动设置成我们第一个获取到的目标驱动并显式注册,确保类加载发生在正确的 ClassLoader 上下文中,最后finally还原线程驱动即可。

seata通过显式加载类和注册驱动到DriverManager,后续驱动实例都会存在在DriverManagergetConnection(...)就能用这个实例

HikariConfigconfig=newHikariConfig(properties);// Get the correct class loaderClassLoaderdriverClassLoader=getDriverClassLoader();StringdriverClassName=getDriverClassName();// Set driver class name in the correct class loader contextClassLoaderoriginalClassLoader=Thread.currentThread().getContextClassLoader();try{Thread.currentThread().setContextClassLoader(driverClassLoader);// 1. Explicitly load and register the drivertry{Class<?>driverClass=Class.forName(driverClassName,true,driverClassLoader);Driverdriver=(Driver)driverClass.newInstance();DriverManager.registerDriver(newDriverWrapper(driver));}catch(Exceptione){logger.warn("Failed to explicitly register driver {}",driverClassName,e);}// 2. Set configurationconfig.setDriverClassName(driverClassName);config.setJdbcUrl(getUrl());config.setUsername(getUser());config.setPassword(getPassword());}finally{Thread.currentThread().setContextClassLoader(originalClassLoader);}

总结

  1. 绕过 HikariCP 的自动类加载机制
    Seata 不依赖 HikariCP 自己去加载driverClassName,而是提前手动加载并注册 Driver
  2. 确保类加载发生在正确的 ClassLoader 上下文中
    通过Thread.currentThread().setContextClassLoader(driverClassLoader),使得后续config.setDriverClassName(...)调用时,HikariCP 内部使用的上下文 ClassLoader 正是能加载驱动的那个。
  3. DriverWrapper 保证后续 getConnection() 使用正确驱动
    即使 DriverManager 中有多个 Driver,包装后的 Driver 也能确保使用我们显式加载的那个实例,避免 ClassLoader 混乱

还有其他问题欢迎评论区友好讨论❤️

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 23:28:37

MCU+AT到OpenCPU:嵌入式通信技术迭代的必然性(完结篇)

上一篇在充分理解了OpenCPU的技术优势与架构潜力后&#xff0c;一个现实而关键的问题摆在工程师及企业面前&#xff1a;如何在实际工程中&#xff0c;将现有的MCUAT模组架构&#xff0c;安全、平滑地演进至OpenCPU平台&#xff1f;第六章&#xff1a;迁移与融合策略——从MCUAT…

作者头像 李华
网站建设 2026/6/10 14:44:53

【更新至2024年】1996-2024年各省公路里程数据

【更新至2024年】1996-2024年各省公路里程数据 1、时间&#xff1a;1996-2024年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;公路里程&#xff08;万公里&#xff09; 4、范围&#xff1a;31省 5、指标解释&#xff1a;公路里程指报告期末公路的实际长度…

作者头像 李华
网站建设 2026/6/10 10:54:34

力扣数据库——第N高的薪水

第N高的薪水https://leetcode.cn/problems/nth-highest-salary/ 一 题目 表: Employee Column NameTypeidintsalaryint id 是该表的主键&#xff08;列中的值互不相同&#xff09;。该表的每一行都包含有关员工工资的信息。编写一个解决方案查询 Employee 表中第 n 高的不…

作者头像 李华
网站建设 2026/6/10 10:55:38

[特殊字符]_内存管理深度解析:如何避免GC导致的性能陷阱[20260107172234]

作为一名经历过无数性能调优案例的工程师&#xff0c;我深知内存管理对Web应用性能的影响有多大。在最近的一个项目中&#xff0c;我们遇到了一个棘手的性能问题&#xff1a;系统在高并发下会出现周期性的延迟飙升&#xff0c;经过深入分析&#xff0c;发现问题根源竟然是垃圾回…

作者头像 李华
网站建设 2026/6/10 10:58:34

在NEAR Protocol中获取用户账户的完整指南

近年来,随着区块链技术的发展,NEAR Protocol作为一个高效的智能合约平台,吸引了越来越多的开发者和用户。今天,我们将探讨如何在NEAR Protocol中获取用户账户列表,这对开发者来说是一个常见的需求,尤其是在进行用户数据分析或开发用户相关的应用时。 问题背景 在NEAR P…

作者头像 李华