news 2026/11/2 13:48:54

若依框架从MySQL迁移到SQL Server实战:依赖、脚本、分页与业务SQL全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
若依框架从MySQL迁移到SQL Server实战:依赖、脚本、分页与业务SQL全解析

若依这套脚手架在国内中小型管理系统里出镜率极高,默认那套 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、ENGINE=InnoDB、COMMENT内联注释、datetime默认值写法,SQL Server 全都不认。
  • 框架配置层:MyBatis-Plus 的分页插件如果不显式指定数据库类型,会按连接推断,某些版本推断不准就分页错乱;主键生成策略默认可能是自增,而 SQL Server 的自增列叫IDENTITY。
  • 业务代码层:代码里散落的DATE_FORMAT、IFNULL、LIMIT、GROUP_CONCAT这类 MySQL 函数,以及大小写敏感、字符串拼接concat的行为差异。

这四层里,前两层是"必须改",第三层是"不改会出诡异 bug",第四层是"跑起来才知道"。所以正确的改造顺序应该是:先动依赖和脚本把库建起来,再调框架配置让基础 CRUD 正常,最后靠实际点页面把业务 SQL 里的方言问题一个个揪出来。反过来做,你会在一堆报错里迷失。

1.2 改造前必须确认的三件事

动手之前,有三件事先确认清楚,能省掉后面大量返工:

  1. SQL Server 版本。2012 和 2019 差别不小,尤其是分页OFFSET FETCH需要 2012 及以上,STRING_AGG需要 2017 及以上。如果甲方给的是 2008,那分页得换ROW_NUMBER()写法,字符串聚合得用FOR XML PATH。版本决定了你后面能用哪些语法。
  2. 排序规则(Collation)。SQL Server 默认排序规则常见是Chinese_PRC_CI_AS,CI表示大小写不敏感,这点和 MySQL 在 Linux 下默认大小写敏感的表名行为不同。如果你的表名、字段名在代码里大小写混用,迁过去可能反而不报错,但一旦换到大小写敏感的排序规则就会炸。建议建库时就明确排序规则,别依赖默认。
  3. 是否保留若依的多数据源能力。若依有@DataSource多数据源注解,如果项目里用了,改造时每个数据源都要单独配 SQL Server 驱动,别只改主库。

提示:改造前先把原始 MySQL 版本的代码打一个 tag 或拉一个分支,SQL Server 改造过程中会大量改脚本和配置,留好回退点,出问题能快速对比。

2. 依赖与连接配置:把 MySQL 的痕迹彻底换掉

这一层是最机械但也最容易漏的。漏一个,启动就报ClassNotFoundException或者驱动版本不匹配。

2.1 pom.xml 里的驱动替换

若依父工程和ruoyi-framework、ruoyi-system等子模块里,MySQL 驱动通常声明在父pom.xml的dependencyManagement或直接在各模块。找到类似这样的依赖:

<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>

替换为 SQL Server 驱动。这里有个选型问题:用mssql-jdbc还是老的sqljdbc4。我的建议是一律用微软官方的mssql-jdbc,版本跟 JDK 对齐——JDK 8 用 8.x 或 9.x 的mssql-jdbc,JDK 11 及以上用 10.x 以上。老的sqljdbc4是 2012 年前后的产物,对 TLS、新版本 SQL Server 支持差,别用。

<dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>mssql-jdbc</artifactId> <version>9.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;databaseName=ry_sqlserver;encrypt=false;trustServerCertificate=true username: sa password: your_password

几个参数值得单独说:

  • databaseName是 SQL Server 指定库的方式,不是像 MySQL 那样写在路径里。
  • encrypt=false和trustServerCertificate=true:新版驱动默认encrypt=true,连本地或内网没配证书的实例会直接握手失败。开发环境关掉加密最省事,生产环境如果要求加密,就得正经配证书,别图省事一直关着。
  • 如果实例不是默认端口,1433换成实际端口;命名实例则用instanceName=XXX而不是端口。

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)自增列,位置在类型后
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4删除SQL Server 无此概念
COMMENT 'xxx'(列内联)EXEC sp_addextendedproperty ...注释要单独语句加
datetime DEFAULT CURRENT_TIMESTAMPdatetime DEFAULT GETDATE()默认时间函数不同
tinyint(1)tinyint或bit布尔语义建议用bit
text/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, ',')需 2017+
NOW()GETDATE()当前时间
ON DUPLICATE KEY UPDATEMERGE语句upsert 逻辑

这张表建议打印出来贴屏幕边上,改脚本时随时对照。

3.2 注释和默认值的处理技巧

MySQL 的列注释是内联的,SQL Server 得用扩展属性。一张表几十个字段,一个个写sp_addextendedproperty会疯。我的做法是:先不管注释,把表结构建对,注释最后用脚本批量补。批量补的思路是从原 MySQL 脚本里把COMMENT 'xxx'抽出来,用正则生成对应的sp_addextendedproperty语句。举个单条的例子:

EXEC sp_addextendedproperty @name = N'MS_Description', @value = N'用户ID', @level0type = N'SCHEMA', @level0name = N'dbo', @level1type = N'TABLE', @level1name = N'sys_user', @level2type = N'COLUMN', @level2name = N'user_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 拼出来还是LIMIT,SQL 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混用:如果代码里手写了LIMIT,SQL 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 这块单独提一句:若依集成了 Quartz,quartz.sql建表脚本也是 MySQL 语法,得一起改。Quartz 官方其实提供了各数据库的建表脚本,直接找 SQL Server 版本替换,比自己改省事。

7.3 几个我踩过、值得你避开的细节

  • nvarchar长度别照抄varchar:MySQL 的varchar(255)迁到 SQL Server 如果写成nvarchar(255),因为nvarchar每字符占 2 字节,索引键长度限制更容易触顶。长字段评估一下,该用nvarchar(max)就用。
  • 默认 schema:SQL Server 默认 schema 是dbo,若依脚本建表没指定 schema 时落在dbo下,连接用户如果有自定义默认 schema,可能找不到表。连接串或用户配置里确认默认 schema 是dbo。
  • 事务与锁:SQL Server 默认隔离级别是READ COMMITTED,和 MySQL 的REPEATABLE READ不同,某些依赖可重复读的业务逻辑迁过来行为会变。若依自带业务一般不受影响,但如果有自定义的复杂事务,得复核。

改造这件事,说到底不是"把 MySQL 换成 SQL Server"这么一句,而是把若依里所有默认绑定 MySQL 行为的地方,一个个换成 SQL Server 的等价物,再用实际功能验证。我个人的体会是:配置和脚本的改动是体力活,框架层和业务 SQL 的适配才是脑力活,前者靠对照表批量处理,后者靠日志和点页面逐个揪。把第 4 章和第 5 章的内容当成检查清单,改造完对着过一遍,能挡掉绝大多数上线后才会暴露的问题。

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

氢能源无人机能量管理系统与DP_MPC控制算法解析

1. 氢能源无人机能量管理系统的核心挑战氢燃料电池无人机与传统锂电池动力系统相比&#xff0c;最显著的优势在于能量密度。以斗山创新的DS30无人机为例&#xff0c;其2.6kW燃料电池系统配合10.8升350巴高压储氢罐&#xff0c;可实现300克氢气的存储&#xff0c;能量密度达到锂…

作者头像 李华
网站建设 2026/11/2 13:48:26

DocResearch面试全解析:Agent+RAG架构设计与工程实践

1. 面试官到底想听什么&#xff1a;DocResearch 项目的定位与考察逻辑DocResearch 这个项目名字听起来像个学术工具&#xff0c;实际上它是一个典型的Agent RAG复合型项目&#xff0c;核心场景是让 AI 自动完成对一批文档的检索、阅读、归纳和报告生成。面试官拿到这个项目&am…

作者头像 李华
网站建设 2026/11/2 13:48:24

智能充电桩后台管理系统设计与多站点运营实践

1. 智能充电桩后台管理系统概述在新能源车快速普及的今天&#xff0c;充电桩运营管理面临前所未有的挑战。我负责过三个城市的充电桩网络建设&#xff0c;深刻体会到没有一套强大的后台管理系统&#xff0c;运营工作简直寸步难行。这套系统本质上是一个"数字指挥中心"…

作者头像 李华
网站建设 2026/11/2 13:46:35

LangChain 大模型缓存实战:无缓存、普通缓存与语义缓存三档对比

1. 三档缓存方案到底在比什么大模型应用上线之后&#xff0c;最容易被低估的成本不是模型调用费&#xff0c;而是重复计算。同一个用户问“你们退货政策是什么”&#xff0c;今天问一遍&#xff0c;明天换个说法又问一遍&#xff0c;后台每次都老老实实把整段提示词塞给模型&am…

作者头像 李华
网站建设 2026/11/2 13:46:26

AI提示词管理工具AiShort的核心功能与应用场景

1. AI提示词管理工具AiShort的核心价值作为一名长期与各类AI工具打交道的从业者&#xff0c;我深刻体会到优质提示词&#xff08;Prompt&#xff09;对生成结果的决定性影响。AiShort正是为解决这个痛点而生的效率工具——它通过结构化管理和智能优化&#xff0c;将零散的提示词…

作者头像 李华
网站建设 2026/11/2 13:45:43

GraphRAG+Neo4j:给AI Agent装上知识图谱,扛住并发实战

最近手头有个AI Agent项目做得比较有意思&#xff0c;正好踩中了"ai agent怎么扛并发"和"graphRAG"这几个热点&#xff0c;核心就是用Neo4j给Agent接上了一套图谱知识库。这个项目做下来&#xff0c;我对GraphRAG的理解算是从"概念热词"真正变成…

作者头像 李华