news 2026/4/23 12:15:28

Forest项目数据库从Derby迁移至MySQL

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Forest项目数据库从Derby迁移至MySQL

Forest项目数据库从Derby迁移至MySQL

在开发Java EE应用时,我们常常会使用像Apache Derby这样的嵌入式数据库作为初期原型或教学示例的存储方案。它轻量、无需额外部署,非常适合快速上手——比如经典的示例项目Duke’s Forest就默认集成了Derby。但一旦进入生产环境,面对真实用户流量、高并发请求和长期数据维护需求,Derby的短板就暴露无遗:缺乏成熟的连接池管理、不支持高效的并发写入、运维工具薄弱……这时候,迁移到一个真正意义上的企业级数据库就成了必然选择。

MySQL,作为一个稳定、高性能且生态完善的关系型数据库,自然成为许多团队的首选。本文记录的是将Forest项目从Derby切换到MySQL的完整实践过程。这不是一次简单的配置替换,而是一次涉及数据源定义、JDBC驱动加载、SQL语法适配以及字符集统一的系统性调整。整个迁移过程基于GlassFish服务器运行环境,并结合EclipseLink(JPA实现)完成持久化层对接。


准备工作:搭建MySQL基础环境

任何迁移的第一步都是确保目标数据库就位。这里推荐使用MySQL 5.7或8.0版本,两者对Java应用的支持都较为成熟。

以Ubuntu为例,安装命令如下:

sudo apt update sudo apt install mysql-server sudo systemctl start mysql

安装完成后,登录MySQL并创建专用数据库与用户:

CREATE DATABASE forest CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'forest_user'@'localhost' IDENTIFIED BY 'secure_password'; GRANT ALL PRIVILEGES ON forest.* TO 'forest_user'@'localhost'; FLUSH PRIVILEGES;

这里有几个关键点值得注意:
- 使用utf8mb4字符集而非utf8,因为后者实际上只支持3字节UTF-8编码,无法正确处理emoji等4字节字符;
- 创建独立数据库用户,避免直接使用root账户,提升安全性;
- 权限范围限定为forest.*,遵循最小权限原则。


引入MySQL JDBC驱动

为了让Java应用能够连接MySQL,必须引入对应的JDBC驱动。如果你是通过Maven构建项目,在pom.xml中添加依赖是最简洁的方式:

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

但如果是在GlassFish这类应用服务器中部署WAR包,则需要手动将mysql-connector-java-x.x.x.jar复制到类路径下。通常位置为:

glassfish/domains/domain1/lib/

或者也可以放入应用的WEB-INF/lib/目录中,使其随应用一起打包部署。

⚠️ 常见问题提醒:如果启动时报错ClassNotFoundException: com.mysql.cj.jdbc.Driver,请立即检查jar包是否已正确放置,并确认文件名拼写无误。


修改数据源配置:从Derby到MySQL

Forest项目通过web.xml中的<data-source>元素声明了一个全局数据源:

<data-source> <name>java:global/ForestDataSource</name> <class-name>com.mysql.cj.jdbc.MysqlDataSource</class-name> <server-name>localhost</server-name> <port-number>3306</port-number> <database-name>forest</database-name> <user>forest_user</user> <password>secure_password</password> <property> <name>useSSL</name> <value>false</value> </property> <property> <name>allowPublicKeyRetrieval</name> <value>true</value> </property> <property> <name>autoReconnect</name> <value>true</value> </property> <property> <name>characterEncoding</name> <value>utf8</value> </property> <property> <name>serverTimezone</name> <value>UTC</value> </property> </data-source>

这个配置替换了原本指向Derby的数据源。其中几个参数值得特别说明:

  • com.mysql.cj.jdbc.MysqlDataSource是MySQL Connector/J 8.0+ 推荐使用的数据源类;
  • useSSL=false在本地测试环境中可以关闭SSL加密,但在生产环境中应启用并配置证书;
  • allowPublicKeyRetrieval=true解决了MySQL 8.0默认采用caching_sha2_password认证插件导致的公钥获取问题;
  • serverTimezone=UTC明确指定时区,防止因服务器与数据库时间不一致引发的时间字段异常。

只要该数据源名称与persistence.xml中引用的一致,JPA层就能自动接管后续操作。


persistence.xml 是否需要修改?

好消息是:不需要

Forest项目的persistence.xml内容如下:

<persistence-unit name="ForestPU" transaction-type="JTA"> <jta-data-source>java:global/ForestDataSource</jta-data-source> <properties> <property name="eclipselink.ddl-generation" value="create-tables"/> <property name="eclipselink.ddl-generation.output-mode" value="both"/> </properties> </persistence-unit>

可以看到,它仅指定了JTA事务类型和DDL生成策略,并未硬编码数据库方言(如<property name="eclipselink.target-database" value="Derby"/>)。这意味着EclipseLink会根据实际连接的数据库自动推断其类型并生成相应SQL语句。因此,只要数据源切换成功,框架层面几乎无需改动。


SQL脚本适配:跨数据库兼容的关键

这才是迁移中最容易出错的部分。Derby与MySQL在SQL语法上有诸多差异,尤其是主键自增、外键约束、索引命名等方面。原始的三个初始化脚本必须逐一调整。

drop.sql:安全清空旧表

为了规避外键依赖导致的删除失败,建议先禁用约束检查:

SET FOREIGN_KEY_CHECKS = 0; DROP TABLE IF EXISTS PERSON_GROUPS; DROP TABLE IF EXISTS PERSON; DROP TABLE IF EXISTS GROUPS; DROP TABLE IF EXISTS ORDER_DETAIL; DROP TABLE IF EXISTS CUSTOMER_ORDER; DROP TABLE IF EXISTS ORDER_STATUS; DROP TABLE IF EXISTS PRODUCT; DROP TABLE IF EXISTS CATEGORY; SET FOREIGN_KEY_CHECKS = 1;

这种方式比逐条判断是否存在更可靠,尤其适合一次性重建场景。


create.sql:建表结构全面重构

这是最核心的脚本。原Derby使用GENERATED BY DEFAULT AS IDENTITY实现自增主键,而在MySQL中应改为AUTO_INCREMENT。同时要显式设置字符集和排序规则。

以下是适配后的完整版本:

CREATE DATABASE IF NOT EXISTS forest CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE forest; SET NAMES utf8mb4; SET character_set_client = utf8mb4; CREATE TABLE CATEGORY ( ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(45) NOT NULL, TAGS VARCHAR(45) ); CREATE TABLE PERSON ( ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT, FIRSTNAME VARCHAR(50) NOT NULL, LASTNAME VARCHAR(100) NOT NULL, EMAIL VARCHAR(45) NOT NULL UNIQUE, ADDRESS VARCHAR(45) NOT NULL, CITY VARCHAR(45) NOT NULL, PASSWORD VARCHAR(100), DTYPE VARCHAR(31) ); CREATE TABLE GROUPS ( ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(50) NOT NULL, DESCRIPTION VARCHAR(300) ); CREATE TABLE PERSON_GROUPS ( GROUPS_ID INT NOT NULL, EMAIL VARCHAR(45) NOT NULL, PRIMARY KEY (GROUPS_ID, EMAIL) ); CREATE TABLE ORDER_STATUS ( ID INT NOT NULL PRIMARY KEY, STATUS VARCHAR(45) NOT NULL, DESCRIPTION VARCHAR(200) ); CREATE TABLE CUSTOMER_ORDER ( ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT, AMOUNT FLOAT(52) NOT NULL, DATE_CREATED TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, CUSTOMER_ID INT NOT NULL, STATUS_ID INT NOT NULL ); CREATE TABLE PRODUCT ( ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(45) NOT NULL, PRICE DECIMAL(10,2) NOT NULL, DESCRIPTION VARCHAR(145) NOT NULL, IMG VARCHAR(45), CATEGORY_ID INT NOT NULL, IMG_SRC LONGBLOB ); CREATE TABLE ORDER_DETAIL ( ORDER_ID INT NOT NULL, PRODUCT_ID INT NOT NULL, QTY INT NOT NULL, PRIMARY KEY (ORDER_ID, PRODUCT_ID) ); -- 外键约束统一后置添加 ALTER TABLE PERSON_GROUPS ADD CONSTRAINT FK_PERSON_GROUPS_PERSON FOREIGN KEY (EMAIL) REFERENCES PERSON(EMAIL); ALTER TABLE PERSON_GROUPS ADD CONSTRAINT FK_PERSON_GROUPS_GROUPS FOREIGN KEY (GROUPS_ID) REFERENCES GROUPS(ID); ALTER TABLE CUSTOMER_ORDER ADD CONSTRAINT FK_CUSTOMER_ORDER_ORDER_STATUS FOREIGN KEY (STATUS_ID) REFERENCES ORDER_STATUS(ID); ALTER TABLE CUSTOMER_ORDER ADD CONSTRAINT FK_CUSTOMER_ORDER_CUSTOMER FOREIGN KEY (CUSTOMER_ID) REFERENCES PERSON(ID); ALTER TABLE PRODUCT ADD CONSTRAINT FK_PRODUCT_CATEGORY FOREIGN KEY (CATEGORY_ID) REFERENCES CATEGORY(ID); ALTER TABLE ORDER_DETAIL ADD CONSTRAINT FK_ORDER_DETAIL_PRODUCT FOREIGN KEY (PRODUCT_ID) REFERENCES PRODUCT(ID); ALTER TABLE ORDER_DETAIL ADD CONSTRAINT FK_ORDER_DETAIL_ORDER FOREIGN KEY (ORDER_ID) REFERENCES CUSTOMER_ORDER(ID); -- 索引优化查询性能 CREATE INDEX SQL_PERSON_EMAIL_INDEX ON PERSON(EMAIL); CREATE INDEX SQL_PERSON_ID_INDEX ON PERSON(ID); CREATE INDEX SQL_PERSONGROUPS_EMAIL_INDEX ON PERSON_GROUPS(EMAIL); CREATE INDEX SQL_PERSONGROUPS_ID_INDEX ON PERSON_GROUPS(GROUPS_ID); CREATE INDEX SQL_ORDER_STATUS_ID_INDEX ON CUSTOMER_ORDER(STATUS_ID); CREATE INDEX SQL_ORDER_CUSTOMER_ID_INDEX ON CUSTOMER_ORDER(CUSTOMER_ID); CREATE INDEX SQL_ORDER_ID_INDEX ON CUSTOMER_ORDER(ID); CREATE INDEX SQL_PRODUCT_ID_INDEX ON PRODUCT(ID); CREATE INDEX SQL_ORDER_DETAIL_INDEX ON ORDER_DETAIL(ORDER_ID, PRODUCT_ID); CREATE INDEX SQL_ORDER_PRODUCT_ID_INDEX ON ORDER_DETAIL(PRODUCT_ID); CREATE INDEX SQL_ORDER_DETAIL_ID_INDEX ON ORDER_DETAIL(ORDER_ID);

几点经验总结:
- 主键全部使用AUTO_INCREMENT
- 图片字段改用LONGBLOB支持更大图像;
- 所有文本字段明确指定utf8mb4编码;
- 外键延迟添加,避免建表顺序冲突;
- 关键字段建立索引,提升查询效率。


data.sql:插入初始数据

此脚本相对简单,主要是验证字段长度和约束是否匹配。例如用户密码仍沿用MD5哈希值:

INSERT INTO PERSON (FIRSTNAME, LASTNAME, EMAIL, ADDRESS, CITY, PASSWORD, DTYPE) VALUES ('Robert','Exampler','robert@example.com','Example street','San Francisco','81dc9bdb52d04dc20036dbd8313ed055','Customer'); INSERT INTO PERSON (FIRSTNAME, LASTNAME, EMAIL, ADDRESS, CITY, PASSWORD, DTYPE) VALUES ('Admin','Admin','admin@example.com','Example street','Belmont','81dc9bdb52d04dc20036dbd8313ed055','Administrator');

注意:81dc9bdb52d04dc20036dbd8313ed055对应明文"1234",登录后台时输入即可。

分类信息也照常插入:

INSERT INTO CATEGORY (NAME, TAGS) VALUES ('Plants', 'Seeds, trees, flowers ...'); INSERT INTO CATEGORY (NAME, TAGS) VALUES ('Food', 'Foods, healthy items ...');

其余商品、订单状态等数据保持不变。


验证迁移结果:功能回归测试

重启GlassFish服务器后,观察日志输出是否有类似提示:

INFO: Connected to database: jdbc:mysql://localhost:3306/forest INFO: Creating tables from schema creation script... INFO: Successfully created table CATEGORY INFO: Successfully created table PERSON ...

这些日志表明JPA已成功连接MySQL,并开始执行DDL脚本。若中途未报错,则可进行前端功能验证:

  • 使用admin@example.com / 1234登录管理员后台;
  • 浏览商品列表是否正常显示;
  • 添加商品至购物车并提交订单;
  • 查看订单状态流转是否准确。

所有流程均能顺利完成,说明迁移达到了预期目标。


常见问题排查指南

问题现象可能原因解决方案
ClassNotFoundException: com.mysql.cj.jdbc.DriverJDBC驱动未加载检查jar包是否位于domains/domain1/lib/WEB-INF/lib/
Access denied for user 'forest_user'@'localhost'账号密码错误或权限不足重新授权并执行FLUSH PRIVILEGES;
Unknown database 'forest'数据库未创建手动执行CREATE DATABASE forest;
Table doesn't exist脚本未执行或执行失败检查create.sql路径及权限,可手动导入
Incorrect string value: \xE2\x8C\xA9...字符集不匹配确保表、字段、连接三者均为utf8mb4

遇到问题时,优先查看GlassFish的日志文件(通常在glassfish/domains/domain1/logs/server.log),其中往往包含详细的异常堆栈和SQL执行上下文。


这种从嵌入式数据库向生产级数据库迁移的做法,不仅适用于Forest项目,也广泛应用于各类基于Java EE + JPA + 应用服务器架构的传统Web系统。掌握这一套方法,意味着你已经具备了将原型项目推向生产的工程能力。

更重要的是,这次迁移揭示了一个通用原则:早期技术选型不必追求完美,但必须预留演进路径。Derby用于教学无可厚非,只要我们在设计之初就考虑到未来可能更换数据库,保持配置解耦、SQL抽象、驱动可插拔,就能在系统成长过程中从容应对变化。

如今,Forest项目已在MySQL支撑下运行稳定,响应更快,扩展性更强。而这只是迈向现代化架构的第一步——下一步,或许就是引入连接池优化、读写分离,甚至过渡到微服务与云原生数据库。

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

查重率和AI率双降:大学生必备的3款免费论文辅助工具

写的文章明明是一个字一个字敲的&#xff0c;提交后却被导师批“满屏机器味”&#xff1f;自查AIGC率飙到87%&#xff0c;改了3遍还是降不下来&#xff1f; 我踩过替换同义词越改越假、用错降AI率工具反升的坑&#xff0c;今天把9个原创免费降AI率技巧3款实测工具深度测评分享…

作者头像 李华
网站建设 2026/4/18 21:32:39

盘点3款好用的免费降AI软件,附知网实测对比报告

写的文章明明是一个字一个字敲的&#xff0c;提交后却被导师批“满屏机器味”&#xff1f;自查AIGC率飙到87%&#xff0c;改了3遍还是降不下来&#xff1f; 我踩过替换同义词越改越假、用错降AI率工具反升的坑&#xff0c;今天把9个原创免费降AI率技巧3款实测工具深度测评分享…

作者头像 李华
网站建设 2026/4/19 16:55:44

MT8870A无线综合测试仪架设与软件安装指南

MT8870A无线综合测试仪架设与软件安装指南 在现代无线通信设备的开发与生产中&#xff0c;测试效率和精度直接决定了产品上市速度与质量稳定性。面对5G、Wi-Fi 6、蓝牙5.0等多标准共存的复杂场景&#xff0c;传统单功能射频仪表已难以满足“一站式”测试需求。安立&#xff08…

作者头像 李华
网站建设 2026/4/18 19:58:33

基于多款软件的电池包仿真分析之旅

基于Hypermesh、Nastran、Abaqus、LS_Dyna和Femfat的电池包仿真分析 动力电池作为新能源车动力系统的重要组成部分,电池包作为电池的支撑载体,起到保护电池组正常工作的作用,其结构安全性不容忽视。 本套课程采用Hypermesh、Optistruct、Nastran、Abaqus、LS_Dyna和Femfat软件对…

作者头像 李华
网站建设 2026/4/19 4:24:21

智谱推出Open-AutoGLM究竟有何深意?(云手机AI架构大揭秘)

第一章&#xff1a;智谱推出Open-AutoGLM的战略深意重塑AI开发范式 智谱AI发布Open-AutoGLM&#xff0c;标志着其在通用语言模型自动化应用领域迈出了关键一步。该工具不仅支持自然语言驱动的代码生成&#xff0c;更实现了从任务理解到执行脚本输出的端到端自动化&#xff0c;极…

作者头像 李华
网站建设 2026/4/21 0:01:48

拆解徕本天猫精灵车载无线充支架

拆解VibeVoice-WEB-UI&#xff1a;一套面向长时多角色对话合成的AI语音系统架构 在播客制作间里&#xff0c;一位内容创作者正对着麦克风反复录制第三遍访谈音频——嘉宾语气不够自然、主持人接话节奏生硬、背景音还出了杂音。她叹了口气&#xff1a;“如果AI能像真人一样‘轮…

作者头像 李华