☰
JavaFX+MySQL+JDBC+MVC家庭记账系统实战:从环境搭建到避坑
2026/10/31 6:00:26 网站建设 项目流程

简介:基于JavaFX、MySQL、JDBC与MVC模式实现的家庭记账系统完整项目包,面向具备JavaSE基础、希望练习桌面应用与数据库整合的开发者。包内共58个文件,约986KB,其中包含16个class编译产物和15个java源码,便于对照运行与学习;另有8个fxml界面布局文件、9个xml配置、4个txt说明、1个SQL数据脚本及MySQL驱动jar包,并附带项目说明文档。目录按controller、dao、view、domain四层组织,业务逻辑与界面分离,分层结构清晰,方便逐个模块研究MVC职责。系统支持用户注册、登录,登录后可查看账单基本信息,并对账单进行新增、修改、删除操作,同时可将账单数据写入文件备份;数据经JDBC同步到MySQL,实现持久化存储,项目还使用了多线程处理,兼顾界面响应与后台任务。当前已有50人学习下载,适合用此项目理解JavaFX图形界面开发、JDBC数据库连接封装、MVC分层设计以及文件读写与并发的基本用法,是JavaSE阶段结合前端、数据库与工程结构的实战练习参考。

1. 家庭记账系统这套组合:JavaFX+MySQL+JDBC+MVC能解决什么

说句实话,大多数人拿到“基于JAVAFX+Mysql+JDBC+MVC实现的家庭记账系统.zip”这个标题时,第一反应是课设或者毕设跑不掉了。但真正把 zip 解开、要把项目跑起来的时候,会发现这套组合其实相当务实:JavaFX 撑起桌面界面,MySQL 存家庭收支流水,JDBC 负责 Java 程序与数据库之间的通信,MVC 把界面、业务和数据访问拆成三层,互不纠缠。对一名正在做 Java 课程设计、想补一个完整项目经验,或者要接手这类桌面项目的开发者来说,这四样东西恰好覆盖了一门完整桌面应用的最小闭环。这套选型最大的好处是:不依赖重型框架,单机部署,任何一台装了 JDK 和 MySQL 的机器都能跑,也方便后续换成 Web 端时把 DAO 层直接搬走。

2. 环境搭建先跑通空窗口:JDK、MySQL 与 Maven 依赖的版本搭配

把结论先放前面:JDK 17 + JavaFX 21 + MySQL 8.0.x + mysql-connector-j 8.x 是当前少出问题的组合。别去碰 JavaFX 2.2 或者 mysql-connector-java 5.1,版本匹配会让你多花一个下午,而且报错信息大概率看不懂。按下面三个顺序排好,后面写代码才有底气。

2.1 JDK 17 与 JavaFX 21:JavaFX 早就不在 JDK 里了,版本匹配是第一个坑

JDK 8 时代,JavaFX 还和 JDK 打成一个包,new Stage() 就能弹窗。JDK 11 开始 Oracle 把 JavaFX 拆成了独立模块 OpenJFX,这意味你的 JDK 目录下已经没有 javafx 相关的 jar。很多人的翻车现场就是这样:装了个 JDK 17,写代码时 import javafx.application.Application,编译直接报“程序包不存在”。

选型理由很简单:JDK 17 是 2021 年发布的 LTS 版本,社区维护成熟,IDEA 的默认支持也到位;JavaFX 21 对应的 Maven 坐标是 org.openjfx:javafx-controls:21,它会按你的操作系统拉对应平台包。在 pom.xml 里只需要显式写 javafx-controls 和 javafx-fxml,javafx-base 与 javafx-graphics 会被自动带进来。

<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <javafx.version>21</javafx.version> </properties> <dependencies> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> <version>${javafx.version}</version> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> <version>${javafx.version}</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> </dependency> </dependencies>

properties 里的 javafx.version 是一次定义全局生效,升级版本时只改这一处。mysql-connector-j 的 groupId 是 com.mysql,如果你在 pom 里写 mysql:mysql-connector-java:5.1.49,连接 MySQL 8 时会遇到认证插件不兼容的问题。编译级别设成 17 是为了让 IDEA 的 Java Compiler 和 Maven compiler 插件行为一致,避免“IDE 能跑、命令行报错”的尴尬。

2.2 MySQL 8.0 的安装与 my.ini 关键配置:字符集、认证插件和端口

Windows 上常见做法是下载 MySQL Installer 选 Server only,装完用 MySQL 8.0 Command Line Client 验证。我一般下载 8.0.44 的 MSI 安装包,Linux 上则用 rpm 方式安装。装完先做三件事:确认服务在运行、确认 root 能登录、把字符集统一成 utf8mb4。

需要改配置时,编辑 C:\ProgramData\MySQL\MySQL Server 8.0\my.ini,改完重启服务才生效。下面这段是针对家庭记账系统最小可用的配置:

[mysqld] port=3306 character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci default_authentication_plugin=mysql_native_password [client] default-character-set=utf8mb4

port 决定 JDBC URL 里要写的端口;character-set-server 决定新建数据库的默认字符集为 utf8mb4,与后面建库语句保持一致;default_authentication_plugin 是 8.0 兼容旧驱动的常用配置。如果你确定驱动版本在 8.0.11 以上,这一项可以不加,默认的 caching_sha2_password 更安全。修改配置后执行 net stop mysql && net start mysql 让参数生效。

注意:如果项目要求 MySQL 5.7,认证插件默认还是 mysql_native_password,连接参数里不需要动认证相关配置;8.0 则可能遇到认证插件兼容问题,这一条在后面的避坑章节细说。

2.3 Maven 依赖与最小启动类:先让空窗口跑起来再谈记账

写任何界面代码之前,先确认 JavaFX 能弹出一个空窗口、JDBC 能连上 MySQL,这两件事分开验证。JavaFX 最小启动类继承 javafx.application.Application,在 start 里创建 Stage:

public class App extends Application { @Override public void start(Stage stage) { stage.setTitle("FamilyBook"); stage.setScene(new Scene(new Label("Hello JavaFX"), 800, 600)); stage.show(); } public static void main(String[] args) { launch(args); } }

start 方法里 stage.setScene 绑定根节点,show 显示窗口;main 里调用 launch 启动 JavaFX 运行时。这一步能跑通说明 JavaFX 模块路径没问题。再用下面的代码验证 JDBC 连通性:

public class ConnectionTest { public static void main(String[] args) throws Exception { String url = "jdbc:mysql://localhost:3306/familybook?useSSL=false&serverTimezone=Asia/Shanghai"; try (Connection conn = DriverManager.getConnection(url, "root", "你的密码")) { Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT VERSION()"); rs.next(); System.out.println("MySQL version: " + rs.getString(1)); } } }

try-with-resources 会自动释放 Connection 和 Statement;URL 里 useSSL=false 是避免 MySQL 8 默认安全连接的握手报错,serverTimezone=Asia/Shanghai 是让驱动知道时区。如果 familybook 库还不存在,先执行 CREATE DATABASE familybook DEFAULT CHARACTER SET utf8mb4,建表语句放到后面 DAO 章节统一给。这两项连接参数是后面所有 DAO 的公共配置,建议封装连接时提取成常量,别在类里到处复制。

3. 按 MVC 拆包:三层结构如何让记账逻辑不再塞在按钮事件里

MVC 在 JavaWeb 里被讲烂了,放到 JavaFX 桌面端也一样成立:Model 管数据,View 管界面,Controller 管转发。JavaWeb 里 MVC 常和 Spring MVC 绑定,但桌面端做同样的事不需要引入 Spring 那套依赖,自己拆包就够。家庭记账系统规模不大,但如果不拆层,你很快会看到“一个 add 按钮里既读输入框又写 SQL 又弹窗”的写法,这种写法三个月后自己都看不懂。这一章先讲包怎么拆,再给实体和 FXML 的最小实现。

3.1 包结构与职责边界:model、view、controller 之外,为什么还要一个 dao 包

常见的包结构如下。我一般会在 controller 之外再加 util(数据库工具类)和 dao(数据访问对象)。MVC 理论里 dao 算 Model 的一部分,实际桌面项目里单独拎出来会让 controller 更薄。

familybook/ ├── App.java ├── controller/ │ ├── MainController.java │ └── StatisticsController.java ├── model/ │ ├── Category.java │ └── Transaction.java ├── dao/ │ ├── BaseDao.java │ ├── CategoryDao.java │ └── TransactionDao.java └── util/ ├── DbUtil.java └── DateUtil.java

controller 只处理界面事件:拿输入、调用 dao、刷新表格;dao 只做 SQL;model 只做实体字段。这样单人开发的价值是:改 SQL 不用动界面类,换界面不用碰 SQL,排查问题的时候定位很快。resources 下放与 controller 对应的 fxml 文件,命名保持一致,路径问题会少很多。

3.2 Model 层字段设计:金额用 BigDecimal,时间用 LocalDateTime

实体类字段直接决定表单校验和表格列显示,也和数据库表一一对应。两个核心实体按下面的写法:

public class Transaction { private Integer id; private Integer categoryId; private BigDecimal amount; // 金额,必须用 BigDecimal private Boolean isIncome; // true 收入,false 支出 private String remark; private LocalDateTime createTime; // getter / setter 省略 }

amount 若用 double,0.1+0.2 会存成 0.30000000000000004,虽然单笔差异小,但月累计会漂移;BigDecimal 的字符串构造函数可以避免这个问题。createTime 用 LocalDateTime,一是能直接绑定到 TableView 的日期列,二是 SQL 里能用 MONTH(create_time) 这类函数做聚合。isIncome 用布尔值区分收支,比字符串 type 查询更快,但数据库列名别叫 income,它是 SQL 关键字,建表时用 is_income 更稳。

Category 实体更简单:id、name、is_income。外键关系上,transaction 表保存 category_id,查询时用 JOIN 拿分类名称,不在 transaction 里冗余分类名字符串,避免分类改名时数据不一致。

3.3 View 与 Controller 绑定:FXML 里放控件,Controller 里只转发事件

FXML 文件和 Controller 的绑定由 fx:controller 属性完成,SceneBuilder 拖出来的界面会自动生成这个属性。最小布局:

<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <BorderPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="familybook.controller.MainController"> <top> <HBox spacing="10"> <TextField fx:id="remarkInput" promptText="备注" /> <TextField fx:id="amountInput" promptText="金额" /> <Button text="记一笔" onAction="#handleAdd" /> </HBox> </top> <center> <TableView fx:id="transactionTable" /> </center> </BorderPane>

fx:id 是控件在 Controller 里的注入名,onAction 指向 Controller 中的方法名。Controller 侧对应写法:

public class MainController { @FXML private TextField remarkInput; @FXML private TextField amountInput; @FXML private TableView<Transaction> transactionTable; @FXML public void handleAdd() { // 这里只做一件事:把界面数据交给 dao,不写 SQL String remark = remarkInput.getText(); BigDecimal amount = new BigDecimal(amountInput.getText()); Transaction t = new Transaction(); t.setRemark(remark); t.setAmount(amount); // new TransactionDao().insert(t); // 然后刷新表格 } }

@FXML 字段名必须与 FXML 里的 fx:id 完全一致,否则加载 FXML 时注入为空。handleAdd 方法里不含任何 JDBC 代码,这也是 MVC 拆分在桌面项目里的实际检验标准:界面类里搜不到 Connection 和 PreparedStatement,就算拆干净了。

4. JDBC 持久层落地:连接池、DAO 封装、SQL 注入防护与事务

DAO 层是家庭记账系统里最容易“随手写”又最容易返工的地方。先把连接管理做好,后面所有 SQL 都是在这个基础上的例行公事。这一章解决三件事:用什么拿连接、DAO 怎么封装、什么时候要手动开事务。

4.1 连接池选型:为什么课设也要用 HikariCP 而不是 DriverManager 裸连

DriverManager.getConnection 的问题不是不能用,而是每次请求都建立一条到 MySQL 的物理连接,记账系统虽然单机低并发,但界面操作频繁,响应时间会明显漂移。连接池解决的是“连接复用”:池里放着几条活连接,用的时候借出去,用完还回来。HikariCP 在 Spring Boot 里是默认连接池,类库很小,用在 JavaFX 里也不会有依赖冲突。

| 对比项 | DriverManager 裸连 | HikariCP 连接池 | | 连接建立时机 | 每次 getConnection 都新建 | 启动时初始化,后续复用 | | 连接上限控制 | 无,写崩了才知道 | maximumPoolSize 可配置 | | 连接失效处理 | 抛异常后代码自己兜 | 池内自动剔除失效连接 | | 侵入性 | JDK 自带 | 一个 jar 依赖 |

引入 com.zaxxer:HikariCP 依赖后,封一个 DbUtil 出来,整个项目只从这一个地方拿连接:

public class DbUtil { private static final HikariDataSource DATA_SOURCE; static { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/familybook?useSSL=false&serverTimezone=Asia/Shanghai"); config.setUsername("root"); config.setPassword("你的密码"); config.setMaximumPoolSize(5); config.setConnectionTimeout(3000); DATA_SOURCE = new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return DATA_SOURCE.getConnection(); } }

maximumPoolSize 对单机桌面应用 5 就够,设太大反而浪费 MySQL 连接数;connectionTimeout 3000 毫秒是借连接的等待上限,数据库假死时这里会先报超时而不是界面卡死。注意 HikariDataSource 是单例,static 块初始化后整个进程共用。如果项目对第三方依赖有限制,退回 DriverManager 时,至少把 URL 和账号密码抽到常量,别散落在每个 DAO 里。

提示:如果项目明确不允许引入第三方依赖,才退回 DriverManager 裸连,但连接获取必须收口到工具类,禁止在各 DAO 里自己 new 连接。

4.2 DAO 封装:BaseDao 管连接,具体 DAO 只管 SQL 与参数

具体 DAO 的重复劳动只有两步:拿连接、执行 SQL。BaseDao 把这两步的样板代码收走,调用方只给 SQL 和参数:

public class BaseDao { protected void executeUpdate(String sql, Object... params) throws SQLException { try (Connection conn = DbUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } ps.executeUpdate(); } } protected <T> List<T> query(String sql, RowMapper<T> mapper, Object... params) throws SQLException { List<T> list = new ArrayList<>(); try (Connection conn = DbUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } ResultSet rs = ps.executeQuery(); while (rs.next()) { list.add(mapper.map(rs)); } } return list; } }

executeUpdate 和 query 都用了 try-with-resources,连接、预编译语句、结果集全部自动关闭。RowMapper 回调让每个 DAO 只关心 rs 到实体的转换。这里必须用 PreparedStatement 而不是 Statement,否则用户输入的备注一旦包含 ' or '1'='1 就会构成 SQL 注入,PreparedStatement 的占位符机制会把参数当纯数据传过去,拼接 SQL 字符串在数据库项目里是红线。

具体 DAO 举例:

public class CategoryDao extends BaseDao { public List<Category> findAll() throws SQLException { return query("SELECT id, name, is_income FROM category ORDER BY id", rs -> { Category c = new Category(); c.setId(rs.getInt("id")); c.setName(rs.getString("name")); c.setIncome(rs.getBoolean("is_income")); return c; }); } }

ORDER BY id 让分类列表顺序稳定,UI 上不会每次刷新乱跳。query 方法的第二个参数是 RowMapper 实现,一行记录映射一个 Category。如果你的分类有父子层级,这里再额外加 parent_id 字段和递归查询,家庭记账一层分类就够,别过度设计。

4.3 事务边界:记账与余额变动必须在同一个 Connection 里 commit 或 rollback

记一笔支出至少涉及两步:往 transaction 表插流水、更新 account 表的余额。两步在默认自动提交模式下是两条独立事务,如果第二步失败,流水就悬空了。解决办法是手动控制事务边界:

public void addOutcome(Transaction t) throws SQLException { Connection conn = DbUtil.getConnection(); try { conn.setAutoCommit(false); try (PreparedStatement ps = conn.prepareStatement( "INSERT INTO transaction(category_id, amount, is_income, remark, create_time) VALUES(?,?,?,?,?)")) { ps.setInt(1, t.getCategoryId()); ps.setBigDecimal(2, t.getAmount()); ps.setBoolean(3, false); ps.setString(4, t.getRemark()); ps.setTimestamp(5, Timestamp.valueOf(LocalDateTime.now())); ps.executeUpdate(); } try (PreparedStatement ps = conn.prepareStatement( "UPDATE account SET balance = balance - ? WHERE id = ?")) { ps.setBigDecimal(1, t.getAmount()); ps.setBigDecimal(2, t.getAccountId()); ps.executeUpdate(); } conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); conn.close(); } }

setAutoCommit(false) 之后,两条 SQL 都在同一个事务里;commit 成功后一起落盘,任何一条失败走 rollback 回滚。finally 里把 autoCommit 恢复为 true 再 close,避免连接池里残留事务状态。建表时表引擎必须用 InnoDB,MyISAM 不支持事务,这个细节写错会让 commit/rollback 静默失效。

5. 避坑记录:数据库连接、FXML 加载和 TableView 刷新里的五个翻车点

这套组合踩过的坑集中在三个位置:连不上数据库、FXML 加载失败、表格刷新不生效。每一条按现象、原因、解决写清楚,最后给一张汇总表方便检索。

5.1 MySQL 8 连接报错:SSL 握手失败与 serverTimezone

现象:用 DriverManager.getConnection 连接 MySQL 8 时,抛 CommunicationsException: Communications link failure,或者 SSLHandshakeException: No appropriate protocol。

原因:MySQL 8 默认开启 useSSL=true,驱动端会尝试 SSL 握手,而开发环境的 MySQL 证书与驱动默认信任库不一致。另一类常见报错是 The server time zone value 'Öйú' is unrecognized,这是服务器时区未被 MySQL 正确识别,多发生在 Windows 中文系统上。

解决:连接 URL 加两个参数:useSSL=false 关掉 SSL,serverTimezone=Asia/Shanghai 显式指定时区。如果驱动是 8.0.13 之前的老版本,还需要 allowPublicKeyRetrieval=true,新驱动一般不需要。

String url = "jdbc:mysql://localhost:3306/familybook?useSSL=false&serverTimezone=Asia/Shanghai";

这条参数要贯穿整个项目:DbUtil、测试类和各 DAO 一起改。别只在某一个测试类里改,否则你会看到“单测能跑,界面连不上”的诡异现象。

5.2 FXML 加载报“Location is not set”

现象:FXMLLoader.load 抛 javafx.fxml.LoadException: Location is not set,而且没有更细的错误信息。

原因:FXMLLoader 是通过类路径找 FXML 文件的,很多人写成 getResource("main.fxml") 或 new File("src/main/resources/main.fxml")。前者找不到,因为 Maven 构建后 FXML 在 target/classes 下而不是源码目录;后者在打成 jar 后路径完全失效。

解决:FXML 放 resources 目录,加载用 getClass().getResource("/fxml/main.fxml"),注意前面带斜杠表示从 classpath 根开始。如果 Controller 和 FXML 在同一级目录,也可以写 getResource("main.fxml"),但打包后路径容易飘,我习惯统一用 classpath 根路径。排查时直接看 target/classes 下有没有对应的 fxml 文件,这是最快定位手段。

5.3 TableView 数据不刷新:你用的是普通 List

现象:调用了 table.setItems(list) 后第一次有数据,之后增删改了 list,界面纹丝不动。

原因:TableView 只监听 ObservableList 的变更事件。setItems 传入普通 ArrayList 后,TableView 持有的仍然是那个 ArrayList 的引用,你往 list 里 add 时它根本不知道。

解决:用 FXCollections.observableArrayList() 构造列表,增删改走这个可观察列表的 API:

ObservableList<Transaction> data = FXCollections.observableArrayList(); tableView.setItems(data); // 之后新增记录用 data.add(tx),删除用 data.remove(tx),界面自动刷新

可观察列表有 item 增删时会触发 TableView 的重绘,不需要手动刷新。注意如果你从 DAO 查询结果后丢进 new ArrayList 再 setAll,那是整体替换,也能刷新,但后续增量修改时就会再次踩坑;统一用可观察列表是更省心的习惯。

5.4 入库中文变成问号

现象:界面显示中文正常,插入 MySQL 后 select 出来全是??。

原因:建库时字符集不是 utf8mb4,或者连接 URL 没带 characterEncoding 参数。MySQL 8 默认 character-set-server 是 utf8mb4,但如果你手工 CREATE DATABASE 却没指定,会继承服务器默认值;老版本 MySQL 默认可能是 latin1。

解决:建库语句显式指定:

CREATE DATABASE familybook DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

utf8mb4 是 utf8 的超集,能存 emoji,连接参数里再补 characterEncoding=utf8 双保险。如果库已经建了,用 ALTER DATABASE familybook DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci 改一遍,新表会继承,旧表还需要 ALTER TABLE 转字符集。

5.5 排查顺序与参数汇总

| 现象 | 根因 | 立即检查项 | | 连接超时 | 端口、服务、防火墙 | netstat -an 查 3306,服务管理器查 MySQL80 | | SSL 握手失败 | useSSL 参数 | URL 补 useSSL=false | | 时区报错 | serverTimezone 缺失 | URL 补 serverTimezone=Asia/Shanghai | | FXML 无法加载 | 资源路径不对 | 看 target/classes 下有没有对应 fxml | | 表格不刷新 | 使用了普通 List | 改 ObservableList |

按照“先库后界面”的顺序排查:先确认 MySQL 服务存在并且能命令行登录,再测 JDBC URL,最后加载界面。很多项目是三者问题叠加,改一个就以为解决了,结果换了台机器问题又冒出来。

6. 进阶与交付:SQL 聚合做统计、JavaFX 图表展示、打包为 zip 前的三件事

6.1 用 SQL 聚合代替 Java 内存统计:GROUP BY 一次拿到月度支出

统计月度支出时,常见做法是把全表查出来再在 Java 里用 Map 累加,这在小数据量下看不出差别,但流水多了就会明显拖慢界面。正确做法是把分组求和交给数据库:

SELECT DATE_FORMAT(create_time, '%Y-%m') AS month, SUM(amount) AS total FROM transaction WHERE is_income = 0 GROUP BY DATE_FORMAT(create_time, '%Y-%m') ORDER BY month;

DATE_FORMAT 把时间格式化成年月字符串,GROUP BY 按月份聚合,SUM 求和,ORDER BY 保证结果按月排序。Java 端只负责接收结果集并填充到界面。分类占比、收支对比这类统计,都改走这条路径,别再用 Java 做聚合。

6.2 让统计结果可见:分类占比 PieChart

SQL 聚合结果直接喂给 JavaFX 图表组件。以按分类统计支出为例:

ObservableList<PieChart.Data> pieData = FXCollections.observableArrayList(); while (rs.next()) { pieData.add(new PieChart.Data(rs.getString("name"), rs.getDouble("total"))); } PieChart chart = new PieChart(pieData);

PieChart.Data 的 name 显示图例,value 决定扇区大小。用 observable 列表可以把 SQL 聚合出来的结果直接喂给图表,切换统计周期时只更新列表数据,图表自动重绘。这一步和第 5 章的 ObservableList 是同一个机制,看到数据变化自动触发重绘,就是这个属性在起作用。

6.3 打包为 zip 前的三件事:数据库初始化脚本、依赖一致性与 jpackage 产物

交付一个 zip,应该让接手的人能按 README 一路跑起来。三件事别省:第一,把建库建表 SQL 单独放 init.sql,包含 CREATE DATABASE、CREATE TABLE 和几条示例分类数据;第二,用 maven-shade-plugin 打成带依赖的 fat jar,再用 JDK 自带的 jpackage 生成免安装应用镜像,别发一堆散落的 class 文件;第三,README 里写明 JDK 17、JavaFX 21、MySQL 8.0+ 和初始账号密码。

我之前吃过亏:只发了 jar 不附 SQL,接手的人自己建库,字段对不上,最后又退回旧版本重做。所以现在会把建库语句、示例数据和连接参数写在同一份文档里。打包前先 mvn clean package 确认编译无告警,再 jpackage --input target --main-class familybook.App 生成应用目录,最后压缩成 zip。检查 zip 里是否真的包含 mysql-connector-j 与 JavaFX 模块,这是最容易落空的依赖。

这份组合跑通一次之后,再做类似的小型桌面项目,我会把数据库字段设计与实体类类型先定死,再动界面,返工率能少一半。上面的避坑清单不能说覆盖了所有场景,但 SSL 握手、字符集和 ObservableList 这三关,十次项目有八次踩,提前写好能省下不少“玄学调参”的时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询