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

发布时间:2026/8/29 2:48:17
Java Swing实战:从零构建个人财务管理系统(MVC+MySQL) 简介在软件开发领域CS客户端-服务器架构是构建桌面应用的基础模式它将用户界面与业务逻辑、数据存储分离提升了应用的可维护性和可扩展性。其核心原理在于客户端负责交互展示服务器端如数据库处理数据持久化两者通过网络或本地连接通信。这种架构的技术价值在于结构清晰、数据集中管理非常适合需要复杂业务逻辑和可靠数据存储的桌面工具。在应用场景上从企业ERP到个人工具软件广泛采用。本文聚焦于使用经典的Java技术栈实现一个CS架构的个人财务管理系统其中通过JDBC进行数据库操作并严格遵循MVC模式进行代码组织确保事务一致性为Java桌面开发提供了一个完整的工程实践范例。1. 项目缘起与核心价值最近在整理个人财务时发现市面上的记账软件要么功能过于复杂要么数据隐私让人不放心。作为一个有十多年Java开发经验的老兵我决定自己动手用最经典的Java Swing技术栈从零搭建一个CS架构的个人财务收支管理系统。这个项目不仅解决了我的实际需求更是一次对Java桌面应用开发核心技术的完整回顾与实践。如果你正在学习Java或者想了解如何将Swing、JDBC、MVC这些教科书上的概念串联成一个可运行、可维护的实战项目那么这篇记录将为你提供一个清晰的路线图。整个项目基于Eclipse IDE开发使用Mysql作为数据存储最终形成一个结构清晰、功能完整的CS结构桌面应用。接下来我将从技术选型、环境搭建、核心模块实现到打包部署为你完整拆解这个项目的每一个细节。2. 技术栈选型与项目环境搭建2.1 为什么选择Java Swing MySQL CS架构在开始敲代码之前明确技术选型的理由至关重要。我选择这套组合是基于以下几个核心考量首先Java Swing作为Java SE的一部分无需额外依赖任何第三方框架就能构建出跨平台的图形界面。对于个人财务管理这种对界面复杂度要求不高但需要稳定、可移植的应用来说Swing是绝佳的选择。它内置了丰富的组件JFrame, JTable, JButton等事件驱动模型成熟学习曲线相对平缓。虽然其界面风格略显“复古”但通过合理的布局管理如BorderLayout, GridBagLayout和少量自定义渲染完全可以做出清爽、实用的界面。其次数据存储选择了MySQL。个人财务数据虽然量不大但结构规整涉及账户、分类、流水等多张表的关系。MySQL作为最流行的开源关系型数据库之一安装简单社区资源丰富JDBC驱动稳定。对于桌面应用通常使用Connector/J驱动进行连接其PreparedStatement能有效防止SQL注入确保数据操作的安全。最后采用经典的CSClient/Server结构。这里的“Server”指的是数据库服务器应用本身是客户端。这种结构将数据集中存储便于备份和管理。虽然也有考虑过使用嵌入式数据库如H2、SQLite来简化部署但考虑到未来数据可能需要在多台设备间同步尽管本项目未实现以及保持与常见Web技术栈数据服务化的潜在兼容性独立的MySQL服务是更稳妥的选择。2.2 开发环境详细配置指南一个顺畅的开发环境是高效编码的基础。以下是每一步的详细操作和避坑点。1. Java开发环境配置确保你的系统已安装JDK 8或更高版本推荐JDK 11 LTS。打开命令行输入java -version和javac -version验证。如果未安装需从Oracle官网或AdoptOpenJDK等渠道下载安装。注意安装后务必配置JAVA_HOME环境变量并将其bin目录添加到PATH中。这是很多初学者容易忽略的一步不配置会导致Eclipse或后续Maven等工具找不到正确的JDK。2. Eclipse IDE安装与优化从Eclipse官网下载“Eclipse IDE for Enterprise Java and Developers”版本它包含了Java SE和Web开发所需的插件。解压即可运行。 安装后建议进行以下设置以提升Swing开发体验调整编码进入Window - Preferences - General - Workspace将“Text file encoding”设置为UTF-8避免中文乱码。安装WindowBuilder插件可选但强烈推荐这是一个可视化的Swing/AWT设计器。通过Help - Eclipse Marketplace搜索“WindowBuilder”安装。安装后新建JFrame类时可以切换到“Design”视图进行拖拽式布局极大提高界面开发效率尤其适合初学者理解布局管理器。配置JRE在Window - Preferences - Java - Installed JREs中确保指向你安装的JDK目录而不是JRE。3. MySQL数据库安装与初始化从MySQL官网下载社区版安装包。安装过程中记住你设置的root用户密码。 安装完成后使用命令行或MySQL Workbench图形工具创建本项目的数据库和用户。-- 创建数据库 CREATE DATABASE personal_finance_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 创建专用用户避免使用root CREATE USER finance_userlocalhost IDENTIFIED BY YourSecurePassword123; -- 授予权限 GRANT ALL PRIVILEGES ON personal_finance_db.* TO finance_userlocalhost; FLUSH PRIVILEGES;注意生产环境务必使用强密码且权限应根据最小权限原则分配。这里为了演示简化了权限。4. 项目创建与依赖管理在Eclipse中新建一个“Java Project”。我建议不使用Maven等构建工具来管理这个纯Swing项目以保持极简。只需将MySQL的JDBC驱动JAR包mysql-connector-java-8.0.xx.jar添加到项目的构建路径Build Path中即可。右键项目 -Build Path - Configure Build Path - Libraries - Add External JARs...选择下载好的驱动JAR包。3. 数据库设计与核心表结构数据库设计是应用的基石良好的设计能保证数据一致性并简化业务逻辑。根据个人财务管理的核心需求我设计了以下几张表。3.1 实体关系分析与表结构定义账户表 (account)记录资金存放的载体如现金、支付宝、微信、银行卡等。CREATE TABLE account ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 账户ID, name VARCHAR(50) NOT NULL COMMENT 账户名称如招商银行储蓄卡, type VARCHAR(20) COMMENT 账户类型现金、银行卡、电子支付等, balance DECIMAL(12, 2) NOT NULL DEFAULT 0.00 COMMENT 当前余额, remarks TEXT COMMENT 备注, created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT账户表;设计思考balance余额字段的设计存在争议。一种做法是不存储每次通过统计流水计算得出另一种是存储并在每次发生流水时更新。我选择了存储因为实时计算所有流水在数据量大时可能影响性能。但这就要求在事务中同时更新流水表和账户表确保数据一致性这是实现上的一个关键点。分类表 (category)对收支进行归类如餐饮、交通、工资、投资等。CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 分类ID, name VARCHAR(50) NOT NULL UNIQUE COMMENT 分类名称, type TINYINT NOT NULL COMMENT 类型1-收入0-支出, parent_id INT DEFAULT NULL COMMENT 父级分类ID用于多级分类, icon VARCHAR(100) COMMENT 图标标识前端可用, FOREIGN KEY (parent_id) REFERENCES category(id) ON DELETE SET NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收支分类表;设计思考使用type字段区分收入与支出分类。parent_id实现了简单的树形结构支持二级分类如“支出-餐饮-早餐”。UNIQUE约束确保分类名不重复。收支流水表 (transaction)核心表记录每一笔具体的财务活动。CREATE TABLE transaction ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 流水ID, amount DECIMAL(12, 2) NOT NULL COMMENT 金额正数为收入负数为支出, account_id INT NOT NULL COMMENT 关联账户ID, category_id INT NOT NULL COMMENT 关联分类ID, transaction_time DATETIME NOT NULL COMMENT 交易时间, description VARCHAR(200) COMMENT 描述/备注, attachment_path VARCHAR(500) COMMENT 附件如票据照片存储路径, created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 记录创建时间, FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE RESTRICT, FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收支流水表;设计思考金额正负用正负值直接区分收支简化了业务逻辑。查询时SUM(amount)即可得到净收入。时间字段transaction_time是业务发生时间created_time是系统记录时间两者区分开更合理。外键约束使用FOREIGN KEY和ON DELETE RESTRICT确保数据完整性。例如如果某账户下还有流水则不允许删除该账户。附件存储attachment_path只存储文件在服务器或本地的路径而不是文件本身BLOB。这是更通用的做法避免数据库膨胀。3.2 初始化数据与索引优化建表后需要插入一些常用的初始数据并考虑查询性能。-- 插入初始分类 INSERT INTO category (name, type) VALUES (工资收入, 1), (兼职收入, 1), (投资理财, 1); INSERT INTO category (name, type) VALUES (餐饮美食, 0), (交通出行, 0), (服饰美容, 0), (生活日用, 0); -- 为高频查询字段添加索引 CREATE INDEX idx_transaction_time ON transaction(transaction_time); CREATE INDEX idx_transaction_account ON transaction(account_id); CREATE INDEX idx_transaction_category ON transaction(category_id);索引能大幅提升按时间、账户或分类查询流水明细的速度。4. 系统架构与MVC模式实现对于桌面应用清晰的代码结构是长期可维护的关键。我采用了经典的MVCModel-View-Controller模式进行架构。4.1 项目包结构规划在Eclipse的src目录下我建立了如下包结构这能让代码职责分明src/ ├── com.finance.app │ ├── Main.java // 程序入口 │ ├── controller/ // 控制器层 │ │ ├── MainFrameController.java │ │ ├── TransactionController.java │ │ └── ... │ ├── view/ // 视图层Swing界面 │ │ ├── component/ // 自定义组件如分类选择器 │ │ ├── dialog/ // 各种对话框 │ │ └── frame/ // 主窗口、列表窗口 │ ├── model/ // 模型层 │ │ ├── entity/ // 实体类与表对应 │ │ │ ├── Account.java │ │ │ ├── Category.java │ │ │ └── Transaction.java │ │ ├── dao/ // 数据访问对象 │ │ │ ├── BaseDAO.java // 封装通用CRUD │ │ │ ├── AccountDAO.java │ │ │ └── ... │ │ └── service/ // 业务逻辑层 │ │ ├── AccountService.java │ │ └── TransactionService.java │ └── util/ // 工具类 │ ├── DBUtil.java // 数据库连接与关闭 │ ├── DateUtil.java // 日期处理 │ └── ... └── resources/ // 资源文件图片、配置文件 └── db.properties // 数据库连接配置4.2 核心模型层Model实现实体类Entity使用纯Java Bean属性与数据库字段一一对应并提供getter/setter方法。// com.finance.app.model.entity.Transaction.java public class Transaction { private Integer id; private BigDecimal amount; // 使用BigDecimal处理金额避免精度丢失 private Integer accountId; private Integer categoryId; private LocalDateTime transactionTime; // 使用Java 8的日期时间API private String description; // ... 省略其他属性和方法 }注意金额一定要用BigDecimal切忌用double或float否则在财务计算中会出现精度误差。数据库工具类DBUtil封装连接获取与释放使用Properties读取配置文件实现连接池这里简化版使用DriverManager。// com.finance.app.util.DBUtil.java public class DBUtil { private static String url; private static String user; private static String password; static { // 从resources/db.properties加载配置 // ... } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } // 重载的close方法用于关闭Connection, Statement, ResultSet public static void close(Connection conn, Statement stmt, ResultSet rs) { // 注意关闭顺序rs - stmt - conn // 并在每个close调用处进行try-catch避免一个关闭异常影响后续关闭 } }数据访问对象DAO每个实体类对应一个DAO封装所有数据库操作。这里以TransactionDAO为例展示核心的增删改查。// com.finance.app.model.dao.TransactionDAO.java public class TransactionDAO extends BaseDAOTransaction { // 插入一笔流水并更新对应账户余额需在Service层用事务控制 public int insert(Transaction trans) throws SQLException { String sql INSERT INTO transaction(amount, account_id, category_id, transaction_time, description) VALUES(?,?,?,?,?); // 使用PreparedStatement防止SQL注入 return executeUpdate(sql, trans.getAmount(), trans.getAccountId(), ...); } // 复合条件查询流水按时间范围、账户、分类 public ListTransaction queryByCondition(LocalDate start, LocalDate end, Integer accountId, Integer categoryId) { StringBuilder sql new StringBuilder(SELECT * FROM transaction WHERE 11 ); ListObject params new ArrayList(); if (start ! null) { sql.append( AND transaction_time ?); params.add(start.atStartOfDay()); } if (end ! null) { sql.append( AND transaction_time ?); // 注意使用避免包含结束日期的下一天 params.add(end.plusDays(1).atStartOfDay()); } // ... 拼接其他条件 return executeQuery(sql.toString(), params.toArray()); } }BaseDAO利用泛型和反射封装了executeQuery和executeUpdate等通用方法避免了大量重复的JDBC模板代码。业务逻辑层ServiceDAO只负责数据存取复杂的业务规则在Service层实现。这是MVC中“Model”的核心。// com.finance.app.model.service.TransactionService.java public class TransactionService { private TransactionDAO transDao new TransactionDAO(); private AccountDAO accountDao new AccountDAO(); public boolean addTransaction(Transaction trans) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 插入流水记录 transDao.insert(conn, trans); // DAO方法需支持传入Connection以在同一个事务中 // 2. 更新账户余额 Account account accountDao.getById(conn, trans.getAccountId()); BigDecimal newBalance account.getBalance().add(trans.getAmount()); // 金额正负已体现收支 account.setBalance(newBalance); accountDao.updateBalance(conn, account); conn.commit(); // 提交事务 return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { DBUtil.close(conn, null, null); } } }这是本项目最关键的代码之一。**务必使用事务Transaction**来保证“记录流水”和“更新余额”这两个操作要么同时成功要么同时失败。否则可能出现流水记了但余额没变或者反之导致数据不一致。4.3 视图层View与控制器层Controller联动视图层主要由Swing的JFrame、JDialog和各种组件构成。以“新增流水”对话框为例。视图ViewAddTransactionDialog.java负责界面布局和组件初始化。public class AddTransactionDialog extends JDialog { private JTextField amountField; private JComboBoxAccount accountCombo; private JComboBoxCategory categoryCombo; private JSpinner dateSpinner; private JTextArea descArea; private JButton submitBtn; private JButton cancelBtn; // 初始化界面加载账户和分类数据到下拉框 public AddTransactionDialog(Frame owner) { super(owner, 新增收支, true); // ... 界面布局代码可使用WindowBuilder辅助设计 loadAccountData(); // 从AccountService获取数据 loadCategoryData(); // 从CategoryService获取数据 initListeners(); } private void initListeners() { submitBtn.addActionListener(e - onSubmit()); cancelBtn.addActionListener(e - setVisible(false)); } private void onSubmit() { // 收集界面数据组装成Transaction对象 Transaction trans new Transaction(); try { trans.setAmount(new BigDecimal(amountField.getText().trim())); } catch (NumberFormatException e) { JOptionPane.showMessageDialog(this, 金额格式错误, 错误, JOptionPane.ERROR_MESSAGE); return; } trans.setAccountId(((Account)accountCombo.getSelectedItem()).getId()); // ... 设置其他字段 // 关键调用控制器处理业务逻辑 boolean success TransactionController.addTransaction(trans); if (success) { JOptionPane.showMessageDialog(this, 添加成功); setVisible(false); // 通知主界面刷新列表 MainFrameController.getInstance().refreshTransactionTable(); } else { JOptionPane.showMessageDialog(this, 添加失败请检查数据, 错误, JOptionPane.ERROR_MESSAGE); } } }控制器ControllerTransactionController.java作为视图和模型的中介。// com.finance.app.controller.TransactionController.java public class TransactionController { private static TransactionService transService new TransactionService(); public static boolean addTransaction(Transaction trans) { // 进行数据校验如金额非空、账户存在等 if (trans.getAmount() null || trans.getAccountId() null) { return false; } // 调用Service层执行业务逻辑 return transService.addTransaction(trans); } // 其他控制器方法如查询、删除、修改 public static ListTransaction queryTransactions(LocalDate start, LocalDate end, Integer accountId) { return transService.queryByCondition(start, end, accountId, null); } }通过这种模式视图只负责展示和接收用户输入控制器负责调度和简单校验模型负责核心业务和数据。代码耦合度低易于测试和维护。5. 核心功能模块实现详解5.1 主界面设计与数据展示主界面采用经典的JFrame包含菜单栏、工具栏、账户概览面板、流水列表和统计图表区域。账户概览面板使用JPanel配合GridLayout或GridBagLayout动态加载所有账户显示账户名和实时余额。余额数据通过AccountService.getAllAccounts()获取并监听账户或流水的增删改事件实现实时刷新。流水列表这是核心组件使用JTable来展示。直接使用DefaultTableModel虽然简单但不利于与实体对象绑定。更优的做法是自定义一个AbstractTableModel。public class TransactionTableModel extends AbstractTableModel { private ListTransaction data; private String[] columnNames {ID, 时间, 账户, 分类, 金额, 备注}; Override public int getRowCount() { return data null ? 0 : data.size(); } Override public int getColumnCount() { return columnNames.length; } Override public Object getValueAt(int row, int col) { Transaction t data.get(row); switch (col) { case 0: return t.getId(); case 1: return DateUtil.format(t.getTransactionTime()); case 2: return AccountService.getAccountName(t.getAccountId()); // 关联查询账户名 case 3: return CategoryService.getCategoryName(t.getCategoryId()); // 关联查询分类名 case 4: return t.getAmount(); case 5: return t.getDescription(); default: return null; } } Override public String getColumnName(int column) { return columnNames[column]; } // 自定义金额列渲染器让支出显示为红色收入显示为绿色 Override public Class? getColumnClass(int columnIndex) { if (columnIndex 4) return BigDecimal.class; return Object.class; } }然后在主界面中transactionTable new JTable(); transactionTable.setModel(new TransactionTableModel()); // 设置自定义渲染器 transactionTable.getColumnModel().getColumn(4).setCellRenderer(new AmountCellRenderer());AmountCellRenderer继承DefaultTableCellRenderer在getTableCellRendererComponent方法中根据数值正负设置前景色。条件查询与刷新在主界面顶部放置查询条件组件如JDatePicker用于选择时间范围JComboBox选择账户点击“查询”按钮后调用TransactionController.queryTransactions(...)获取数据再更新TransactionTableModel的data并调用fireTableDataChanged()刷新表格。5.2 收支录入与数据校验“新增流水”对话框AddTransactionDialog的实现有几个细节需要注意金额输入校验在amountField上可以添加DocumentListener或焦点失去事件实时校验输入是否为合法数字。更严格的做法是使用JFormattedTextField。分类下拉框优化收支分类应分开显示。可以创建两个JComboBox一个放收入分类一个放支出分类再通过一个JRadioButton组“收入”/“支出”来切换显示哪个下拉框。数据从CategoryService.getCategoriesByType(type)获取。日期时间选择使用JSpinner绑定一个SpinnerDateModel是简单的方法。更友好的方式是使用第三方库如JDatePicker。数据提交的健壮性在onSubmit方法中除了格式校验还应进行业务校验例如支出时金额不能大于账户余额这需要查询当前余额。这部分逻辑可以放在TransactionService.addTransaction中。5.3 统计报表与图表生成统计功能是财务系统的灵魂。我实现了按日、月、年以及按分类的统计。数据统计服务在TransactionService中增加统计方法。public MapString, BigDecimal getStatByCategory(LocalDate start, LocalDate end, int type) { // type: 0-支出1-收入 String sql SELECT c.name, SUM(t.amount) as total FROM transaction t JOIN category c ON t.category_id c.id WHERE t.transaction_time ? AND t.transaction_time ? AND c.type ? GROUP BY c.id, c.name; // 执行查询返回Map分类名总额 } public MapString, BigDecimal getDailyStat(LocalDate start, LocalDate end) { // 按天统计使用DATE()函数 String sql SELECT DATE(transaction_time) as day, SUM(amount) as total FROM transaction WHERE transaction_time ? AND transaction_time ? GROUP BY DATE(transaction_time) ORDER BY day; }图表展示Java Swing本身没有高级图表库。我选择了轻量级的JFreeChart库。将其JAR包加入项目后可以轻松生成饼图、柱状图等。// 生成支出分类饼图 private JPanel createCategoryPieChart(MapString, BigDecimal data) { DefaultPieDataset dataset new DefaultPieDataset(); for (Map.EntryString, BigDecimal entry : data.entrySet()) { dataset.setValue(entry.getKey(), entry.getValue()); } JFreeChart chart ChartFactory.createPieChart( 支出分类占比, dataset, true, // 显示图例 true, // 显示提示 false ); ChartPanel chartPanel new ChartPanel(chart); chartPanel.setPreferredSize(new Dimension(400, 300)); return chartPanel; }将ChartPanel嵌入主界面的一个JTabbedPane中即可实现图表选项卡。5.4 数据备份与恢复功能桌面应用的数据安全很重要。我实现了简单的数据库备份与恢复功能。备份本质上是调用MySQL的mysqldump命令。public boolean backupDatabase(String backupPath) { String command String.format( mysqldump -u%s -p%s --databases %s %s, DB_USER, DB_PASSWORD, DB_NAME, backupPath ); try { Process process Runtime.getRuntime().exec(command); int exitCode process.waitFor(); return exitCode 0; } catch (IOException | InterruptedException e) { e.printStackTrace(); return false; } }注意将数据库密码写在命令行中存在安全风险。一种改进方法是使用配置文件并在执行时从标准输入传递密码-p后不接密码但实现稍复杂。对于个人应用这是一个权衡。恢复调用mysql命令执行备份的SQL文件。public boolean restoreDatabase(String backupFilePath) { // 恢复前可以弹窗二次确认因为这会覆盖现有数据。 String command String.format( mysql -u%s -p%s %s %s, DB_USER, DB_PASSWORD, DB_NAME, backupFilePath ); // ... 执行逻辑与备份类似 }在界面上添加“系统设置”菜单下设“备份数据库”和“恢复数据库”选项通过JFileChooser让用户选择备份文件路径。6. 项目打包、部署与进阶优化6.1 将项目打包为可执行JAR开发完成后需要将项目打包成便于分发的形式。导出可运行JAR在Eclipse中右键项目 -Export - Java - Runnable JAR file。选择启动配置Launch configuration选择包含Main类的那个配置。库处理方式选择“Extract required libraries into generated JAR”。这会将所有依赖如MySQL驱动、JFreeChart的JAR解压并合并到最终的JAR文件中生成一个“胖JAR”Fat JAR用户只需这一个文件即可运行。处理外部资源resources/目录下的配置文件如db.properties需要被打包进JAR。确保在代码中通过ClassLoader.getResourceAsStream()来读取它们而不是使用new File()。InputStream is getClass().getClassLoader().getResourceAsStream(db.properties); Properties props new Properties(); props.load(is);常见打包问题找不到主类确保META-INF/MANIFEST.MF文件中正确指定了Main-Class。Eclipse导出时会自动生成。数据库连接失败检查打包后db.properties文件是否在JAR根目录以及其中的连接参数尤其是密码是否正确。缺少本地库Native Library某些图表库或驱动可能需要本地库。JFreeChart是纯Java的没问题。但如果你用了其他需要本地库的组件需要额外处理。6.2 安装程序制作与部署对于更专业的发布可以使用Launch4j将JAR包装成Windows的.exe文件或者使用JPackageJDK 14生成真正的原生安装包如MSI、DMG。这可以包含JRE让用户无需预先安装Java。6.3 性能与体验优化建议数据库连接池当前每次操作都新建连接频繁操作下性能有损。可以引入如HikariCP这样的轻量级连接池。列表分页当流水记录上万条时一次性加载到JTable会卡顿。需要实现分页查询并在表格底部添加分页导航条。数据缓存账户列表、分类列表这些不常变的数据可以在应用启动时加载到内存中缓存起来避免频繁查询数据库。异步操作备份/恢复数据库、生成复杂报表等耗时操作应该在单独的线程如SwingWorker中执行避免阻塞事件调度线程EDT导致界面卡死。界面美化可以使用LookAndFeel更换Swing默认皮肤如FlatLaf库能提供现代化的扁平化界面。6.4 遇到的典型问题与解决方案问题一Swing界面在数据加载时卡顿。原因在事件调度线程EDT中执行了耗时的数据库操作。解决使用SwingWorker。SwingWorkerListTransaction, Void worker new SwingWorker() { Override protected ListTransaction doInBackground() throws Exception { // 在后台线程执行耗时查询 return TransactionController.queryTransactions(...); } Override protected void done() { try { ListTransaction result get(); // 在EDT中更新UI updateTableModel(result); } catch (Exception e) { e.printStackTrace(); } } }; worker.execute();问题二金额计算出现几分钱的误差。原因使用了double类型进行财务计算。解决全线改用BigDecimal并且定义统一的精度和舍入模式如RoundingMode.HALF_UP。问题三窗口关闭后数据库连接未释放导致连接数耗尽。原因程序退出时连接池或未关闭的连接没有正确释放。解决为主窗口添加WindowListener在windowClosing事件中调用一个统一的资源清理方法关闭所有数据库连接池的连接。这个个人财务管理系统项目虽然技术栈传统但涵盖了CS桌面应用开发的完整闭环从需求分析、技术选型、数据库设计、分层架构、模块实现、到打包部署和问题排查。它不仅是Swing和JDBC的练习场更是理解MVC、事务、异常处理、UI线程等核心概念的绝佳案例。本文还有配套的精品资源点击获取