Java Swing+JDBC+MySQL实战:从零构建个人财务管理系统

📅 发布时间:2026/9/5 23:50:25
Java Swing+JDBC+MySQL实战:从零构建个人财务管理系统
简介这是一套基于Java Swing开发的个人财务收支管理系统源码面向Java初学者与桌面应用开发学习者聚焦C/S架构实践与MySQL数据库集成帮助理解GUI编程、CRUD操作及分层设计思想。资源包共111个文件含22个核心Java源文件如login.class、shourulist.class、zhichuadd.class等体现登录、收支增删查模块、80个编译后class文件、5个运行依赖jar包、1个建库脚本javacaiwu.sql及Eclipse项目配置文件整体压缩包仅2.91MB结构清晰、开箱即用。已有207人下载学习适合作为课程设计、毕业实训或Swing专项练习项目。用户可直接导入Eclipse需JDKMySQL环境使用admin/admin账号登录快速掌握用户管理、收支分类维护、密码修改等完整业务流程并通过源码深入理解DAO层封装、事件驱动机制与数据库连接池配置逻辑。1. 项目概述与核心价值最近在整理自己的开源项目仓库翻到了一个多年前用Java Swing写的个人财务收支管理系统。这个项目虽然技术栈现在看来有些“复古”但麻雀虽小五脏俱全从数据库设计、后端逻辑到桌面端GUI界面一应俱全完整地走完了一个CS架构桌面应用的开发全流程。对于正在学习Java、想从“Hello World”跨越到“完整项目”的开发者或者想了解如何将Swing、JDBC、Mysql这些技术点串联起来的朋友来说这个项目依然有很强的参考价值。它本质上解决的是个人或家庭场景下的流水记账、分类统计和财务可视化需求技术实现上则涵盖了使用Eclipse进行Java桌面开发、通过JDBC连接操作Mysql数据库、以及用Swing构建用户界面的核心技能。接下来我会把这个项目的设计思路、关键实现、踩过的坑以及一些扩展思考毫无保留地拆解一遍。2. 项目整体设计与技术选型考量2.1 为什么选择CS结构而非BS在项目启动时第一个要决定的就是架构。个人财务数据敏感且操作频繁BS浏览器/服务器架构虽然部署方便但需要常驻Web服务器对于个人单机使用来说略显笨重且数据通过网络传输即便是本地回环也会引入不必要的延迟和潜在风险。CS客户端/服务器架构则不同客户端我们的Swing程序和服务器端Mysql数据库可以安装在同一台机器上通过本地socket或管道通信速度极快且整个数据链路完全在用户掌控之下隐私性好。对于桌面端丰富的交互体验如拖拽、右键菜单、实时图表刷新Swing也能比当时的Web技术提供更流畅的响应。因此CS结构是这个桌面工具最自然的选择。2.2 技术栈深度解析Swing JDBC Mysql这个组合是Java桌面开发一个非常经典和稳健的搭配。Swing 作为Java基础的GUI工具包它足够轻量无需引入第三方庞大依赖所有组件从按钮、表格到树形结构一应俱全。虽然界面风格看起来比较“经典”但通过合理的布局管理器如BorderLayout,GridBagLayout和自定义渲染完全可以做出清晰、易用的界面。它的学习曲线相对平缓事件驱动模型ActionListener,MouseListener也易于理解。JDBC Java数据库连接的规范。我们不需要像MyBatis、Hibernate这样的ORM框架因为项目的数据模型账单非常简单。直接使用JDBC能让开发者更清晰地理解“如何执行一条SQL”、“如何处理结果集”、“事务如何控制”这是理解数据库操作本质的重要一步。我们选用Mysql Connector/J这个官方驱动包来建立连接。Mysql 选择它而不是SQLite或Access主要是考虑到几点。一是Mysql更接近生产环境学习它的使用对开发者职业发展有益。二是其功能完善支持事务、用户权限管理等即便我们这个单机项目用不到所有功能但规范的建表、索引实践是通用的。三是作为独立的数据库进程它更稳定能更好地处理并发虽然单用户场景并发压力小。注意 很多新手会纠结于使用最新框架但对于学习核心原理而言从这些“原始”技术入手反而能打下更坚实的基础。理解了JDBC再学MyBatis会感觉豁然开朗。2.3 系统核心功能模块设计围绕“收支管理”这个核心我们将系统划分为四个主要模块这也是数据库设计和程序结构划分的依据用户认证模块 负责登录验证。虽然单机但引入简单的用户表可以区分不同用户的账本也为未来可能的简易多用户支持留有余地。数据维护模块 这是核心中的核心包括收支记录的增、删、改、查CRUD。每条记录包含关键字段金额、类型收入/支出、分类餐饮、交通、工资等、日期、备注。统计查询模块 根据日期范围、分类等条件筛选记录并生成统计结果如某段时间的总收入、总支出、结余以及各分类的占比。报表展示模块 将统计结果以直观的形式展现例如用JTable展示明细用JFreeChart这是一个常用的开源图表库生成饼图来展示支出分类构成。3. 数据库设计与连接层实现3.1 数据库表结构设计在Mysql中我们主要设计了两张表用户表 (user)CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(255) NOT NULL COMMENT 密码存储MD5哈希值, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY username_unique (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;设计要点密码字段使用varchar(255)是为了存储经过MD5等哈希算法加密后的字符串明文密码绝不能直接存入数据库。username设为唯一键防止重复注册。收支记录表 (financial_record)CREATE TABLE financial_record ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL COMMENT 关联用户ID, amount decimal(10,2) NOT NULL COMMENT 金额保留两位小数, type tinyint(1) NOT NULL COMMENT 类型0-支出1-收入, category varchar(20) NOT NULL COMMENT 分类如餐饮、购物, record_date date NOT NULL COMMENT 记录日期, remark varchar(200) DEFAULT NULL COMMENT 备注, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_date (user_id,record_date), -- 复合索引 KEY idx_category (category), CONSTRAINT fk_record_user FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收支记录表;设计要点amount字段使用DECIMAL(10,2)精确表示金额避免浮点数精度问题。type用tinyint表示比字符串更节省空间。record_date使用DATE类型便于进行日期范围的查询和统计。建立了复合索引idx_user_date因为最常见的查询场景就是“查询某个用户在某个时间段的收支”这个索引能极大提升查询速度。设置了外键约束fk_record_user保证数据完整性当用户被删除时其所有记录也会级联删除。3.2 使用JDBC建立稳健的数据库连接在Java中我们通过一个DBUtil工具类来封装所有数据库操作。关键点在于管理Connection对象。public class DBUtil { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/finance_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD your_password; static { try { Class.forName(DRIVER); // 早期驱动需要显式加载高版本可省略但仍建议保留 } catch (ClassNotFoundException e) { e.printStackTrace(); } } // 获取数据库连接 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 关闭资源重载方法用于关闭不同组合的资源 public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt ! null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } // ... 其他重载的close方法 }实操心得 这里有几个坑点。第一连接URL中的serverTimezone参数必须设置否则可能遇到时区错误。第二关闭资源的顺序必须是ResultSet-Statement-Connection且每个关闭操作都要放在独立的try-catch里防止一个关闭异常导致后续资源无法关闭。第三实际项目中可以考虑使用DataSource连接池如HikariCP但对于初学者和小型项目DriverManager足够清晰。3.3 数据访问对象DAO模式的应用我们采用简单的DAO模式来组织数据库操作将每个实体类的CRUD操作封装在对应的DAO类中如UserDao和RecordDao。这有利于代码分离和后续维护。以RecordDao中插入一条记录的方法为例public class RecordDao { public boolean insertRecord(FinancialRecord record) { Connection conn null; PreparedStatement pstmt null; String sql INSERT INTO financial_record (user_id, amount, type, category, record_date, remark) VALUES (?, ?, ?, ?, ?, ?); try { conn DBUtil.getConnection(); pstmt conn.prepareStatement(sql); // 使用PreparedStatement防止SQL注入 pstmt.setInt(1, record.getUserId()); pstmt.setBigDecimal(2, record.getAmount()); pstmt.setInt(3, record.getType()); pstmt.setString(4, record.getCategory()); pstmt.setDate(5, new java.sql.Date(record.getRecordDate().getTime())); pstmt.setString(6, record.getRemark()); return pstmt.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } finally { DBUtil.close(conn, pstmt, null); // 确保资源被释放 } } // ... 其他查询、更新、删除方法 }关键点解析使用PreparedStatement 这是必须的它通过预编译和参数绑定能有效防止SQL注入攻击同时性能也更好。参数设置setDate方法需要传入java.sql.Date对象它是对java.util.Date的包装只保留日期部分符合我们数据库DATE类型的定义。异常处理 将SQLException在DAO层捕获并处理如打印日志、返回false而不是直接抛给界面层使业务逻辑更清晰。资源关闭 在finally块中关闭资源是保证资源释放的黄金准则。4. Swing GUI界面构建与事件处理4.1 主界面布局与组件选择主界面采用经典的BorderLayout结合JTabbedPane选项卡面板来组织功能。顶部NORTH 放置JMenuBar菜单栏包含“文件”、“编辑”、“统计”、“帮助”等菜单。中部CENTER 放置JTabbedPane包含以下几个主要标签页首页/仪表盘 显示本月概览收入、支出、结余、快速添加记录的简洁表单。流水明细 核心区域使用JTable展示所有收支记录支持排序、筛选。统计图表 使用JFreeChart库生成月度支出分类饼图、月度收支趋势折线图。分类管理 允许用户自定义收支分类如新增“宠物”、“学习”等分类。使用GridBagLayout或MigLayout第三方更灵活来管理复杂表单的布局比绝对定位更易于适配不同分辨率。4.2 使用JTable展示与编辑数据JTable是展示流水明细的核心组件。我们不会直接使用默认的数据模型而是自定义一个RecordTableModel继承AbstractTableModel这样可以更精细地控制数据的加载、显示和更新。public class RecordTableModel extends AbstractTableModel { private ListFinancialRecord data new ArrayList(); private final String[] columnNames {ID, 日期, 类型, 分类, 金额, 备注}; Override public int getRowCount() { return data.size(); } Override public int getColumnCount() { return columnNames.length; } Override public String getColumnName(int column) { return columnNames[column]; } Override public Object getValueAt(int rowIndex, int columnIndex) { FinancialRecord record data.get(rowIndex); switch (columnIndex) { case 0: return record.getId(); case 1: return new SimpleDateFormat(yyyy-MM-dd).format(record.getRecordDate()); case 2: return record.getType() 1 ? 收入 : 支出; case 3: return record.getCategory(); case 4: return record.getAmount(); case 5: return record.getRemark(); default: return null; } } // 更新表格数据的方法 public void setData(ListFinancialRecord newData) { this.data newData; fireTableDataChanged(); // 通知表格数据已改变触发重绘 } }然后在界面中RecordTableModel tableModel new RecordTableModel(); JTable recordTable new JTable(tableModel); // 从数据库加载数据并更新模型 ListFinancialRecord records recordDao.queryByUserId(currentUserId); tableModel.setData(records);技巧 为JTable添加ListSelectionListener监听行选择事件当用户点击某一行时可以将其数据填充到下方的编辑表单中方便修改。4.3 事件驱动编程与业务逻辑绑定Swing是事件驱动的。我们需要为按钮、菜单项等组件添加监听器ActionListener在监听器中调用相应的业务方法DAO操作。例如“保存”按钮的事件处理saveButton.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { // 1. 从表单各个输入框JTextField, JComboBox等获取数据 BigDecimal amount new BigDecimal(amountField.getText().trim()); int type (typeComboBox.getSelectedItem().equals(收入)) ? 1 : 0; String category (String) categoryComboBox.getSelectedItem(); Date date datePicker.getDate(); // 假设使用了JDatePicker组件 String remark remarkArea.getText(); // 2. 构造实体对象 FinancialRecord record new FinancialRecord(); record.setUserId(currentUserId); record.setAmount(amount); record.setType(type); record.setCategory(category); record.setRecordDate(date); record.setRemark(remark); // 3. 调用DAO层方法 boolean success recordDao.insertRecord(record); // 4. 根据结果反馈用户 if (success) { JOptionPane.showMessageDialog(mainFrame, 保存成功); refreshTableData(); // 刷新表格显示 clearForm(); // 清空表单 } else { JOptionPane.showMessageDialog(mainFrame, 保存失败请检查数据或连接。, 错误, JOptionPane.ERROR_MESSAGE); } } });注意事项 所有与Swing组件的交互如获取值、更新UI都必须在事件分发线程EDT上进行。在上述代码中actionPerformed方法本身就是在EDT中执行的。如果DAO操作非常耗时比如查询上万条记录应该使用SwingWorker在后台线程执行避免界面卡死。5. 核心业务功能实现详解5.1 收支记录的增删改查CRUD增删改查是系统的基石前面已经介绍了插入Create和查询Read。这里重点说一下更新Update和删除Delete的要点。更新操作 通常流程是用户在表格选中一条记录点击“编辑”按钮表单填充旧数据修改后点击“保存”。此时DAO层执行的是UPDATE语句需要特别注意WHERE条件要精确匹配记录ID避免误更新。// RecordDao中的更新方法 String sql UPDATE financial_record SET amount?, type?, category?, record_date?, remark? WHERE id? AND user_id?; // 设置参数时最后两个参数是id和user_id删除操作 提供“删除”按钮并在执行前进行二次确认防止误操作。int selectedRow recordTable.getSelectedRow(); if (selectedRow -1) { JOptionPane.showMessageDialog(frame, 请先选择要删除的记录); return; } int confirm JOptionPane.showConfirmDialog(frame, 确定要删除这条记录吗, 确认删除, JOptionPane.YES_NO_OPTION); if (confirm JOptionPane.YES_OPTION) { int recordId (int) tableModel.getValueAt(selectedRow, 0); // 获取ID boolean success recordDao.deleteRecordById(recordId, currentUserId); // ... 处理结果 }5.2 条件查询与数据统计功能统计功能依赖于复杂的SQL查询。例如查询当前用户某年某月的收支统计// RecordDao 中的统计方法 public MapString, BigDecimal getMonthlySummary(int userId, int year, int month) { MapString, BigDecimal summary new HashMap(); String sql SELECT type, SUM(amount) as total FROM financial_record WHERE user_id? AND YEAR(record_date)? AND MONTH(record_date)? GROUP BY type; // 执行查询... // 遍历ResultSet将 type1 的 total 放入 summary.put(income, total)type0的放入 summary.put(expense, total) return summary; // 返回包含 income 和 expense 的Map }更复杂的分类统计例如获取某月所有支出分类的金额SELECT category, SUM(amount) as total FROM financial_record WHERE user_id? AND type0 AND YEAR(record_date)? AND MONTH(record_date)? GROUP BY category ORDER BY total DESC;这个查询结果直接可以用于生成饼图的数据集。5.3 使用JFreeChart生成可视化报表首先需要在项目中引入JFreeChart库的JAR包。生成一个简单的月度支出分类饼图步骤如下准备数据 执行上面的分类统计SQL得到一个MapString, BigDecimal。创建数据集DefaultPieDataset dataset new DefaultPieDataset(); for (Map.EntryString, BigDecimal entry : categoryData.entrySet()) { dataset.setValue(entry.getKey(), entry.getValue()); }创建图表对象JFreeChart chart ChartFactory.createPieChart( 月度支出分类统计, // 标题 dataset, // 数据集 true, // 是否显示图例 true, // 是否生成工具提示 false // 是否生成URL链接 ); // 可以进一步定制图表样式如设置标题字体、颜色等将图表嵌入Swing界面ChartPanel chartPanel new ChartPanel(chart); chartPanel.setPreferredSize(new Dimension(600, 400)); // 将chartPanel添加到你的JPanel或JTabbedPane中 statisticsTab.add(chartPanel);这样一个交互式的饼图就生成了用户可以将鼠标悬停在扇区上查看具体数值。6. 项目部署、打包与常见问题排查6.1 使用Eclipse导出可执行JAR包开发完成后我们需要将项目打包成一个可以独立运行的JAR文件。在Eclipse中右键项目 -Export-Java-Runnable JAR file。Launch configuration 选择包含main方法的主类通常是你的登录窗口或主窗口类。Export destination 指定JAR文件的输出路径和名称。Library handling这是关键选择“Extract required libraries into generated JAR”。这会把所有依赖的第三方库如mysql-connector-java.jar,jfreechart.jar解压并打包进同一个JAR文件中生成一个“胖JAR”Fat JAR用户双击即可运行无需额外配置classpath。点击FinishEclipse会生成最终的JAR文件。6.2 确保数据库可移植性我们的程序连接的是本地的Mysql。为了让程序能在其他电脑上运行你需要考虑提供数据库初始化脚本 创建一个.sql文件包含创建数据库、用户、表结构的SQL语句。让用户在首次使用时执行。配置文件外置 不要将数据库连接参数URL、用户名、密码硬编码在DBUtil中。应该将它们放在一个外部的配置文件如config.properties里。# config.properties db.urljdbc:mysql://localhost:3306/finance_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai db.userroot db.password123456在程序启动时读取这个文件Properties props new Properties(); try (InputStream in DBUtil.class.getClassLoader().getResourceAsStream(config.properties)) { props.load(in); String url props.getProperty(db.url); // ... 其他参数 } catch (IOException e) { /* 处理异常 */ }这样用户只需修改配置文件而无需重新编译程序。6.3 常见问题与解决方案速查表在开发和运行过程中你几乎一定会遇到以下问题问题现象可能原因解决方案ClassNotFoundException: com.mysql.cj.jdbc.Driver1. 未导入MySQL驱动JAR包。2. 打包时库处理方式不对。1. 确保mysql-connector-java.jar在项目的构建路径Build Path中。2. 导出Runnable JAR时按6.1节选择正确的库处理方式。Communications link failure或Access denied1. MySQL服务未启动。2. 连接URL、用户名、密码错误。3. 用户权限不足。1. 启动MySQL服务net start mysql。2. 仔细检查config.properties中的配置。3. 使用MySQL命令行工具用配置的用户名密码登录测试。界面卡顿特别是查询大量数据时耗时操作阻塞了事件分发线程EDT。将耗时的数据库操作放入SwingWorker的后台线程执行在done()方法中更新UI。中文乱码1. 数据库字符集不是UTF-8。2. JDBC连接字符串未指定字符集。1. 确保数据库、表、字段的字符集为utf8mb4。2. 在JDBC URL中加上useUnicodetruecharacterEncodingUTF-8。日期显示或存储错误时区不一致。在JDBC URL中加上serverTimezoneAsia/Shanghai或你所在的时区。双击JAR文件无法运行1. 未关联Java运行时。2. 主类清单Manifest配置错误。1. 确保系统已安装JRE并通过java -jar your_app.jar命令运行。2. 检查导出的JAR包其META-INF/MANIFEST.MF文件中必须包含Main-Class条目指向正确的主类。Eclipse导出Runnable JAR时会自动处理。6.4 从项目中学到的核心经验回顾整个项目除了具体的Swing和JDBC语法更重要的是掌握了几种工程化的思维分层思想 将界面View、业务逻辑Service/Controller、数据访问DAO和实体Model分离。这能让代码结构清晰易于维护和测试。我们这个项目虽然小但依然遵循了MVC的雏形。异常处理与资源管理 数据库连接、文件流等都是宝贵资源必须确保在任何情况下包括发生异常时都能被正确关闭。try-with-resources语句Java 7是更现代和简洁的做法。SQL注入防护 只要涉及用户输入拼接SQL就必须使用PreparedStatement这是一个安全底线。用户体验 桌面程序要注重响应性。任何可能耗时的操作如网络请求、复杂查询都不应在EDT上执行避免界面“冻住”。配置化 将可能变化的部分如数据库连接信息抽取到配置文件中提高程序的适应性。这个项目就像一把钥匙帮你打开了Java应用开发的一扇门。理解了这些你再去看Spring Boot、JavaFX甚至安卓开发会发现很多底层概念是相通的。它可能不是最炫酷的项目但扎实地做完它你对Java编程的理解一定会深一个层次。本文还有配套的精品资源点击获取