【金仓数据库征文】Java 应用接入金仓数据库:从驱动、连接池到存储过程调试的踩坑实录
前言一个要原样跑起来的新项目今年年初我们组接了个内部交易中台的重构活儿。架构早就定了Spring Boot MyBatis Druid数据库这次要换成金仓数据库。组里大部分人之前都是写 Oracle 上的应用对金仓不算熟。leader 把数据库接入这块甩给了我原话是“先把连接打通存储过程跑顺别出幺蛾子。”我当时答应得挺痛快心想不就换个数据库嘛。真上手才知道打通这俩字背后全是坑。这篇就把我在驱动、连接池、MyBatis、存储过程调试这几处栽过的跟头记一下给后来人提个醒。目录前言一个要原样跑起来的新项目痛点看似只是换个数据库实则处处暗礁方案JDBC Druid MyBatis KStudio 调试踩坑实录坑一JDBC 驱动 jar 跟 JDK 版本没对上坑二Druid 连接池假活连接泄漏坑三MyBatis 里的 Oracle 方言 SQL坑四存储过程跑不对靠 KStudio 单步揪出 bug关键优化大结果集的 fetchsize落地效果写在最后痛点看似只是换个数据库实则处处暗礁动手前我盘了一下麻烦主要在三处。一是驱动。金仓的 JDBC 驱动按 JDK 版本分了好几个 jar跟平时用 MySQL 那种一个 jar 走天下完全不一样。版本没对上启动直接类加载失败连数据库的影儿都摸不着。二是连接池。原来 Druid 配的是针对另一套库的参数验证语句、超时这些照搬过来连接池看着是活的其实是假活线上隔三差五就报错排查起来特别费劲。三是存储过程。业务里有一批 PL/SQL 存储过程迁过来逻辑跑不对。光盯着代码看看不出来毛病得靠调试一步步过。方案JDBC Druid MyBatis KStudio 调试技术栈没得选就是那套。JDBC 用金仓原生驱动连接池继续 DruidORM 还是 MyBatis存储过程调试用金仓自带的 KStudio。整体思路其实挺简单先把连接跑通再调连接池参数接着把 MyBatis 里的方言 SQL 适配掉最后用 KStudio 把存储过程的 bug 一个个揪出来。听起来就四步实际每一步都够喝一壶。我画了张图照着这个顺序往下啃踩坑实录坑一JDBC 驱动 jar 跟 JDK 版本没对上第一个坑来得特别快。周一早上我把驱动 jar 往项目里一丢启动直接报ClassNotFoundException: com.kingbase8.Driver。第一反应是 jar 没引进来。检查了半天 pom 和 lib 目录jar 明明就躺在那儿。我又怀疑是 IDE 缓存清缓存重启还是一样。卡了快一个小时后来翻了金仓的文档才反应过来–它的 JDBC 驱动是按 JDK 版本分的。我项目跑 JDK8结果手滑丢进去的是 jre6 那个。版本不匹配类加载直接挂怪不得报找不到类。驱动 jar 文件对应 JDK 版本kingbase8-9.0.0.jre6.jarJDK 1.6kingbase8-9.0.0.jre7.jarJDK 1.7kingbase8-9.0.0.jarJDK 1.8这些 jar 都在数据库安装目录的Interface/jdbc下。别想当然随便抓一个就塞进去先看清楚自己项目的 JDK 版本。换回对应版本连接立马通了// 金仓 JDBC 连接注意驱动类名和连接串前缀都跟 Oracle/MySQL 不一样Class.forName(com.kingbase8.Driver);Stringurljdbc:kingbase8://10.0.0.12:54321/trade?currentSchematrade;try(ConnectionconnDriverManager.getConnection(url,appuser,pwd123)){System.out.println(连通了版本: conn.getMetaData().getDatabaseProductVersion());}这种坑说实话挺低级但越是低级的坑越容易因为这还能有错的心态忽略掉。坑二Druid 连接池假活连接泄漏这个坑折磨了我小两天。应用本地跑起来一切正常压测也没事。可一上预发隔三差五就报无法获取连接。看日志连接池里的连接全被占满了。重启就好过会儿又犯跟闹钟似的。我第一反应是连接没关。把代码里所有getConnection翻了一遍都老老实实try-with-resources了没漏。这就卡住了–代码没问题连接池却满了邪门。后来盯 Druid 的监控页面才看出门道。连接池里有大量连接处于活动状态但实际上对数据库已经失效了。问题出在validationQuery上。原来这套配置是从别的库照搬的验证语句写的是那套库的写法到金仓这儿语义对不上等于没验证。连接早断了池子还以为它活着业务一拿就是个死连接占着茅坑不拉屎很快就被耗光了。排查这玩意儿我走了弯路后来总结了个流程照着走能少绕不少弯子光看池子不行我还直接去数据库里数了一把活动会话确认是不是真的有一堆僵死连接赖在那儿-- 直连金仓查当前活动会话看有没有一堆连接赖着不动SELECTpid,usename,application_name,client_addr,state,query_start,state_change,LEFT(query,60)ASqueryFROMsys_stat_activityWHEREdatnametradeORDERBYquery_startDESC;一查果然一堆state idle的连接state_change时间还是十几分钟前的典型的僵死连接。证据确凿改配置就有了方向。把验证语句换成金仓能识别的再配上testWhileIdle问题立马消停spring:datasource:type:com.alibaba.druid.pool.DruidDataSourcedriver-class-name:com.kingbase8.Driverurl:jdbc:kingbase8://10.0.0.12:54321/trade?currentSchematradeusername:appuserpassword:pwd123druid:# 关键验证语句得是金仓认的别照搬别家的validation-query:SELECT 1test-while-idle:truetest-on-borrow:falsetest-on-return:false# 空闲连接存活检测间隔别让死连接赖在池子里time-between-eviction-runs-millis:60000min-evictable-idle-time-millis:300000max-active:50min-idle:5顺带说一句金仓兼容模式下SELECT 1 FROM DUAL也能跑但直接SELECT 1更省事没必要绕那一下。坑三MyBatis 里的 Oracle 方言 SQLMyBatis 接入本身不难。驱动类名、URL 换掉配置基本就齐了。金仓对 MyBatis 3.x 的几个版本都做过适配验证这块挺省心。# jdbc.properties jdbc.driverClassNamecom.kingbase8.Driver jdbc.urljdbc:kingbase8://10.0.0.12:54321/trade?currentSchematrade jdbc.usernameappuser jdbc.passwordpwd123!-- mybatis config.xml --environmentsdefaultdevelopmentenvironmentiddevelopmenttransactionManagertypeJDBC/dataSourcetypePOOLEDpropertynamedrivervalue${jdbc.driverClassName}/propertynameurlvalue${jdbc.url}/propertynameusernamevalue${jdbc.username}/propertynamepasswordvalue${jdbc.password}//dataSource/environment/environments真正的坑不在配置在 Mapper 的 SQL 里。原来有一段拿主键的逻辑直接调了 Oracle 的序列!-- 原写法直接调序列 nextvalOracle 上没毛病 --selectidnextOrderIdresultTypelongSELECT seq_order.nextval FROM DUAL/select金仓兼容模式下序列和 DUAL 都支持这段能跑。但我心里不踏实–这种方言写法留在代码里以后就是个隐患指不定哪天换个驱动版本、换个模式就炸了。索性趁这次接入把拿主键的方式统一换掉。新表直接用 IDENTITY 列插入时用useGeneratedKeys一把拿回主键干净利落跟序列彻底说再见!-- 改造后用 RETURNING 直接拿自增主键跟序列说再见 --insertidinsertOrderparameterTypeOrderuseGeneratedKeystruekeyPropertyidINSERT INTO orders(order_no, amount, create_time) VALUES(#{orderNo}, #{amount}, #{createTime})/insert我的建议是接入的时候顺手把这类方言写法扫一遍能换的就换掉别图省事留着。当时省的事以后都得还。坑四存储过程跑不对靠 KStudio 单步揪出 bug前面几个坑好歹是应用层的看得见摸得着。存储过程这个就恶心了。有个算订单优惠金额的存储过程迁过来以后个别订单算出来的结果跟预期差几分钱。光盯着代码看逻辑好像没毛病变量赋值、循环、条件分支都对得上。瞪了半天眼睛没看出问题。没办法上 KStudio 调试。这工具是金仓自带的图形界面能对 PL/SQL 函数和存储过程断点单步调试比光看代码硬啃强太多。操作不复杂我后来画了张图照着点就行具体操作就是在对象树里找到那个函数右键选调试把入参填进去点开始调试。然后就是工具栏那几个按钮–单步跳过一步步往下走单步跳入钻进嵌套调用的函数里。变量值实时显示在旁边哪一步算错了看得一清二楚。调了两轮bug 现形了。是中间一步SELECT ... INTO取折扣率的时候没处理空值。遇到某类没配折扣的商品INTO取回来是空后续乘法一算结果就飘了。代码大概长这样-- 出问题的存储过程片段INTO 没兜住空值CREATEORREPLACEPROCEDUREcalc_order_amount(p_order_idINNUMERIC)ASv_discountNUMERIC;v_amountNUMERIC;BEGIN-- 这里某些商品没配折扣discount 取回来是 NULLSELECTdiscount_rateINTOv_discountFROMproduct_ruleWHEREproduct_id(SELECTproduct_idFROMordersWHEREidp_order_id);-- NULL 参与乘法结果直接飘SELECTtotal_amountINTOv_amountFROMordersWHEREidp_order_id;UPDATEordersSETpay_amountv_amount*v_discountWHEREidp_order_id;END;/加个兜底就好了没配折扣就当不打折折扣率按 1.0 算-- 修复NVL 兜底没折扣就当 1.0不打折SELECTNVL(discount_rate,1.0)INTOv_discountFROMproduct_ruleWHEREproduct_id(SELECTproduct_idFROMordersWHEREidp_order_id);这种 bug不调试光看代码真的很难发现–逻辑流程是对的错的是数据。NULL 这玩意儿在关系库里就是个坑参与运算默默给你搞出个空还不报错。KStudio 那个单步调试在这儿救了我半条命。关键优化大结果集的 fetchsize接入跑通之后又冒出一个性能问题。有个导出接口查几十万行数据跑着跑着内存就飙上去了时不时 OOM。查下来是 fetchsize 没设。默认情况下 JDBC 驱动会一次性把结果集全捞到客户端内存里数据量一大直接撑爆。设上setFetchSize让驱动按需分批拉内存立马就稳了。另外金仓有个enable_autocommit_fetch参数在自动提交模式下控制是全量返回还是按 fetchsize 按需返回。大结果集场景值得留意一下默认行为不一定符合你预期// 大结果集查询务必设 fetchsize别让驱动一把全捞进内存publicvoidexportOrders(OutputStreamout)throwsSQLException,IOException{StringsqlSELECT id, order_no, amount, create_time FROM orders WHERE create_time ?;try(ConnectionconndataSource.getConnection();PreparedStatementpsconn.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY)){// 自动提交 按需获取配合 fetchsize 省内存conn.setAutoCommit(true);ps.setFetchSize(500);// 每次只拉 500 行ps.setTimestamp(1,Timestamp.valueOf(2025-01-01 00:00:00));try(ResultSetrsps.executeQuery()){while(rs.next()){writeRow(out,rs);// 流式写出内存占用恒定}}}}落地效果踩完这些坑应用接入稳定运行预发压测也过了。前后对比大致这样指标接入初期踩坑中调优后启动成功率偶发类加载失败100%连接池稳定性间歇性无法获取连接连续压测无泄漏大结果集导出内存峰值OOM 频发稳定在 300MB 以内存储过程排查耗时肉眼盯代码半天起步KStudio 单步分钟级定位写在最后这趟接入干下来我最大的感受是换数据库这事难的不是能连上而是连得稳、跑得对。驱动版本、连接池验证语句、方言 SQL、存储过程空值这几个坑单拎出来都不大但凑一块儿够你忙活好几天。而且它们有个共同特点–本地测试的时候基本不冒头非得上预发、压一压、跑跑真实数据才现形。这也是我为什么特别强调要尽早把应用丢到预发环境上去跑别在本地自嗨。几条心得掏心窝子说一下。驱动 jar 一定对照 JDK 版本别手滑这步错了后面全白搭。连接池的validationQuery别照搬得是目标库认的语句配合testWhileIdle才管用。MyBatis 里的方言写法趁接入的机会能清就清别留给以后当定时炸弹。存储过程有 bug 别干瞪眼KStudio 单步调试比肉眼强一百倍变量值一目了然。大结果集别忘了 fetchsize这个最容易忽略也最容易 OOM。金仓数据库在 JDBC、MyBatis 这些主流开发框架上的适配做得挺到位驱动、连接、ORM 基本都能平滑接上KStudio 的调试功能也确实好用省了我不少排查时间。但适配归适配该抠的细节一个都不能少。开发接入这活儿慢点没关系把每个环节都踩实了后面上线才踏实。