简介这份PPT资料面向正在推进应用系统国产化改造的开发与运维人员聚焦信创环境下的适配落地问题系统梳理了国产数据库与国产Web应用容器的迁移改造经验。内容涵盖达梦、瀚高数据库的适配案例以及东方通、宝兰德等国产中间件的适配实践并给出数据库与组件的映射对比指南帮助读者快速判断原环境组件对应的国产替代方案。资源包共1个pptx文件约6.6MB以图文并茂的幻灯片形式呈现便于直接用于内部培训或方案汇报。资料中具体展开达梦库对MySQL的兼容适配包括UUID函数替代、存储过程格式差异、insert ignore改写为merge into、group by语法规范及关键字冲突处理等细节同时介绍瀚高库自定义同名函数、数据类型强校验与无dual虚表等注意事项并涉及DTS数据迁移工具与分页拦截器注入等应用改造要点。目前已有1709人学习下载适合需要落地信创适配、排查兼容性问题的技术人员参考借鉴。1. 应用系统国产化改造从“能跑”到“敢上生产”的信创适配实战复盘去年接手一个内部业务系统的国产化改造原环境是 x86 某商业数据库 某商业中间件目标是把数据库换成国产库、Web 容器换成国产容器应用代码尽量不动。听起来像“换个驱动、改个连接串”就完事实际做下来光 SQL 兼容性和容器类加载就折腾了三周。这篇文章不讲政策背景只讲我踩过的坑和验证过的路径国产数据库适配怎么做、国产 Web 容器适配怎么做、哪些参数必须调、哪些地方一定会翻车。适合正在做信创适配的后端和运维同学尤其是那种“领导说下个月要上线”的节奏。全文按“先搞清楚适配面 → 数据库适配 → 容器适配 → 避坑 → 验证收尾”推进每一步都给可抄的命令和配置。2. 适配面拆解先搞清楚哪些东西必须换、哪些可以不动国产化改造最容易犯的错是一上来就全量替换结果应用起不来排查方向都找不到。我的做法是先画一张“依赖清单”把系统拆成四层操作系统层、数据库层、Web 容器层、应用代码层。操作系统层通常由运维统一处理应用侧主要关注后三层。数据库层要区分“连接方式”和“SQL 方言”两件事——连接方式换驱动就行SQL 方言才是真正的工作量。Web 容器层要区分“部署形态”和“类加载机制”前者影响启动脚本后者影响框架能不能跑起来。2.1 依赖清单怎么列一张表锁定改造范围我一般会先用脚本把应用的所有外部依赖扫一遍重点看 JDBC 驱动、连接池、ORM 框架、Servlet 容器相关依赖。下面这个 bash 脚本是我常用的扫的是 Maven 项目的依赖树输出里过滤出和数据库、容器相关的条目#!/bin/bash # 扫描 Maven 项目依赖过滤数据库和容器相关组件 # 用法./scan_deps.sh /path/to/project PROJECT_DIR$1 cd $PROJECT_DIR || exit 1 # 输出完整依赖树到临时文件 mvn dependency:tree -DoutputFile/tmp/dep_tree.txt -DoutputTypetext -q # 过滤关键组件 echo 数据库相关依赖 grep -iE mysql|oracle|postgres|jdbc|druid|hikari|mybatis|hibernate /tmp/dep_tree.txt echo Web 容器相关依赖 grep -iE tomcat|jetty|undertow|servlet|spring-boot-starter-web /tmp/dep_tree.txt这个脚本的逻辑很简单先让 Maven 把依赖树落盘再用 grep 按关键词过滤。参数上-DoutputTypetext保证输出是纯文本方便 grep-q减少控制台噪音。扫完之后你会得到一张清单清单里每一项都要标记“保留 / 替换 / 移除”。比如 Druid 连接池通常可以保留但 JDBC 驱动必须换成国产库对应的驱动Spring Boot 内嵌 Tomcat 如果要换成国产容器spring-boot-starter-web里的 Tomcat 依赖就要排除掉。提示依赖清单一定要在改造前完成不要边改边发现新依赖。我见过一个项目改到一半才发现用了某商业数据库的私有函数返工成本极高。2.2 改造优先级先数据库还是先容器我的经验是先数据库、后容器。原因很直接数据库适配的失败是“报错可见”的SQL 执行不了会直接抛异常排查路径清晰容器适配的失败往往是“启动卡住”或“类加载冲突”日志可能只给一个模糊的ClassNotFoundException排查成本更高。先把数据库跑通应用至少能在旧容器里验证业务逻辑再换容器就只剩部署问题。具体顺序建议第一步换 JDBC 驱动和连接串让应用能连上国产库第二步跑全量 SQL 回归把不兼容的语法逐个改掉第三步换 Web 容器调整启动脚本和类加载配置第四步联合压测验证连接池和线程模型。每一步都要有独立的验证标准不要跳步。2.3 环境准备国产数据库和容器的基本部署确认在动手改代码之前先确认目标环境是通的。国产数据库一般会提供客户端工具先用它验证库能连、账号权限够、字符集正确。下面是一个通用的连接验证命令具体客户端名称按实际环境替换# 验证数据库连通性和字符集 # 假设客户端工具为 dbclient实际按环境替换 dbclient -h 10.0.0.10 -P 5432 -U app_user -d app_db -c SELECT version(); dbclient -h 10.0.0.10 -P 5432 -U app_user -d app_db -c SHOW server_encoding;参数说明-h是数据库地址-P是端口-U是用户名-d是库名-c后面跟要执行的 SQL。字符集这一步特别关键如果数据库是 UTF-8 而应用连接串没指定中文乱码会以“玄学”的方式出现——有时候正常有时候乱排查起来很痛苦。Web 容器侧先确认容器能独立启动、能部署一个最简单的 war 包或 jar 包再往上叠应用。3. 国产数据库适配SQL 方言、驱动和连接池的三重门数据库适配是信创改造里工作量最大的部分没有之一。很多人以为换个驱动就完事实际上国产数据库虽然大多兼容标准 SQL但各家在函数、分页、数据类型、事务隔离级别上都有自己的“脾气”。这一章按“驱动替换 → SQL 兼容性排查 → 连接池调参”三步走每步都给可执行的方案。3.1 JDBC 驱动替换连接串和驱动类的改法驱动替换是最基础的一步。以 Maven 项目为例先在pom.xml里把原数据库驱动替换成国产库驱动。假设国产库驱动坐标为com.example:kingbase-jdbc:8.6.0实际按厂商提供的坐标填写原 MySQL 驱动要移除!-- 移除原数据库驱动 -- !-- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId /dependency -- !-- 引入国产数据库驱动 -- dependency groupIdcom.example/groupId artifactIdkingbase-jdbc/artifactId version8.6.0/version /dependency驱动类名和连接串格式也要改。下面是一个 Spring Boot 的application.yml示例展示了连接串的典型结构spring: datasource: driver-class-name: com.example.jdbc.Driver url: jdbc:kingbase8://10.0.0.10:54321/app_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai username: app_user password: ${DB_PASSWORD} hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 validation-query: SELECT 1逻辑说明driver-class-name必须换成国产库的驱动类写错会直接报ClassNotFoundExceptionurl的协议头、端口、参数名各家不同useUnicode和characterEncoding是中文场景必带validation-query有些国产库不支持SELECT 1要换成SELECT 1 FROM DUAL之类的写法这个坑后面会细说。参数上maximum-pool-size不要照搬原环境的数值国产库的连接建立开销可能更大建议先设小一点压测后再调。3.2 SQL 兼容性排查分页、函数和隐式转换SQL 兼容性是真正花时间的地方。我一般会先把应用里所有 SQL 捞出来按“分页语句、内置函数、数据类型转换、DDL”四类过一遍。分页是最常见的翻车点MySQL 的LIMIT offset, size在国产库里可能不支持要改成LIMIT size OFFSET offset或者用ROW_NUMBER()窗口函数。下面是一个改造前后的对比-- 改造前MySQL 风格 SELECT id, name, created_at FROM orders ORDER BY created_at DESC LIMIT 10, 20; -- 改造后标准 SQL 风格多数国产库支持 SELECT id, name, created_at FROM orders ORDER BY created_at DESC LIMIT 20 OFFSET 10; -- 如果国产库不支持 LIMIT OFFSET用窗口函数 SELECT * FROM ( SELECT id, name, created_at, ROW_NUMBER() OVER (ORDER BY created_at DESC) AS rn FROM orders ) t WHERE t.rn 10 AND t.rn 30;函数兼容性更隐蔽。比如IFNULL()在有些国产库里要写成COALESCE()DATE_FORMAT()的格式串可能不一样GROUP_CONCAT()的替代函数名各家不同。我的做法是写一个 SQL 扫描脚本把可疑函数列出来人工确认#!/bin/bash # 扫描项目中可疑的数据库函数 # 用法./scan_sql.sh /path/to/sql/files SQL_DIR$1 grep -rniE ifnull|date_format|group_concat|limit [0-9] *,|now\(\)|sysdate $SQL_DIR \ --include*.xml --include*.sql --include*.java \ /tmp/suspicious_sql.txt echo 可疑 SQL 已输出到 /tmp/suspicious_sql.txt共 $(wc -l /tmp/suspicious_sql.txt) 处这个脚本把常见的不兼容函数和语法模式扫出来输出到文件后逐条确认。注意limit [0-9] *,这个正则专门抓 MySQL 的LIMIT offset, size写法这是翻车率最高的模式。隐式类型转换也要注意有些国产库对字符串和数字的比较更严格WHERE id 123可能不走索引甚至报错要改成WHERE id 123。3.3 连接池参数调优validationQuery 和超时设置连接池参数是另一个容易忽略的点。国产数据库的validationQuery支持情况不一有的支持SELECT 1有的必须SELECT 1 FROM DUAL写错了连接池会一直报“连接无效”。下面是一个 HikariCP 的完整配置示例标注了每个参数的作用spring: datasource: hikari: maximum-pool-size: 20 # 最大连接数按压测结果调 minimum-idle: 5 # 最小空闲连接别设太大浪费资源 connection-timeout: 30000 # 获取连接超时国产库建连慢可适当加大 idle-timeout: 600000 # 空闲连接回收时间 max-lifetime: 1800000 # 连接最大存活时间要小于数据库侧的超时 validation-query: SELECT 1 FROM DUAL # 按国产库实际支持情况填写 validation-query-timeout: 5 # 校验超时别设太长拖慢获取连接 test-on-borrow: true # 借出时校验国产库网络不稳时建议开参数说明max-lifetime必须小于数据库服务端的连接超时时间否则会出现“应用以为连接可用、数据库已经断开”的情况报错信息通常是connection reset或broken pipe。test-on-borrow会带来一点性能开销但如果网络环境不稳定建议开启否则会频繁遇到“拿到坏连接”的问题。validation-query-timeout设 5 秒足够设太长会在数据库卡顿时拖垮整个连接池。注意连接池参数没有“万能值”一定要在目标环境压测后调。我一般会先用默认值跑一轮观察连接等待时间和活跃连接数再针对性调整。4. 国产 Web 容器适配类加载、启动脚本和会话管理Web 容器适配的坑和数据库不一样数据库是“报错明确”容器是“启动玄学”。最常见的现象是应用在旧容器里跑得好好的换到国产容器后启动卡住、类找不到、或者会话丢失。这一章按“容器选型 → 类加载配置 → 启动脚本改造 → 会话管理”推进。4.1 容器选型内嵌容器和外置容器的取舍国产 Web 容器一般有两种用法作为外置容器部署 war 包或者作为内嵌容器打包进 Spring Boot 应用。我的建议是优先用外置容器原因是外置容器的类加载机制更接近传统 Servlet 容器兼容性问题更容易定位内嵌容器虽然部署方便但类加载冲突往往表现为“启动即失败”日志信息少排查困难。如果必须用内嵌容器要先把 Spring Boot 默认的 Tomcat 排除掉再引入国产容器的 starter。下面是一个 Maven 排除示例dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId exclusions !-- 排除默认 Tomcat -- exclusion groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId /exclusion /exclusions /dependency !-- 引入国产容器 starter坐标按厂商提供填写 -- dependency groupIdcom.example/groupId artifactIdexample-container-spring-boot-starter/artifactId version1.0.0/version /dependency逻辑说明排除 Tomcat 是为了避免两个容器实现同时存在导致类加载冲突典型报错是java.lang.LinkageError或ClassCastException。引入国产容器 starter 后启动类通常不需要改但如果有自定义的ServletWebServerFactory配置要确认国产容器是否支持同样的接口。4.2 类加载机制为什么你的框架在国产容器里起不来类加载是容器适配里最“黑匣子”的部分。国产容器有的默认使用“父优先”加载策略有的用“子优先”这直接影响框架能不能扫描到自己的类。典型现象是Spring 启动时报NoSuchMethodException或ClassNotFoundException但类明明在依赖里。解决办法是调整容器的类加载配置把应用包路径设为“子优先加载”。以某国产容器为例配置文件里通常有一个classloader相关的配置项可以指定哪些包由应用类加载器优先加载!-- 容器类加载配置示例具体标签名按实际容器文档 -- classloader loader-priorityapplication-first/loader-priority application-packages packagecom.example.app/package packageorg.springframework/package /application-packages /classloader参数说明loader-priority设为application-first表示应用类优先加载适合 Spring 这类需要扫描自身类的框架application-packages列出需要优先加载的包路径一般至少包含应用根包和 Spring 相关包。如果容器不支持这种配置退而求其次的办法是把冲突的依赖打到 war 包的WEB-INF/lib下利用 Servlet 规范的类加载顺序。4.3 启动脚本改造JVM 参数和容器参数的对齐启动脚本改造看起来简单实际上很容易漏。原环境的启动脚本里可能有一堆针对旧容器的 JVM 参数换容器后有些参数不再适用有些必须新增。下面是一个改造后的启动脚本示例#!/bin/bash # 国产 Web 容器启动脚本示例 export JAVA_HOME/usr/local/jdk export APP_HOME/opt/app export CONTAINER_HOME/opt/container # JVM 参数堆内存按实际调整元空间别设太小 JVM_OPTS-Xms2g -Xmx2g -XX:MetaspaceSize256m -XX:MaxMetaspaceSize512m JVM_OPTS$JVM_OPTS -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath$APP_HOME/logs JVM_OPTS$JVM_OPTS -Dfile.encodingUTF-8 -Duser.timezoneAsia/Shanghai # 容器参数端口、应用路径、类加载配置 CONTAINER_OPTS--port 8080 --app-path $APP_HOME/webapp --classloader-config $APP_HOME/classloader.xml $CONTAINER_HOME/bin/start.sh $JVM_OPTS $CONTAINER_OPTS逻辑说明-XX:MetaspaceSize和-XX:MaxMetaspaceSize在换容器后要重新评估因为不同容器加载的类数量不同设太小会频繁 Full GC-Dfile.encodingUTF-8和-Duser.timezone是中文环境的标配漏了会出现乱码或时间偏差。容器参数里的--classloader-config指向前面说的类加载配置文件确保启动时生效。4.4 会话管理容器切换后 session 丢失怎么办会话丢失是容器切换后最常见的“用户可感知”问题。原因通常是国产容器的 session 序列化机制和原容器不同或者 session 存储路径变了。如果应用是单机部署检查容器的 session 超时配置和序列化配置如果是集群部署建议直接把 session 外置到 Redis避免依赖容器实现。下面是一个 Spring Session Redis 的配置示例用来替代容器自带的 session 管理spring: session: store-type: redis timeout: 1800s redis: namespace: app:session redis: host: 10.0.0.20 port: 6379 password: ${REDIS_PASSWORD}参数说明store-type: redis表示 session 存到 Redis不再依赖容器timeout是会话超时时间按业务调整namespace是 Redis 里的 key 前缀多应用共用 Redis 时必设否则会互相覆盖。改成 Redis 存储后容器切换对 session 的影响就基本消除了这也是我推荐的做法。5. 信创适配避坑清单五条血泪经验这一章是我在多个项目里踩过的坑每条按“现象 → 原因 → 解决”写。有些坑看起来很小但排查起来能耗掉一整天希望你能跳过。5.1 中文乱码字符集不一致的连锁反应现象应用连上国产库后部分中文显示为问号或乱码但有些中文又正常。原因数据库服务端字符集、客户端连接字符集、JVM 文件编码三者不一致。常见情况是数据库是 UTF-8连接串没指定characterEncodingJVM 默认编码又是 GBK导致写入和读取用了不同编码。解决三处统一为 UTF-8。连接串加useUnicodetruecharacterEncodingUTF-8启动脚本加-Dfile.encodingUTF-8数据库侧确认server_encoding是 UTF-8。改完后要重新建表或转换已有数据否则旧数据仍然是坏编码。5.2 连接池报“连接无效”validationQuery 写错现象应用启动后频繁报“连接无效”或“connection is closed”但数据库明明能连。原因连接池的validationQuery写了国产库不支持的语法比如写了SELECT 1但国产库要求SELECT 1 FROM DUAL。校验失败后连接池会不断创建新连接又丢弃表现为连接数暴涨。解决查国产库文档确认校验语句改成支持的写法。如果不确定可以先关掉test-on-borrow用test-while-idle替代减少校验频率。改完后观察连接池活跃连接数是否稳定。5.3 容器启动卡住类加载死锁现象国产容器启动时卡在某个阶段日志最后一行是某个类加载信息之后没有任何输出CPU 占用不高。原因类加载器之间的循环依赖导致死锁常见于应用包和框架包互相引用且容器用了“父优先”加载策略。解决调整类加载策略为“应用优先”把应用根包和框架包加入优先加载列表。如果容器不支持配置用jstack抓线程栈找到卡住的类加载器把冲突的依赖调整到同一层。这个坑排查成本高建议在测试环境先验证。5.4 分页查询结果错乱LIMIT 语法不兼容现象分页查询返回的数据条数不对或者第二页数据和第一页重复。原因MySQL 的LIMIT offset, size在国产库里被解析成了别的含义或者直接不支持导致优化器选了错误的执行计划。解决统一改成LIMIT size OFFSET offset标准写法或者用ROW_NUMBER()窗口函数。改完后要验证边界情况第一页、最后一页、offset 超过总数的情况。我一般会写一个分页回归测试覆盖这些边界。5.5 事务不回滚隔离级别和自动提交的差异现象业务代码里抛了异常但数据已经写进去了事务没有回滚。原因国产库的默认事务隔离级别和原库不同或者连接池的autoCommit配置不一致。有些国产库默认隔离级别是“读已提交”而原库是“可重复读”在某些场景下行为不同。解决在连接串或连接池配置里显式指定事务隔离级别不要依赖默认值。Spring 的Transactional要确认rollbackFor覆盖了实际抛出的异常类型。改完后写一个事务回滚测试故意抛异常验证数据没写入。6. 适配后的验证怎么确认系统真的“敢上生产”改造完成不等于可以上线验证环节才是决定“敢不敢上生产”的关键。我一般分三步验证功能回归、性能压测、故障演练。功能回归重点测 SQL 相关业务尤其是分页、统计、批量操作性能压测重点看连接池和容器线程模型故障演练模拟数据库断连、容器重启验证恢复能力。6.1 功能回归SQL 兼容性测试用例怎么写功能回归不能只靠手点要写自动化用例覆盖 SQL 兼容性风险点。下面是一个用 JUnit 写的分页和函数兼容性测试示例// 分页兼容性测试验证第一页、最后一页、越界情况 Test public void testPaginationCompatibility() { // 第一页 ListOrder page1 orderMapper.selectPage(0, 10); assertEquals(10, page1.size()); // 最后一页假设总数 25 ListOrder page3 orderMapper.selectPage(20, 10); assertEquals(5, page3.size()); // 越界offset 超过总数应返回空列表而不是报错 ListOrder page4 orderMapper.selectPage(100, 10); assertTrue(page4.isEmpty()); } // 函数兼容性测试验证 COALESCE 替代 IFNULL 后的行为 Test public void testNullHandling() { String result orderMapper.selectNameWithFallback(999L); assertEquals(unknown, result); // 确认空值处理逻辑一致 }逻辑说明分页测试要覆盖边界因为LIMIT OFFSET在越界时的行为各家不同有的返回空、有的报错函数测试要验证空值处理因为IFNULL和COALESCE在参数个数和类型上有差异。这些用例跑通基本能覆盖大部分 SQL 兼容性问题。6.2 性能压测连接池和容器线程数的联合调优压测的目标不是刷高 TPS而是找到稳定运行的参数组合。我一般用 JMeter 或 wrk 压核心接口同时监控连接池活跃数、容器线程数、数据库 CPU。下面是一个压测结果对比表展示调参前后的差异指标调参前调参后说明平均响应时间320ms180ms连接池 max 从 50 降到 20错误率2.3%0.1%validationQuery 修正后连接稳定数据库 CPU85%60%减少无效连接创建容器线程数200150按压测结果下调调参思路连接池maximum-pool-size不是越大越好超过数据库处理能力反而会增加上下文切换容器线程数要和连接池匹配线程太多会大量阻塞在获取连接上。压测时逐步加压观察哪个指标先到瓶颈再针对性调整。6.3 故障演练数据库断连和容器重启的恢复验证故障演练是最后一道关。我一般做两个场景数据库主动断连 30 秒后恢复观察应用是否能自动重连容器强制重启观察 session 是否丢失、请求是否被正确处理。下面是一个模拟数据库断连的脚本#!/bin/bash # 模拟数据库断连通过防火墙规则临时阻断端口 # 注意仅在测试环境执行生产环境禁用 DB_HOST10.0.0.10 DB_PORT54321 echo 阻断数据库连接... iptables -A OUTPUT -d $DB_HOST -p tcp --dport $DB_PORT -j DROP sleep 30 echo 恢复数据库连接... iptables -D OUTPUT -d $DB_HOST -p tcp --dport $DB_PORT -j DROP echo 观察应用日志确认是否自动重连 tail -f /opt/app/logs/app.log | grep -iE reconnect|connection|error逻辑说明用 iptables 临时阻断端口模拟网络故障30 秒后恢复观察应用日志里是否有重连成功的记录。如果应用没有自动重连要检查连接池的connection-timeout和数据库驱动的重连配置。这个演练能暴露很多“平时看不出来”的问题建议上线前必做。6.4 一个我常用的验证习惯每次适配完成后我会保留一份“改造前 vs 改造后”的配置对照表包括连接串、JVM 参数、容器参数、连接池参数。这份表在出问题时能快速定位是哪个参数改错了。另外我会在测试环境多跑一轮“异常场景”比如数据库重启、容器 kill -9确认系统能恢复。这些习惯看起来笨但能省下大量生产排查时间。希望帮到你。本文还有配套的精品资源点击获取