尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Java Swing+MySQL个人财务系统开发:从MVC设计到CS架构实践

Java Swing+MySQL个人财务系统开发:从MVC设计到CS架构实践 简介在软件开发领域CSClient-Server架构是理解现代分布式系统的基础模型其核心在于客户端与服务器之间的请求-响应通信机制。通过JDBCJava Database Connectivity这一Java标准API应用程序能够与关系型数据库如MySQL建立稳定连接执行SQL语句并处理结果集实现数据的持久化操作。这种技术组合的价值在于它让开发者能够专注于业务逻辑与数据交互的本质是掌握企业级应用数据层开发的必经之路。在桌面应用开发场景中Java Swing作为Java标准库的GUI工具包结合MVCModel-View-Controller设计模式能够构建出结构清晰、职责分离的图形界面程序。本文以【个人财务管理系统】这一经典练手项目为例系统阐述了如何运用【Swing界面开发】与【JDBC数据库操作】技术实现一个涵盖增删改查、条件查询与统计报表的完整CS架构桌面应用为Java初学者提供了一条从理论到实践的清晰路径。1. 项目概述与核心价值最近在整理自己的开源项目仓库翻到了一个多年前用 Java Swing 写的个人财务收支管理系统。这个项目虽然技术栈看起来有些“复古”但麻雀虽小五脏俱全完整地走通了从数据库设计、后端逻辑到桌面客户端开发的整个流程。对于刚学完 Java 基础想找一个综合性的小项目来练手理解 CSClient-Server结构、Swing 界面开发以及数据库操作的朋友来说它依然是一个非常好的学习样本。我自己当年也是通过做这类管理系统才真正把 JDBC、事件监听、MVC 思想这些书本上的概念给串了起来。今天我就把这个项目的设计思路、关键实现细节以及那些年踩过的坑系统地梳理一遍希望能给需要的朋友提供一个清晰的参考路线图。简单来说这是一个基于 Java Swing 图形界面开发的桌面端应用。用户通过它来记录每天的收支情况比如工资收入、餐饮支出、交通费用等并可以按时间、按类别进行查询和统计最终以表格或图表的形式直观展示自己的财务状况。项目采用典型的 CS 结构客户端Client就是我们用 Swing 写的窗口程序服务端Server则是 Mysql 数据库两者通过 JDBC 进行通信。开发工具是 Eclipse。整个项目的核心就在于如何用 Java 这一门语言优雅地连接起前端的用户交互与后端的数据持久化。2. 系统整体设计与技术选型考量2.1 为什么选择 Java Swing Mysql CS 结构这是一个经典的学生项目或入门级个人项目组合。选择它背后有几层很实际的考虑。首先技术栈的纯粹性与学习价值。Java 语言本身是面向对象的典范Swing 是 Java 标准库JFC的一部分无需引入任何第三方界面库保证了项目的纯粹性。对于学习者而言这意味着你可以专注于 Java 核心语法、事件驱动编程模型以及面向对象设计而不必被五花八门的框架分散精力。Mysql 作为最流行的开源关系型数据库之一其安装、使用资料丰富JDBC 连接方式标准。通过这个项目你能扎实掌握 SQL 语句编写、JDBC API 的使用以及数据库连接池哪怕是最简单的的基本概念。CS 结构则是软件架构的基石理解客户端如何请求、服务端如何响应对于后续学习 B/S 架构或更复杂的分布式系统有直接的铺垫作用。其次开发环境轻量与可控。Eclipse 作为一个成熟的 IDE对 Java 标准项目的支持非常友好其内置的调试、代码提示、数据库连接工具足以支撑本项目开发。整个项目依赖极少基本上只需要 JDK 和一个 Mysql 的 JDBC 驱动 jar 包环境搭建简单避免了因依赖冲突导致的各种诡异问题让开发者能更聚焦于业务逻辑本身。最后项目复杂度适中功能闭环。一个财务管理系统核心的“增删改查”CRUD操作、简单的统计报表、基础的数据验证这些功能点恰好覆盖了软件开发中最常见的要求。它不像一个计算器那样过于简单也不至于像电商系统那样庞大是一个能让你在短时间内体验到完整开发流程并获得成就感的“甜点”型项目。2.2 系统架构与模块划分尽管项目不大但良好的结构是代码可维护性的基础。我采用的是改良版的 MVCModel-View-Controller模式在桌面 Swing 开发中这通常表现为Model模型层对应数据库中的表和业务实体。这里我们会创建与数据库表结构对应的 Java Bean 类如IncomeRecord、ExpenseRecord或统一的FinanceRecord以及负责数据库访问的 DAOData Access Object类。View视图层由 Swing 的各种组件JFrame, JPanel, JTable, JButton, JTextField 等构建的用户界面。它的职责是展示数据并接收用户的操作输入。Controller控制层这是连接 View 和 Model 的桥梁。通常是界面类的事件监听器如ActionListener。当用户点击按钮时监听器被触发它从 View 中获取数据调用相应的 ModelDAO进行业务处理然后再将处理结果更新到 View 上。在实际项目中我常常会将 DAO 和数据库连接工具单独抽离。一个典型的包package结构如下com.finance.system ├── main │ └── MainApp.java // 程序入口 ├── view │ ├── MainFrame.java // 主窗口 │ ├── AddRecordDialog.java // 添加记录对话框 │ └── QueryPanel.java // 查询面板 ├── model │ ├── entity │ │ └── FinanceRecord.java // 财务记录实体类 │ └── dao │ ├── FinanceRecordDAO.java // 数据访问接口 │ └── impl │ └── FinanceRecordDAOImpl.java // 数据访问实现 ├── util │ ├── DBUtil.java // 数据库连接与关闭工具类 │ └── DateUtil.java // 日期处理工具类 └── service (可选) └── FinanceService.java // 业务逻辑层封装复杂操作注意对于小型项目不一定严格遵循多层架构但将数据库操作DAO与界面逻辑分离是必须的好习惯。这能让你在未来需要更换数据库或重构界面时代价最小。3. 数据库设计与核心表结构解析数据库是系统的基石设计的好坏直接影响到后续编程的复杂度。3.1 核心表财务记录表finance_record经过权衡我没有将收入和支出分成两张表而是设计了一张统一的表。这样在做全量统计和查询时会更方便。表结构设计如下CREATE TABLE finance_record ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 主键ID, type tinyint(1) NOT NULL COMMENT 类型1-收入 2-支出, category varchar(50) NOT NULL COMMENT 类别如工资、餐饮、交通, amount decimal(10,2) NOT NULL COMMENT 金额保留两位小数, record_date date NOT NULL COMMENT 记录日期, remark varchar(255) DEFAULT NULL COMMENT 备注, create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_date (record_date), KEY idx_type_category (type,category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT财务记录表;设计思路与字段解析id: 自增主键是每条记录的唯一标识。AUTO_INCREMENT让数据库自动管理ID生成Java 代码中插入数据时无需关心。type: 使用tinyint而不是varchar存储类型。1表示收入2表示支出。这样设计的好处是存储效率高查询和统计时例如SUM(CASE WHEN type1 THEN amount ELSE 0 END)性能更好且不易出错避免了字符串拼写错误。category: 类别字段。这里用了varchar(50)。一个更优的设计是使用字典表category_dict来管理类别通过外键关联。但对于第一个版本的个人系统直接存储字符串更简单直观。你可以预先在程序中定义一个常用类别列表供用户选择。amount: 金额字段使用decimal(10,2)。这是处理货币金额的标准选择它能精确存储小数点后两位避免浮点数类型如float,double带来的精度丢失问题。(10,2)表示总共10位数字其中小数位占2位最大可存储99999999.99。record_date: 记录发生的日期使用date类型。区分于create_time记录创建到系统的时间。统计报表通常按业务发生日期record_date来汇总。remark: 备注可选字段为用户提供额外的说明空间。create_time: 记录创建时间默认值为当前时间戳。用于审计了解数据何时入库。索引为record_date和(type, category)分别建立了索引。因为我们的查询很可能按日期范围筛选或按类型和类别进行过滤和分组统计。合理的索引能大幅提升查询速度尤其是在记录量增多以后。3.2 实体类Java Bean映射数据库表设计好后需要在 Java 中创建对应的实体类。这是一个简单的 POJOPlain Old Java Object。package com.finance.system.model.entity; import java.math.BigDecimal; import java.util.Date; public class FinanceRecord { private Integer id; private Integer type; // 1:收入, 2:支出 private String category; private BigDecimal amount; // 使用BigDecimal对应数据库的decimal private Date recordDate; private String remark; private Date createTime; // 无参构造器、全参构造器、getter、setter、toString() 方法 // 这里省略具体代码可以使用IDE如Eclipse的Source-Generate...功能快速生成 }实操心得金额字段一定要用BigDecimal而不是Double或Float。在初始化BigDecimal时推荐使用字符串参数的构造器new BigDecimal(100.50)或者使用BigDecimal.valueOf(double val)方法避免直接使用double构造器可能带来的精度问题。4. 核心功能模块实现详解4.1 数据库连接工具类DBUtil.java这是所有数据库操作的起点。一个健壮、简洁的连接工具类至关重要。package com.finance.system.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBUtil { // 数据库配置参数通常应该放在配置文件中如.properties private static final String URL jdbc:mysql://localhost:3306/finance_db?useUnicodetruecharacterEncodingUTF-8useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD your_password; static { try { // 1. 加载驱动类 (MySQL 8.0 驱动类名是 com.mysql.cj.jdbc.Driver) Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new RuntimeException(加载数据库驱动失败, e); } } /** * 获取数据库连接 */ public static Connection getConnection() { Connection conn null; try { conn DriverManager.getConnection(URL, USER, PASSWORD); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(获取数据库连接失败, e); } return conn; } /** * 关闭连接资源重载方法用于关闭不同的组合 */ 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();} } public static void close(Connection conn, Statement stmt) { close(conn, stmt, null); } }关键点解析驱动加载Class.forName(...)在静态代码块中执行确保程序启动时驱动就被注册到DriverManager。连接字符串URL 中的参数很重要。useUnicodetruecharacterEncodingUTF-8保证中文不乱码。useSSLfalse本地开发通常关闭SSL连接避免警告。serverTimezoneAsia/Shanghai非常重要指定服务器时区避免因时区不一致导致的日期时间错误。这是使用高版本 MySQL 驱动时最常见的坑之一。资源关闭在finally块或 try-with-resources 语句中关闭ConnectionStatementResultSet是必须的以防止资源泄漏。这里封装成工具方法使业务代码更简洁。注意关闭顺序先开后关ResultSet - Statement - Connection。4.2 数据访问层DAO实现DAO 层封装了所有对finance_record表的操作。这里以FinanceRecordDAO接口及其实现类为例。首先定义接口package com.finance.system.model.dao; import com.finance.system.model.entity.FinanceRecord; import java.util.List; public interface FinanceRecordDAO { // 增 int insert(FinanceRecord record); // 删 int deleteById(Integer id); // 改 int update(FinanceRecord record); // 查根据ID FinanceRecord selectById(Integer id); // 查所有 ListFinanceRecord selectAll(); // 按条件查询日期范围、类型、类别 ListFinanceRecord selectByCondition(FinanceRecord condition, java.util.Date startDate, java.util.Date endDate); // 统计总收入、总支出、结余 java.util.MapString, BigDecimal getStatistics(java.util.Date startDate, java.util.Date endDate); }然后是具体的实现类FinanceRecordDAOImpl。这里重点展示插入和条件查询方法package com.finance.system.model.dao.impl; import com.finance.system.model.dao.FinanceRecordDAO; import com.finance.system.model.entity.FinanceRecord; import com.finance.system.util.DBUtil; import java.math.BigDecimal; import java.sql.*; import java.util.ArrayList; import java.util.List; public class FinanceRecordDAOImpl implements FinanceRecordDAO { Override public int insert(FinanceRecord record) { Connection conn null; PreparedStatement pstmt null; ResultSet rs null; int generatedId -1; String sql INSERT INTO finance_record(type, category, amount, record_date, remark) VALUES(?, ?, ?, ?, ?); try { conn DBUtil.getConnection(); // 使用 PreparedStatement防止SQL注入且方便设置参数 pstmt conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); pstmt.setInt(1, record.getType()); pstmt.setString(2, record.getCategory()); pstmt.setBigDecimal(3, record.getAmount()); // 将 java.util.Date 转换为 java.sql.Date pstmt.setDate(4, new java.sql.Date(record.getRecordDate().getTime())); pstmt.setString(5, record.getRemark()); int affectedRows pstmt.executeUpdate(); if (affectedRows 0) { // 获取自增生成的主键ID rs pstmt.getGeneratedKeys(); if (rs.next()) { generatedId rs.getInt(1); record.setId(generatedId); // 回填ID到实体对象 } } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, pstmt, rs); } return generatedId; // 返回生成的ID-1表示失败 } Override public ListFinanceRecord selectByCondition(FinanceRecord condition, java.util.Date startDate, java.util.Date endDate) { ListFinanceRecord list new ArrayList(); Connection conn null; PreparedStatement pstmt null; ResultSet rs null; // 动态构建SQL避免字符串拼接漏洞 StringBuilder sql new StringBuilder(SELECT * FROM finance_record WHERE 11 ); ListObject params new ArrayList(); if (condition ! null) { if (condition.getType() ! null) { sql.append( AND type ?); params.add(condition.getType()); } if (condition.getCategory() ! null !condition.getCategory().trim().isEmpty()) { sql.append( AND category LIKE ?); params.add(% condition.getCategory() %); // 模糊查询 } } if (startDate ! null) { sql.append( AND record_date ?); params.add(new java.sql.Date(startDate.getTime())); } if (endDate ! null) { sql.append( AND record_date ?); params.add(new java.sql.Date(endDate.getTime())); } sql.append( ORDER BY record_date DESC, create_time DESC); // 按日期倒序排列 try { conn DBUtil.getConnection(); pstmt conn.prepareStatement(sql.toString()); // 动态设置参数 for (int i 0; i params.size(); i) { pstmt.setObject(i 1, params.get(i)); } rs pstmt.executeQuery(); while (rs.next()) { FinanceRecord record new FinanceRecord(); record.setId(rs.getInt(id)); record.setType(rs.getInt(type)); record.setCategory(rs.getString(category)); record.setAmount(rs.getBigDecimal(amount)); record.setRecordDate(rs.getDate(record_date)); record.setRemark(rs.getString(remark)); record.setCreateTime(rs.getTimestamp(create_time)); list.add(record); } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, pstmt, rs); } return list; } // ... 其他方法update, delete, selectById等实现类似 }注意事项务必使用PreparedStatement绝对不要用字符串拼接的方式来构造 SQL 语句如SELECT * FROM table WHERE id id这会导致严重的 SQL 注入安全漏洞。PreparedStatement使用占位符?能有效防止注入并且性能更好数据库可以预编译SQL。资源关闭确保在finally块中关闭所有数据库资源Connection, Statement, ResultSet否则随着程序运行数据库连接会耗尽。日期处理Java 中的java.util.Date需要转换为java.sql.Date、java.sql.Time或java.sql.Timestamp才能正确存入数据库。rs.getDate()取出的也是java.sql.Date。动态查询selectByCondition方法展示了如何安全地构建动态查询条件。使用WHERE 11作为恒真条件可以方便地后续拼接AND子句。参数列表ListObject params用于收集参数值然后循环设置到PreparedStatement中。4.3 Swing 界面设计与事件处理界面是用户直接交互的部分。主界面通常包含以下几个区域功能按钮区添加记录、删除记录、修改记录、查询、统计等按钮。条件查询区下拉框选择类型收入/支出/全部、文本框输入类别、日期选择器选择起止日期。数据展示区一个JTable用于以表格形式展示查询到的财务记录。统计信息区几个JLabel用于显示总收入、总支出、当前结余。核心代码片段主界面表格数据加载// 在 MainFrame 类中 private JTable recordTable; private DefaultTableModel tableModel; // TableModel 控制 JTable 的数据 private FinanceRecordDAO recordDao new FinanceRecordDAOImpl(); private void initTable() { String[] columnNames {ID, 类型, 类别, 金额, 记录日期, 备注, 创建时间}; tableModel new DefaultTableModel(columnNames, 0) { Override public boolean isCellEditable(int row, int column) { // 设置表格不可直接编辑通过双击或按钮来修改 return false; } }; recordTable new JTable(tableModel); // 设置表格属性行高、选择模式等 recordTable.setRowHeight(25); recordTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // 添加滚动条 JScrollPane scrollPane new JScrollPane(recordTable); // 将scrollPane添加到主界面合适的位置... } // 加载数据到表格的方法 private void loadTableData() { // 清空旧数据 tableModel.setRowCount(0); // 从DAO层获取数据这里查询所有实际可以加上查询条件 ListFinanceRecord recordList recordDao.selectAll(); // 遍历列表填充到TableModel for (FinanceRecord record : recordList) { Object[] rowData new Object[] { record.getId(), record.getType() 1 ? 收入 : 支出, // 将数字转换为中文 record.getCategory(), record.getAmount(), record.getRecordDate(), record.getRemark(), record.getCreateTime() }; tableModel.addRow(rowData); } } // “添加记录”按钮的事件监听 btnAdd.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { // 1. 弹出一个自定义的对话框AddRecordDialog让用户输入信息 AddRecordDialog dialog new AddRecordDialog(MainFrame.this, true); dialog.setVisible(true); // 模态对话框会阻塞直到关闭 // 2. 对话框关闭后判断用户是否点击了“确定” if (dialog.isConfirmed()) { FinanceRecord newRecord dialog.getRecord(); // 3. 调用DAO插入数据 int newId recordDao.insert(newRecord); if (newId 0) { JOptionPane.showMessageDialog(MainFrame.this, 添加成功); loadTableData(); // 4. 重新加载表格数据 } else { JOptionPane.showMessageDialog(MainFrame.this, 添加失败, 错误, JOptionPane.ERROR_MESSAGE); } } } });界面开发心得使用DefaultTableModelJTable本身不存储数据数据由TableModel管理。DefaultTableModel是最常用的实现通过addRow()、removeRow()、setValueAt()等方法可以方便地更新界面数据。模态对话框对于添加、修改这类需要用户输入完整信息的操作使用模态对话框JDialog是标准做法。setVisible(true)会阻塞当前线程直到对话框关闭这样可以保证获取到用户的输入结果后再执行后续逻辑。事件分发线程EDT所有 Swing 组件的创建和更新都必须在事件分发线程Event Dispatch Thread中进行。对于耗时操作如复杂的数据库查询应该使用SwingWorker在后台线程执行避免界面卡死。数据验证在对话框点击“确定”前务必对用户输入进行验证。例如金额必须是正数、日期格式是否正确、必填项是否为空等。可以在前端用JTextField的InputVerifier或直接在后端逻辑中判断。4.4 统计报表功能实现统计功能是财务系统的核心价值之一。除了简单的列表我们还需要汇总信息。在FinanceRecordDAOImpl中实现getStatistics方法Override public MapString, BigDecimal getStatistics(Date startDate, Date endDate) { MapString, BigDecimal stats new HashMap(); stats.put(totalIncome, BigDecimal.ZERO); stats.put(totalExpense, BigDecimal.ZERO); stats.put(balance, BigDecimal.ZERO); Connection conn null; PreparedStatement pstmt null; ResultSet rs null; // 使用一条SQL同时计算收入和支出总和 String sql SELECT SUM(CASE WHEN type 1 THEN amount ELSE 0 END) as income, SUM(CASE WHEN type 2 THEN amount ELSE 0 END) as expense FROM finance_record WHERE record_date BETWEEN ? AND ?; try { conn DBUtil.getConnection(); pstmt conn.prepareStatement(sql); pstmt.setDate(1, new java.sql.Date(startDate.getTime())); pstmt.setDate(2, new java.sql.Date(endDate.getTime())); rs pstmt.executeQuery(); if (rs.next()) { BigDecimal income rs.getBigDecimal(income); BigDecimal expense rs.getBigDecimal(expense); income (income null) ? BigDecimal.ZERO : income; expense (expense null) ? BigDecimal.ZERO : expense; stats.put(totalIncome, income); stats.put(totalExpense, expense); stats.put(balance, income.subtract(expense)); // 结余 收入 - 支出 } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, pstmt, rs); } return stats; }在界面上可以添加一个“统计”按钮点击后调用此方法并将结果显示在专门的标签或文本区域中。更进一步可以引入JFreeChart这样的开源图表库将收支趋势用折线图或柱状图展示出来更加直观。5. 项目部署、打包与常见问题排查5.1 环境准备与项目配置安装 JDK确保系统已安装 JDK 8 或以上版本并配置好JAVA_HOME环境变量。安装 Eclipse下载 Eclipse IDE for Java Developers 版本即可。安装与配置 MySQL下载 MySQL Community Server 并安装。记住安装时设置的 root 密码。使用 MySQL 命令行客户端或图形化工具如 MySQL Workbench登录执行前面提供的CREATE TABLE语句创建数据库和表。创建一个专门用于本项目的数据库用户非 root并授予其对finance_db数据库的权限这样更安全。创建 Eclipse 项目与导入驱动在 Eclipse 中新建一个 Java Project。将下载好的 MySQL JDBC 驱动 jar 包如mysql-connector-java-8.0.xx.jar复制到项目根目录下或创建一个lib文件夹存放。右键项目 - Build Path - Configure Build Path - Libraries - Add JARs...选择刚才的 jar 包将其添加到项目的构建路径中。修改数据库连接配置在DBUtil.java中将URL、USER、PASSWORD修改为你自己数据库的实际信息。5.2 将项目打包成可执行的 JAR 文件开发完成后你可能想分享给朋友使用这就需要打包。确保代码无误在 Eclipse 中运行项目测试所有功能。导出为 Runnable JAR右键项目 - Export - Java - Runnable JAR file。Launch configuration: 选择包含main方法的那个类通常是MainApp。Export destination: 选择 JAR 文件的保存路径和名称。Library handling:选择 “Copy required libraries into a sub-folder next to the generated JAR”。这是最稳妥的方式它会将项目依赖的所有 jar 包包括 MySQL 驱动复制到一个单独的文件夹中并配置好Class-Path。运行 JAR双击生成的 JAR 文件或者在命令行使用java -jar YourFinanceSystem.jar运行。踩坑记录直接打包成 JAR 后如果数据库连接信息是硬编码在DBUtil类中的那么用户就无法修改。一个更好的做法是将配置信息数据库URL、用户名、密码写入一个外部的配置文件如config.propertiesJAR 包运行时从同目录读取这个文件。这样用户无需重新打包就能修改配置。5.3 常见问题与解决方案速查表在开发和运行过程中你几乎一定会遇到下面这些问题问题现象可能原因解决方案ClassNotFoundException: com.mysql.jdbc.Driver或com.mysql.cj.jdbc.Driver1. MySQL 驱动 jar 包未添加到项目构建路径。2. 驱动类名写错MySQL 5.x 和 8.x 驱动类名不同。1. 检查Build Path中是否有驱动 jar。2. MySQL 8.0 使用com.mysql.cj.jdbc.Driver。Communications link failure或Access denied for user1. 数据库服务未启动。2. 连接URL、用户名、密码错误。3. 数据库用户权限不足。1. 启动 MySQL 服务。2. 仔细检查DBUtil中的配置。3. 用 root 用户登录为项目用户授权GRANT ALL ON finance_db.* TO your_userlocalhost;插入或查询中文时显示乱码数据库、连接、程序编码不统一。1. 确保数据库、表、字段的字符集为utf8mb4。2. 在 JDBC URL 中添加参数?useUnicodetruecharacterEncodingUTF-8。3. 确保 Java 源码文件也是 UTF-8 编码。程序运行时界面卡死特别是执行查询后在事件分发线程EDT中执行了耗时的数据库操作。使用SwingWorker将耗时操作放到后台线程执行。例如在SwingWorker的doInBackground()方法中执行查询在done()方法中更新界面。打包成 JAR 后无法运行提示找不到主类或驱动1. 打包时未正确指定主类。2. 依赖的 jar 包未正确打包进去。1. 检查导出 Runnable JAR 时的 Launch configuration 选择是否正确。2. 使用 “Copy required libraries” 的打包方式并确保生成的 JAR 文件同级目录下有lib文件夹包含所有依赖。日期时间显示或存储错误差8小时数据库服务器时区与程序时区不一致。在 JDBC URL 中强制指定时区serverTimezoneAsia/Shanghai或你所在的时区。JTable中数字、日期显示格式不佳DefaultTableModel将所有数据当作Object处理默认渲染器格式不友好。为JTable的特定列设置自定义渲染器DefaultTableCellRenderer。例如为金额列设置右对齐、两位小数为日期列设置特定的日期格式。6. 项目扩展与优化方向当你完成了基础版本后可以考虑以下方向进行扩展和优化这会让你的项目更像一个“产品”引入连接池每次操作都新建和关闭数据库连接开销很大。可以使用像 HikariCP、C3P0 这样的数据库连接池管理一组可复用的连接大幅提升性能。使用更高级的架构尝试将业务逻辑从 DAO 和界面中抽离出来形成清晰的 Service 层。学习并使用 Spring Framework 来管理依赖和事务这几乎是现代 Java 后端开发的标配。数据可视化集成JFreeChart不仅显示数字统计还能生成月度收支趋势折线图、消费类别饼图等让数据更直观。数据导出增加将查询结果导出为 Excel使用 Apache POI 库或 PDF 文件的功能方便打印和存档。用户登录与多账本为系统增加简单的用户登录功能不同用户的数据隔离。甚至可以支持一个用户创建多个独立的账本如“个人账本”、“家庭账本”、“旅行账本”。预算管理为每个支出类别设置月度预算系统在记录支出时进行提示或预警。数据备份与恢复提供将数据库导出为 SQL 文件或特定格式备份文件的功能并能从备份中恢复。这个项目就像一块很好的敲门砖通过它你实践了 Java 核心语法、面向对象设计、Swing 图形界面、JDBC 数据库操作、SQL 语句、基础的软件工程思想如分层、MVC。虽然现在企业级开发很少直接用 Swing 做桌面端了但其中蕴含的客户端与服务器交互、数据持久化、事件驱动等思想是相通的。把这些基础打牢再去学习 Spring Boot、Vue.js 等现代框架你会发现自己理解起来快很多因为很多底层逻辑是共通的。动手把它做出来运行起来再试着去优化它这个过程带来的提升远比只看书要大得多。本文还有配套的精品资源点击获取
返回列表