07-H2与MySQL双数据源-演示与生产的切换艺术
上回书说到 URM Ultra 选了 Spring Boot 3 + JPA + 单体按域分包。这回咱们聊一个新手最容易被劝退、却又最见功力的细节:数据库怎么连。
一句话先剧透:本项目默认用 H2 内存库跑 Demo——你下载下来,啥数据库都不用装,点一下启动,表自动建好、演示数据自动灌入,马上就能调接口;等真要上线了,改改 application.yml、执行一份 SQL 脚本,无缝切到 MySQL 8。
这叫"演示零依赖、生产可平滑"。今天咱把这套"切换艺术"彻底拆明白。
一、为什么需要"两套数据源"这种东西
先想一个问题:你写了个给同事演示的 Demo,难道还要让他先去装个 MySQL、建库建表、导数据?那演示还没开始,对方已经睡着了。
所以工程里常见的一种套路是:开发/演示用轻量数据库,生产用正经数据库。这就是"双数据源"的由来——不是一个程序同时连两个库,而是同一套代码,通过配置切换用哪个库。
本项目做了这样的角色分工:
| 场景 | 用的库 | 特点 |
|---|---|---|
| 本地跑、看效果、写接口 | H2 内存库 | 零安装、启动即建表、自带演示数据 |
| 真上线、有持久化需求 | MySQL 8 | 稳定、可多端共享、数据不丢 |
二、H2 是什么神仙数据库
H2 是一个纯 Java 写的关系型数据库,可以跑在内存里(memory),也可以落盘成文件。它最大的优点:不用单独安装一个数据库服务。Spring Boot 引入一个依赖,启动时就自带了一个数据库引擎。
对于 Demo 来说,这简直是天降福音——省去了"配环境两小时,写代码五分钟"的尴尬。
三、看懂那串"鬼画符"一样的连接串
打开 application.yml,数据源是这样配的(项目源码):
# 项目源码:application.yml(节选)
spring:
datasource:
url: jdbc:h2:mem:urm_ultra;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_DELAY=-1
driver-class-name: org.h2.Driver
username: sa
password: ""
那一长串 url 看着吓人,其实每个分号后面都是一个小开关。我逐个翻译成人话:
| 片段 | 含义 | 为什么要写 |
|---|---|---|
jdbc:h2: | 用 H2 的 JDBC 驱动 | 指定数据库类型 |
mem:urm_ultra | 内存模式,库名叫 urm_ultra | 数据存在内存,重启即清空 |
MODE=MySQL | 以 MySQL 兼容模式运行 | 让 H2 的 SQL 语法、类型尽量贴近 MySQL,避免建表/查询出错 |
DATABASE_TO_LOWER=TRUE | 库名、表名转小写 | MySQL 在 Linux 下默认大小写敏感,统一小写防坑 |
CASE_INSENSITIVE_IDENTIFIERS=TRUE | 标识符大小写不敏感 | 配合上一条,进一步减少跨库差异 |
DB_CLOSE_DELAY=-1 | 连接全关也不销毁库 | 防止测试时库被提前清空 |
重点圈一下
MODE=MySQL。这是这套"切换艺术"的灵魂开关——H2 故意装成 MySQL 的口音说话,这样 JPA 生成的建表语句、查询语句,在 H2 和 MySQL 上都能跑得通,切换时少踩语法差异的坑。
那内存库的"重启即清空"怎么办? 别慌,下面第四节的"自动建表 + 自动灌数据"就是答案。
四、ddl-auto=update:不用手写建表语句
传统开发,你得先 CREATE TABLE 把表建好。但 JPA 有个贴心设定——它可以根据实体类自动建表。配置长这样(项目源码):
# 项目源码:application.yml(节选)
jpa:
hibernate.ddl-auto: update
open-in-view: false
show-sql: false
properties.hibernate.default_schema: PUBLIC
ddl-auto: update 的意思是:启动时,Hibernate 拿着你的 @Entity 类,比对数据库里有没有对应的表,没有就建,字段对不上就补。你改了实体加了个字段,重启它自动给你加上——全程不用手写一条 DDL。
常用取值我们对比一下:
| 取值 | 行为 | 适用 |
|---|---|---|
none | 不碰表结构 | 生产常用(表由脚本管) |
update | 有则改、无则建 | 本项目演示用 |
create | 每次启动删了重建 | 纯测试,数据会没 |
create-drop | 启动建、关闭删 | 单元测试 |
validate | 只校验不改动 | 怕表结构漂变的场景 |
警告:生产环境千万不要用
update。你可不希望哪天 JPA 自作主张把生产表字段改了。生产该用none,表结构由 SQL 迁移脚本严格管起来(这点二次开发文档里强调过)。
open-in-view: false 这行也提一嘴:它关掉了"视图层还开着事务"的默认行为,能避免懒加载引发的 N+1 查询和长事务,是个好习惯开关。
五、DataInitializer:让 Demo 永远"有米下锅"
表是自动建了,但空库一个商品都没有,演示啥?这时候 DataInitializer 登场。它实现了 CommandLineRunner,意思是"Spring 容器起来后,自动跑一段初始化代码"。
项目里它的逻辑是(项目源码):
// 项目源码:如果商品表已非空,就不灌数据(避免重启重复)
if (productRepository.count() > 0) return;
然后它会一股脑塞进这些演示数据:
| 数据类别 | 内容举例 | 用途 |
|---|---|---|
| 类目 | 饮料 / 零食 / 日用品(3 条) | 商品分类展示 |
| 商品 | 可乐 8 元、矿泉水 3 元、薯片 7 元…(7 条,编码 P001~P007) | 货道售卖 |
| 设备 | DEV-0001 / DEV-0002 双门智能柜 | 售货机本体 |
| 货道绑定 | 10 条 slot 与商品、库存的绑定 | 模拟真实货道 |
| 无人车 | VEH-0001/0002,电量 85/60 | 物流执行 |
| 机械臂 | RBT-0001/0002,六轴 | 上货/分拣 |
| 前置仓 | WH-0001 中央前置仓 | 补货起点 |
也就是说,你啥都不用干,启动完直接 GET /app-api/device/list 就能拉到两台设备、七件商品。这才是"开箱即跑"的真实含义。
小坑提示:项目里判断"要不要初始化"只看了 Product 表是否为空。如果你手动把商品删了但没删其他表,可能出现数据不一致。Demo 阶段无所谓,生产化时要注意做整体校验。
顺带一提,H2 还贴心地开了个网页控制台:
h2.console.enabled: true # 访问路径 /h2-console
你本地启动后,浏览器开 http://localhost:8080/h2-console,用配置里的 sa 空密码就能连进去,直观地看表、查数据——调试神器。
六、切到 MySQL:就两步
轮子造好了,生产上车怎么办?其实就两步走。
第一步:执行建表脚本
项目里提供了一份 sql/urm_ultra.sql,共 15 张表,按域前缀划分(pms_ 商品、dms_ 设备、tos_ 订单、pss_ 支付、vec_ 车辆、rbt_ 机械臂、rep_ 补货配送)。风格是 InnoDB + utf8mb4 + 中文 COMMENT + 唯一键 uk_* 和关联索引 idx_*。
在 MySQL 里建好库、跑这份脚本,表结构就齐了(脚本不含种子数据,演示数据那套交给 H2,生产你自己导)。
第二步:改 application.yml
把 H2 那段换成 MySQL 的数据源(项目配置中预留了切换注释),核心就是把 url 指向你的 MySQL 实例,并切换驱动与方言相关项:
# 生产切换示意(项目源码注释形式提供)
spring:
datasource:
url: jdbc:mysql://localhost:3306/urm_ultra?useUnicode=true&characterEncoding=utf8mb8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: 你的账号
password: 你的密码
jpa:
hibernate.ddl-auto: none # 生产关掉自动建表!
改完重启,代码一行不改,底层库就从 H2 换成了 MySQL。这就是"双数据源切换艺术"——同一套 JPA 实体和 Repository,靠配置换引擎。
七、为什么要这么折腾,而不是直接用 MySQL
有同学问:反正最后都要 MySQL,一开始就用它不完了?
不行,原因有三:
- 降低他人上手门槛。别人想看你的 Demo,总不能先让他装 MySQL 吧?H2 让"克隆即运行"成为可能。
- 隔离演示数据与脏数据。内存库重启即空,你随便造数据、随便测,不污染任何东西。
- 验证"可移植性"。既然 H2 用 MySQL 兼容模式都能跑,说明你的表设计、SQL 写法没踩数据库私货,切真库更稳。
八、一张表记住切换要点
| 环节 | H2 演示 | MySQL 生产 |
|---|---|---|
| 依赖 | h2 (runtime) | mysql-connector-j (runtime) |
| 驱动 | org.h2.Driver | com.mysql.cj.jdbc.Driver |
| url 关键 | MODE=MySQL | 真实连接串 |
| ddl-auto | update(自动建表) | none(脚本管表) |
| 演示数据 | DataInitializer 自动灌 | 自行导入 |
| 控制台 | /h2-console 可看 | 用 Navicat 等客户端 |
九、黒漂小结
数据库切换这件事,本质是**“让对的人用对的库”**:新人、演示、测试用 H2 省心;上线、共享、持久用 MySQL 稳当。而 JPA + MODE=MySQL + ddl-auto 这套组合,正是把"切换成本"压到几乎为零的巧劲儿。
下一篇,我们要升级难度——聊聊这套系统对外暴露的 三类 API 是怎么隔离的(/admin-api、/app-api、/device-api),以及那个贯穿全场的统一返回体 Result 和分页 PageResult。接口设计好了,前端和设备端才能各忙各的、互不打扰。
(本篇完。文中"项目源码"标注处均可在 urm-server 工程的 application.yml、DataInitializer 与 sql/urm_ultra.sql 中对照。)

429

被折叠的 条评论
为什么被折叠?



