若依这套脚手架在国内中小型管理系统里出镜率极高默认那套 MySQL 跑得好好的一旦甲方机房摆的是一台 SQL Server事情就来了。我前后经手过三个把若依从 MySQL 迁到 SQL Server 的项目第一个项目踩了整整两周的坑后面两个就顺多了。这篇就把这套改造的完整链路摊开讲清楚从依赖替换、驱动配置、建表脚本改写到分页方言、主键策略、批量插入、日期函数这些真正会卡住你的地方一个不落。适合手上正拿着若依源码、面对 SQL Server 数据库不知道从哪下手的朋友也适合想提前摸清两种数据库差异、避免上线前夜返工的同学。全文按我实际改造的顺序走每一步都说明为什么这么改能直接照着抄。1. 先想清楚为什么若依换库不是改个连接串那么简单很多人第一反应是把application.yml里的driver-class-name和url一换重启完事。真这么干大概率启动能过一登录就报错。原因在于若依虽然是基于 MyBatis-Plus 的通用脚手架但它在很多地方默认绑定了 MySQL 的行为习惯这些习惯散落在依赖、SQL 脚本、分页插件、代码生成器模板里不是一处配置能覆盖的。1.1 若依对 MySQL 的隐性依赖藏在哪几个层面我把这些年遇到的依赖点归了归类大致是四层驱动与连接层pom.xml里默认引的是 MySQL 的 JDBC 驱动连接串参数也是 MySQL 方言比如useUnicode、serverTimezone。建表脚本层若依自带的sql目录下是 MySQL 语法AUTO_INCREMENT、ENGINEInnoDB、COMMENT内联注释、datetime默认值写法SQL Server 全都不认。框架配置层MyBatis-Plus 的分页插件如果不显式指定数据库类型会按连接推断某些版本推断不准就分页错乱主键生成策略默认可能是自增而 SQL Server 的自增列叫IDENTITY。业务代码层代码里散落的DATE_FORMAT、IFNULL、LIMIT、GROUP_CONCAT这类 MySQL 函数以及大小写敏感、字符串拼接concat的行为差异。这四层里前两层是必须改第三层是不改会出诡异 bug第四层是跑起来才知道。所以正确的改造顺序应该是先动依赖和脚本把库建起来再调框架配置让基础 CRUD 正常最后靠实际点页面把业务 SQL 里的方言问题一个个揪出来。反过来做你会在一堆报错里迷失。1.2 改造前必须确认的三件事动手之前有三件事先确认清楚能省掉后面大量返工SQL Server 版本。2012 和 2019 差别不小尤其是分页OFFSET FETCH需要 2012 及以上STRING_AGG需要 2017 及以上。如果甲方给的是 2008那分页得换ROW_NUMBER()写法字符串聚合得用FOR XML PATH。版本决定了你后面能用哪些语法。排序规则Collation。SQL Server 默认排序规则常见是Chinese_PRC_CI_ASCI表示大小写不敏感这点和 MySQL 在 Linux 下默认大小写敏感的表名行为不同。如果你的表名、字段名在代码里大小写混用迁过去可能反而不报错但一旦换到大小写敏感的排序规则就会炸。建议建库时就明确排序规则别依赖默认。是否保留若依的多数据源能力。若依有DataSource多数据源注解如果项目里用了改造时每个数据源都要单独配 SQL Server 驱动别只改主库。提示改造前先把原始 MySQL 版本的代码打一个 tag 或拉一个分支SQL Server 改造过程中会大量改脚本和配置留好回退点出问题能快速对比。2. 依赖与连接配置把 MySQL 的痕迹彻底换掉这一层是最机械但也最容易漏的。漏一个启动就报ClassNotFoundException或者驱动版本不匹配。2.1 pom.xml 里的驱动替换若依父工程和ruoyi-framework、ruoyi-system等子模块里MySQL 驱动通常声明在父pom.xml的dependencyManagement或直接在各模块。找到类似这样的依赖dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId /dependency替换为 SQL Server 驱动。这里有个选型问题用mssql-jdbc还是老的sqljdbc4。我的建议是一律用微软官方的mssql-jdbc版本跟 JDK 对齐——JDK 8 用 8.x 或 9.x 的mssql-jdbcJDK 11 及以上用 10.x 以上。老的sqljdbc4是 2012 年前后的产物对 TLS、新版本 SQL Server 支持差别用。dependency groupIdcom.microsoft.sqlserver/groupId artifactIdmssql-jdbc/artifactId version9.4.1.jre8/version /dependency版本号里的jre8后缀别漏微软的驱动按 JDK 版本分了jre8和jre11两个包选错了运行时报UnsupportedClassVersionError。2.2 连接串参数的门道MySQL 的连接串参数在 SQL Server 上大部分不适用得换成 SQL Server 的写法。一个能用的基础连接串长这样spring: datasource: druid: driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver url: jdbc:sqlserver://127.0.0.1:1433;databaseNamery_sqlserver;encryptfalse;trustServerCertificatetrue username: sa password: your_password几个参数值得单独说databaseName是 SQL Server 指定库的方式不是像 MySQL 那样写在路径里。encryptfalse和trustServerCertificatetrue新版驱动默认encrypttrue连本地或内网没配证书的实例会直接握手失败。开发环境关掉加密最省事生产环境如果要求加密就得正经配证书别图省事一直关着。如果实例不是默认端口1433换成实际端口命名实例则用instanceNameXXX而不是端口。Druid 的校验 SQL 也要改。若依默认配的是SELECT 1这个在 SQL Server 上其实也能跑但更稳妥的是SELECT 1保持不动即可不用改成SELECT 1 FROM dual那是 Oracle 的写法SQL Server 没有dual表改了反而错。这一点很多人搞混记住 SQL Server 支持无 FROM 的SELECT 1。2.3 别忘了代码生成器和多数据源若依的代码生成器模块ruoyi-generator里读取表结构的 SQL 是写死的 MySQL 查询information_schema的方式。SQL Server 也有information_schema但字段和函数不完全一样比如取表注释、列注释的方式不同。如果项目要用代码生成器这块得单独改否则生成器页面一打开就报错。多数据源场景下DataSourceType枚举和DruidConfig里每个数据源都要补 SQL Server 的驱动类名别只改主库。3. 建表脚本改写SQL Server 不认的那些 MySQL 语法这是改造里工作量最大、也最考验耐心的一块。若依自带的ry_20xxx.sql和quartz.sql加起来几十张表逐条手改不现实得先摸清差异规律再批量处理加人工核对。3.1 高频语法差异对照我把改造中反复遇到的差异整理成表照着替换能覆盖八成以上MySQL 写法SQL Server 对应写法说明AUTO_INCREMENTIDENTITY(1,1)自增列位置在类型后ENGINEInnoDB DEFAULT CHARSETutf8mb4删除SQL Server 无此概念COMMENT xxx列内联EXEC sp_addextendedproperty ...注释要单独语句加datetime DEFAULT CURRENT_TIMESTAMPdatetime DEFAULT GETDATE()默认时间函数不同tinyint(1)tinyint或bit布尔语义建议用bittext/longtextnvarchar(max)大文本varchar(n)nvarchar(n)建议统一 nvarchar 支持中文反引号[ ]或不用SQL Server 用方括号LIMIT 0,10OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY分页语法IFNULL(a,b)ISNULL(a,b)空值处理DATE_FORMAT(d,%Y-%m-%d)CONVERT(varchar(10), d, 23)日期格式化GROUP_CONCAT(x)STRING_AGG(x, ,)需 2017NOW()GETDATE()当前时间ON DUPLICATE KEY UPDATEMERGE语句upsert 逻辑这张表建议打印出来贴屏幕边上改脚本时随时对照。3.2 注释和默认值的处理技巧MySQL 的列注释是内联的SQL Server 得用扩展属性。一张表几十个字段一个个写sp_addextendedproperty会疯。我的做法是先不管注释把表结构建对注释最后用脚本批量补。批量补的思路是从原 MySQL 脚本里把COMMENT xxx抽出来用正则生成对应的sp_addextendedproperty语句。举个单条的例子EXEC sp_addextendedproperty name NMS_Description, value N用户ID, level0type NSCHEMA, level0name Ndbo, level1type NTABLE, level1name Nsys_user, level2type NCOLUMN, level2name Nuser_id;字段多的时候写个小脚本Python 或 Shell 都行读原 SQL、正则匹配COMMENT、拼出这批语句比手写快一个数量级。这是改造里少数值得花时间写工具的地方。默认值方面CURRENT_TIMESTAMP换成GETDATE()是基本操作但要注意 SQL Server 里datetime和datetime2精度不同若依里大量用datetime存创建时间保持datetime即可别自作主张换datetime2否则和 Java 侧Date映射时可能出现精度截断的困惑。3.3 索引、主键与自增的坑若依的表主键基本都是bigint自增。SQL Server 里bigint IDENTITY(1,1)没问题但有个坑自增列不能显式插入值除非开启IDENTITY_INSERT。若依的初始化数据脚本里如果有显式指定主键值的INSERT比如预置管理员、菜单数据直接跑会报错。解决办法是在这批 INSERT 前后包一层SET IDENTITY_INSERT sys_user ON; INSERT INTO sys_user (user_id, ...) VALUES (1, ...); SET IDENTITY_INSERT sys_user OFF;每张有预置数据的表都要这么处理且IDENTITY_INSERT同一时刻只能对一张表开启不能并行。初始化脚本里表多的话得一张张来别想着一次性全开。另外MySQL 的KEY idx_xxx (col)建索引语法SQL Server 要写成CREATE INDEX idx_xxx ON table(col)或者建表时用INDEX子句但语法不同。若依脚本里索引不少这块要逐条转。4. 框架层适配分页、主键、批量操作这些隐形雷库建好了、连接通了、能登录了别急着庆祝。真正的坑往往在业务跑起来之后才暴露。这一层的问题最隐蔽因为不报错只是结果不对。4.1 MyBatis-Plus 分页方言必须显式指定若依用的是 MyBatis-Plus 的分页插件。默认情况下分页插件会尝试根据 JDBC URL 自动识别数据库类型但某些版本识别 SQL Server 时不够准或者项目里配了DbType.MYSQL写死导致分页 SQL 拼出来还是LIMITSQL Server 直接语法错误。正确做法是在分页插件配置里显式指定Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.SQL_SERVER); pagination.setMaxLimit(500L); interceptor.addInnerInterceptor(pagination); return interceptor; }DbType.SQL_SERVER这个枚举值别写错写SQLSERVER或别的拼法编译不过。指定之后分页会自动生成OFFSET ... FETCH NEXT ...的写法。这里顺带说setMaxLimit设个上限防止有人传个巨大的pageSize把库拖垮500 是个经验值按业务调。4.2 主键生成策略的调整若依实体类上常见TableId(type IdType.AUTO)依赖数据库自增。SQL Server 的IDENTITY列在 JDBC 里能正常回填自增主键所以IdType.AUTO理论上是兼容的。但如果你项目里用了IdType.ASSIGN_ID雪花算法或ASSIGN_UUID那和数据库无关不用改。真正要注意的是代码生成器生成的实体它可能按 MySQL 习惯生成AUTO迁到 SQL Server 后如果某张表主键不是IDENTITY而是应用层赋值就会插入失败。改造时把生成器模板里的主键策略检查一遍确保和实际表结构一致。4.3 批量插入的写法差异若依里批量插入常见两种MyBatis 的foreach拼多值INSERT或者saveBatch。前者在 SQL Server 上有个硬限制一条 INSERT 语句的 VALUES 行数不能无限多虽然不像 Oracle 有 1000 行限制那么死但行数太多会触发语句过大。更稳的做法是分批比如每 500 条一批。saveBatch底层也是拼多值 INSERT同样建议控制批次大小。这个参数在MybatisPlus的批量配置或业务代码里调别用默认的一次全塞。还有一个隐蔽点SQL Server 对INSERT INTO ... VALUES (...),(...)的多值语法支持是从 2008 开始的如果甲方是更老的版本虽然少见就得分单条插。确认版本后决定。5. 业务 SQL 里的方言清理靠点页面一个个揪前面四层是改配置和脚本这一层是改代码里的 SQL。若依自带的业务 SQL 大部分是标准 SQL但 XML mapper 里散落着一些 MySQL 函数得靠实际跑功能来发现。5.1 日期函数是重灾区若依的日志、监控、定时任务模块里日期格式化用得很多。DATE_FORMAT(create_time, %Y-%m-%d)这种在 SQL Server 里没有直接对应得用CONVERT或FORMAT。FORMAT函数2012写法更接近直觉-- MySQL SELECT DATE_FORMAT(create_time, %Y-%m-%d %H:%i:%s) FROM sys_logininfor; -- SQL Server SELECT FORMAT(create_time, yyyy-MM-dd HH:mm:ss) FROM sys_logininfor;FORMAT的缺点是性能不如CONVERT数据量大时慎用。追求性能就用CONVERT(varchar(19), create_time, 120)120这个 style 值对应yyyy-mm-dd hh:mi:ss记不住就查一下 style 对照表。我的经验是列表展示用CONVERT格式特殊、量小的场景用FORMAT。5.2 字符串聚合与空值处理若依的部门树、角色权限相关查询里可能用到GROUP_CONCAT把多个值拼成一个字符串。SQL Server 2017 之前没有STRING_AGG得用FOR XML PATH的土办法-- 2017 SELECT STRING_AGG(role_name, ,) FROM sys_role; -- 2017 以前 SELECT STUFF(( SELECT , role_name FROM sys_role FOR XML PATH() ), 1, 1, );STUFF那个写法第一次看很别扭本质是把结果拼成一串 XML 文本再去掉第一个逗号。改造时如果版本够优先STRING_AGG可读性好太多。空值处理IFNULL换ISNULL是机械替换但要注意ISNULL的返回类型由第一个参数决定如果第一个参数是varchar而第二个是nvarchar可能有隐式转换问题必要时显式CAST。5.3 大小写与排序规则的连带影响MySQL 在 Linux 下表名默认大小写敏感SQL Server 默认排序规则通常大小写不敏感。这意味着原来在 MySQL 上因为大小写写错而报错的 SQL迁到 SQL Server 上可能意外能跑但这是假象——一旦哪天换了排序规则或者上了大小写敏感的库立刻炸。改造时不要因为能跑就放过把 mapper 里的表名、字段名大小写统一成和建表脚本一致这是给自己留后路。6. 实测排查链路一个分页错乱的完整定位过程光讲理论不够讲一个我实际遇到的、比较典型的排查过程你能看到思路是怎么走的。6.1 现象列表页数据重复且总数不对改造到一半用户管理列表页出现怪现象第一页显示 10 条但其中几条重复翻到第二页又出现第一页的数据总数显示也不对。这种数据重复 总数错的组合八成是分页 SQL 有问题。6.2 定位从日志里把真实 SQL 捞出来第一步不是猜是开 MyBatis 的 SQL 日志把实际执行的 SQL 打出来。若依里配logging.level.com.ruoyi: debug或者配 MyBatis-Plus 的log-impl为StdOutImpl。捞出来的 SQL 一看末尾是LIMIT 0,10——问题定位了分页插件还在按 MySQL 方言拼 SQL。6.3 根因与修复根因就是 4.1 说的分页插件没显式指定DbType.SQL_SERVER或者项目里某处配置覆盖成了 MySQL。检查MybatisPlusConfig发现分页拦截器构造时没传DbType依赖自动推断而当前驱动版本推断失败回退到了默认的 MySQL 方言。改成显式new PaginationInnerInterceptor(DbType.SQL_SERVER)重启分页正常。这个案例的价值在于遇到分页问题先看真实 SQL别在 Java 代码里瞎找。SQL 日志是最直接的证据。6.4 举一反三还有哪些不报错但结果错的坑顺着这个思路我整理了几个同类隐患改造时主动去验证排序不稳定MySQL 分页不带ORDER BY时结果顺序不保证SQL Server 也一样但两者不保证的具体表现不同。分页查询务必带稳定排序比如按主键否则翻页数据错乱。COUNT与分页总数某些复杂查询里COUNT子查询和主查询条件不一致MySQL 上碰巧对SQL Server 上暴露。改造后重点核对带JOIN的列表总数。TOP与LIMIT混用如果代码里手写了LIMITSQL Server 要换TOP n或OFFSET FETCH别漏。7. 收尾阶段数据迁移与回归验证清单代码改完、功能能点最后一步是把存量数据从 MySQL 搬到 SQL Server并做一轮系统验证。这一步做扎实上线才稳。7.1 数据迁移的两种路子工具迁移用支持异构库的数据迁移工具配置源和目标做字段映射。优点是快缺点是类型映射和自增列处理要人工核对尤其是datetime精度、text到nvarchar(max)的转换。脚本导出导入从 MySQL 导出 CSV 或 SQL清洗后导入 SQL Server。适合数据量不大、需要精细控制的场景。导入前记得处理IDENTITY_INSERT导入后重置自增种子DBCC CHECKIDENT(table, RESEED, 0)否则新插入的数据主键会从旧的最大值继续可能冲突。迁移后一定要抽查中文有没有乱码排序规则和nvarchar是否到位、时间字段有没有偏移时区处理、大文本有没有截断。7.2 回归验证的重点清单功能点太多不可能全测按风险排序优先验证这些验证项关注点登录与权限用户、角色、菜单关联查询是否正常列表分页每页条数、总数、翻页数据一致性新增/编辑自增主键回填、默认值、非空约束批量操作批量删除、批量导入的批次大小定时任务Quartz 表在 SQL Server 下的兼容性日志与监控日期格式化、统计查询代码生成器若使用表结构读取是否正常Quartz 这块单独提一句若依集成了 Quartzquartz.sql建表脚本也是 MySQL 语法得一起改。Quartz 官方其实提供了各数据库的建表脚本直接找 SQL Server 版本替换比自己改省事。7.3 几个我踩过、值得你避开的细节nvarchar长度别照抄varcharMySQL 的varchar(255)迁到 SQL Server 如果写成nvarchar(255)因为nvarchar每字符占 2 字节索引键长度限制更容易触顶。长字段评估一下该用nvarchar(max)就用。默认 schemaSQL Server 默认 schema 是dbo若依脚本建表没指定 schema 时落在dbo下连接用户如果有自定义默认 schema可能找不到表。连接串或用户配置里确认默认 schema 是dbo。事务与锁SQL Server 默认隔离级别是READ COMMITTED和 MySQL 的REPEATABLE READ不同某些依赖可重复读的业务逻辑迁过来行为会变。若依自带业务一般不受影响但如果有自定义的复杂事务得复核。改造这件事说到底不是把 MySQL 换成 SQL Server这么一句而是把若依里所有默认绑定 MySQL 行为的地方一个个换成 SQL Server 的等价物再用实际功能验证。我个人的体会是配置和脚本的改动是体力活框架层和业务 SQL 的适配才是脑力活前者靠对照表批量处理后者靠日志和点页面逐个揪。把第 4 章和第 5 章的内容当成检查清单改造完对着过一遍能挡掉绝大多数上线后才会暴露的问题。
阅读完成 · 觉得有帮助?