新闻详情

新闻详情

首页 / 资讯中心 / 详情

Java Swing与SQLite构建本地记账本:从零实现桌面应用开发

发布时间:2026/9/2 16:56:20来源:尧图网络
Java Swing与SQLite构建本地记账本:从零实现桌面应用开发
最近在整理个人财务时发现一个挺有意思的现象很多人尝试过各种手机记账App但最终都放弃了。原因五花八门——有的嫌广告太多有的担心数据隐私有的觉得功能太复杂还有的纯粹是因为App停止更新数据导不出来几年的记录说没就没。这让我想起一个更本质的问题对于只是想清晰记录每一笔收支、做做月度复盘的个人或小家庭来说我们真的需要一个联网的、功能繁多的云端应用吗很多时候我们需要的只是一个完全由自己掌控、轻量、快速、且能直观看到数据的工具。这个需求用最经典的技术组合就能完美解决Java Swing 做界面SQLite 存数据。你可能觉得 Swing 过时了SQLite 太简单。但恰恰是这种“过时”和“简单”构成了这个方案的独特优势零外部依赖一个JAR包就能在所有主流桌面系统运行数据就是一个本地文件拷贝、备份、迁移毫无压力整个项目结构清晰是理解桌面应用开发、数据库操作、MVC架构的绝佳练手项目。它解决的不是“功能最强”而是“主权在我”的确定性和“从零到一”的完整实践感。下面我就带你从零开始思考并动手实现一个属于你自己的可视化本地记账本。我们会绕过那些华而不实的复杂功能直击核心如何用代码管理你的金钱流水。1. 为什么是 Swing SQLite重新审视“老旧技术”的现代价值在开始写代码之前我们需要先达成一个共识技术选型没有绝对的好坏只有是否契合场景。对于个人本地记账本这个场景Swing和SQLite的组合其实隐藏着几个被低估的闪光点。1.1 极致简洁的部署与数据主权这是最核心的优势。整个应用最终可以打包成一个可执行的JAR文件。你的财务数据就是一个普通的.db或.sqlite文件安静地躺在你指定的目录里。完全离线所有计算、存储都在本地完成无需网络没有账号体系自然也就没有隐私泄露的担忧。数据主权这个数据库文件你可以用任何SQLite工具比如DB Browser for SQLite打开、查看、甚至手动修改。你可以随意拷贝它到U盘、网盘进行备份。这种“看得见、摸得着”的数据掌控感是很多云端应用无法给予的。零成本运行不需要购买服务器不需要配置数据库环境。只要电脑有JREJava运行环境双击就能运行。对于个人项目这种简洁性是巨大的加分项。1.2 一个完整的“微全栈”练习场别看项目不大但它麻雀虽小五脏俱全涵盖了软件开发中的多个关键环节前端ViewSwing 提供了一套完整的GUI组件按钮、表格、文本框、菜单。你需要学习如何布局BorderLayout,GridBagLayout等如何响应用户事件点击、输入。后端逻辑Controller如何处理按钮点击如何验证用户输入比如金额不能为负如何协调界面和数据层的交互这是业务逻辑的核心。数据层Model如何使用JDBC连接SQLite如何设计收入、支出表结构如何执行增删改查CRUD这里会涉及最基础的数据库知识。架构思想你会自然而然地用到MVC模型-视图-控制器的设计模式来分离关注点让代码更清晰、更易维护。通过完成这个项目你获得的是一个完整的、可闭环的开发体验这比单纯学习某个碎片化知识点有价值得多。1.3 避开“时髦技术”的复杂度陷阱现在流行的方案可能是JavaFX Spring Boot MySQL或者直接用Electron Node.js。这些技术栈更强大但也更重会引入大量的配置、依赖和学习成本。对于一个定位是“本地、轻量、个人使用”的记账本这些复杂度是多余的。Swing和SQLite就像一把锤子和一把螺丝刀简单、可靠、目标明确。用它们来“钉钉子”做本地记账本效率反而最高。你不会在环境配置、依赖冲突、打包部署上浪费大量时间可以更专注于业务逻辑本身。2. 核心设计先想清楚数据与交互再动手编码很多新手一上来就急着写界面结果发现数据结构不合理又要回头大改。正确的顺序是先设计数据模型再规划用户交互最后才实现界面。2.1 数据库表设计简单但需考虑扩展我们的核心实体是“账单记录”。一张表可能就够了但好的设计要为未来留点余地。-- 账单记录表 CREATE TABLE IF NOT EXISTS billing_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- 主键自增长 type INTEGER NOT NULL, -- 类型1-收入0-支出 amount REAL NOT NULL, -- 金额 category VARCHAR(50) NOT NULL, -- 类别如“餐饮”、“交通”、“工资” description TEXT, -- 描述可选 record_date DATE NOT NULL, -- 记录日期 created_time TIMESTAMP DEFAULT (datetime(now,localtime)) -- 创建时间 ); -- 类别表用于管理预设的收支类别可选但推荐 CREATE TABLE IF NOT EXISTS category ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(50) NOT NULL UNIQUE, -- 类别名称 type INTEGER NOT NULL, -- 所属类型1-收入0-支出 icon VARCHAR(20) -- 图标标识可选用于界面 );为什么这么设计type用整数而非字符串查询效率更高。amount用REAL(浮点数) 存储足够应对日常记账精度。如果涉及金融计算需考虑使用DECIMAL或以“分”为单位存储整数。将category独立成表是关键的一步。这样当用户想添加一个“健身”类别时只需向category表插入一条记录而不是去硬编码或修改程序。这大大提升了可维护性。record_date和created_time分开。record_date是账单发生的日期created_time是录入系统的时间两者意义不同。2.2 用户交互流程设计围绕核心场景记账本的核心场景就两个记一笔账和查账。记一笔账入口一个醒目的“记一笔”按钮或菜单项。表单弹出对话框或跳转面板包含收入/支出切换单选按钮。金额输入框需做数字校验。类别选择框下拉列表数据来自category表。描述文本框多行可选。日期选择器默认今天。动作点击“保存”数据校验通过后插入数据库并刷新主界面列表。查账主界面默认展示最近一个月的所有账单以表格形式呈现。表格列类型图标或文字、日期、类别、金额、描述。筛选顶部提供筛选控件按时间段筛选今日、本月、自定义范围。按类型筛选全部、收入、支出。按类别筛选下拉框多选。统计在表格上方或侧边实时显示筛选后的总收入、总支出和结余。操作表格每一行可支持右键菜单进行“编辑”或“删除”。想清楚这些你的界面布局和代码结构就有了清晰的蓝图。3. 从零搭建连接、界面与核心逻辑实现现在我们开始将设计转化为代码。我们将遵循MVC的粗略思想进行组织。3.1 项目初始化与数据库连接首先你需要一个Java项目。如果你使用Maven在pom.xml中添加SQLite JDBC驱动依赖dependency groupIdorg.xerial/groupId artifactIdsqlite-jdbc/artifactId version3.45.1.0/version !-- 请使用最新稳定版本 -- /dependency创建一个DatabaseConnection类负责建立和关闭数据库连接。这里有一个关键点数据库文件路径的处理。import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DatabaseConnection { private static final String DB_URL_PREFIX jdbc:sqlite:; private static String dbPath my_finance.db; // 默认在当前目录 // 可以提供一个方法来设置自定义路径 public static void setDbPath(String path) { dbPath path; } public static Connection getConnection() throws SQLException { try { Class.forName(org.sqlite.JDBC); } catch (ClassNotFoundException e) { throw new SQLException(SQLite JDBC driver not found., e); } // 如果数据库文件不存在SQLite会自动创建它 return DriverManager.getConnection(DB_URL_PREFIX dbPath); } // 初始化数据库表 public static void initializeDatabase() { String createBillingTable ...; // 上述建表SQL String createCategoryTable ...; try (Connection conn getConnection(); Statement stmt conn.createStatement()) { stmt.execute(createBillingTable); stmt.execute(createCategoryTable); // 可以在这里插入一些预设的类别数据 stmt.execute(INSERT OR IGNORE INTO category (name, type) VALUES (餐饮, 0), (交通, 0), (工资, 1)); } catch (SQLException e) { e.printStackTrace(); } } }注意在生产环境中你可能希望将数据库文件放在用户目录如System.getProperty(user.home)下的某个子文件夹中而不是项目根目录。3.2 构建主界面JFrame与组件布局主窗口我们继承JFrame。布局是Swing里比较考验耐心的一环推荐使用GridBagLayout或结合BorderLayout与BoxLayout它虽然复杂但非常灵活。public class FinanceApp extends JFrame { private JTable recordTable; private DefaultTableModel tableModel; private JLabel incomeLabel, expenseLabel, balanceLabel; public FinanceApp() { setTitle(我的本地记账本); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(900, 600); setLocationRelativeTo(null); // 居中显示 initUI(); loadData(); // 加载初始数据 } private void initUI() { // 1. 顶部工具栏 JToolBar toolBar new JToolBar(); JButton addButton new JButton(记一笔); JButton filterButton new JButton(筛选); // ... 为按钮添加事件监听器 toolBar.add(addButton); toolBar.add(filterButton); // 2. 统计信息面板放在北边 JPanel statsPanel new JPanel(new FlowLayout(FlowLayout.LEFT)); incomeLabel new JLabel(总收入0.00); expenseLabel new JLabel(总支出0.00); balanceLabel new JLabel(结余0.00); statsPanel.add(incomeLabel); statsPanel.add(new JLabel( | )); statsPanel.add(expenseLabel); statsPanel.add(new JLabel( | )); statsPanel.add(balanceLabel); // 3. 中央的账单表格 String[] columnNames {ID, 类型, 日期, 类别, 金额, 描述}; tableModel new DefaultTableModel(columnNames, 0) { Override public boolean isCellEditable(int row, int column) { return false; // 禁止直接编辑表格 } }; recordTable new JTable(tableModel); JScrollPane tableScrollPane new JScrollPane(recordTable); // 4. 使用BorderLayout组装主界面 setLayout(new BorderLayout()); add(toolBar, BorderLayout.NORTH); add(statsPanel, BorderLayout.SOUTH); add(tableScrollPane, BorderLayout.CENTER); } private void loadData() { // 这里调用数据层方法查询数据并更新tableModel和统计标签 // 例如ListBillingRecord records dao.getRecentRecords(30); // 遍历records添加到tableModel // 计算并更新incomeLabel, expenseLabel, balanceLabel } }3.3 实现数据访问层DAODAOData Access Object模式将数据库操作封装起来让业务逻辑层Controller不用关心SQL细节。public class BillingRecordDAO { public boolean insert(BillingRecord record) { String sql INSERT INTO billing_record(type, amount, category, description, record_date) VALUES(?,?,?,?,?); try (Connection conn DatabaseConnection.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setInt(1, record.getType()); pstmt.setDouble(2, record.getAmount()); pstmt.setString(3, record.getCategory()); pstmt.setString(4, record.getDescription()); pstmt.setString(5, record.getRecordDate()); // 可使用java.time.LocalDate int affectedRows pstmt.executeUpdate(); return affectedRows 0; } catch (SQLException e) { e.printStackTrace(); return false; } } public ListBillingRecord getRecordsByFilter(Date startDate, Date endDate, Integer type, ListString categories) { ListBillingRecord records new ArrayList(); StringBuilder sql new StringBuilder(SELECT * FROM billing_record WHERE 11 ); ListObject params new ArrayList(); // 动态拼接SQL条件注意防范SQL注入我们使用PreparedStatement if (startDate ! null) { sql.append( AND record_date ?); params.add(startDate); } if (endDate ! null) { sql.append( AND record_date ?); params.add(endDate); } if (type ! null) { sql.append( AND type ?); params.add(type); } if (categories ! null !categories.isEmpty()) { sql.append( AND category IN (); // 这里需要根据categories数量生成占位符 ?,?,? // 并将参数加入params列表 } sql.append( ORDER BY record_date DESC, id DESC); try (Connection conn DatabaseConnection.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql.toString())) { // 循环设置参数 for (int i 0; i params.size(); i) { pstmt.setObject(i 1, params.get(i)); } ResultSet rs pstmt.executeQuery(); while (rs.next()) { BillingRecord record new BillingRecord(); // 从ResultSet映射到Java对象 record.setId(rs.getInt(id)); record.setType(rs.getInt(type)); record.setAmount(rs.getDouble(amount)); record.setCategory(rs.getString(category)); record.setDescription(rs.getString(description)); record.setRecordDate(rs.getString(record_date)); records.add(record); } } catch (SQLException e) { e.printStackTrace(); } return records; } // 其他方法update, delete, getStatistics等 }3.4 连接一切事件监听与业务逻辑最后我们需要在界面事件如点击“记一笔”按钮和DAO操作之间建立桥梁。// 在FinanceApp的initUI方法中为按钮添加监听 addButton.addActionListener(e - showAddRecordDialog()); private void showAddRecordDialog() { // 1. 创建一个模态对话框(JDialog) JDialog dialog new JDialog(this, 新增记录, true); dialog.setLayout(new GridBagLayout()); GridBagConstraints gbc new GridBagConstraints(); // 2. 创建表单组件JRadioButton, JTextField, JComboBox, JTextArea, JDatePicker等 JComboBoxString categoryCombo new JComboBox(); // 需要先从数据库加载类别列表填充到JComboBox loadCategoriesIntoCombo(categoryCombo); // 3. “保存”按钮的监听器 JButton saveButton new JButton(保存); saveButton.addActionListener(saveEvent - { // 收集表单数据 int type ...; double amount Double.parseDouble(amountField.getText()); String category (String) categoryCombo.getSelectedItem(); String desc descArea.getText(); String date datePicker.getDateString(); // 数据校验金额0类别非空等 if (amount 0) { JOptionPane.showMessageDialog(dialog, 金额必须大于0); return; } // 创建BillingRecord对象 BillingRecord newRecord new BillingRecord(type, amount, category, desc, date); // 调用DAO插入数据 BillingRecordDAO dao new BillingRecordDAO(); boolean success dao.insert(newRecord); if (success) { JOptionPane.showMessageDialog(dialog, 保存成功); dialog.dispose(); loadData(); // 刷新主界面表格和统计 } else { JOptionPane.showMessageDialog(dialog, 保存失败请重试。); } }); // 4. 将组件添加到对话框设置布局... // 5. 显示对话框 dialog.pack(); dialog.setLocationRelativeTo(this); dialog.setVisible(true); }4. 超越基础让记账本真正“可用”与“好用”一个能跑通的Demo和一個真正可用的工具之间差的就是对细节的打磨和工程化思考。以下是几个关键的进阶方向。4.1 数据校验与用户体验输入校验金额输入框要防止非数字字符可以使用JFormattedTextField或监听键盘事件。日期选择最好用第三方库如JDatePicker或自定义面板避免手动输入格式错误。即时反馈在保存前对必填项做检查并给出明确提示。保存成功后不仅弹出提示更要立即刷新界面数据让用户有清晰的感知。数据加载优化如果账单记录很多一次性加载到JTable会卡顿。可以考虑分页加载或者使用SwingWorker在后台线程加载数据保持界面响应。4.2 数据备份、导出与导入这是个人数据工具的“生命线”功能。手动备份最简单的提供一个菜单项“备份数据库”点击后让用户选择路径然后使用Files.copy将当前的.db文件复制到指定位置。自动备份可以每次启动应用时自动在用户目录的备份文件夹中创建一个带时间戳的副本例如backup_20240527.db。但要注意避免备份文件无限增长可以设置只保留最近N份。导出为通用格式提供“导出为CSV/Excel”功能。使用ResultSet遍历数据借助Apache Commons CSV或POI库生成文件。这方便用户用其他工具如Excel进行深度分析。导入允许用户从CSV文件导入历史数据。这需要解析CSV并处理好与现有数据的合并如跳过重复、或清空后导入。4.3 简单的统计分析与图表Swing本身绘图能力JFreeChart库已较老但对于基础需求可以简化处理月度收支趋势可以不用画折线图而是在筛选后用文本或简单的进度条来展示。例如“本月餐饮支出占比 35%”。类别饼图如果真想画图可以考虑将数据导出或用更现代的、支持Java的图表库如XChart但会引入新的依赖。对于本地小工具清晰的表格和数字汇总往往比简陋的图表更有效。年度报表通过SQL的GROUP BY和SUM语句可以轻松计算出年度总收入、总支出、各月对比等并以表格形式展示。4.4 工程化与可维护性思考配置化将数据库路径、备份策略、主题颜色等写入一个配置文件如.properties文件而不是硬编码在代码里。日志记录使用java.util.logging或Log4j记录关键操作如用户登录、数据删除、异常错误方便后期排查问题。异常处理不要只是e.printStackTrace()。对用户应给出友好的提示如“无法连接数据库请检查文件是否被占用”在后台则记录详细的异常日志。打包发布使用Maven的maven-assembly-plugin或maven-shade-plugin将所有依赖包括SQLite JDBC驱动打包成一个可执行的“胖JAR”uber-jar。这样用户只需双击这个JAR文件即可运行。4.5 可能遇到的“坑”与解决方案SQLite并发写入SQLite在写入时会锁定整个数据库文件。如果未来你想增加多线程备份或同步功能需要注意这一点。通常的解决方案是所有数据库访问通过一个单例的数据库连接管理类进行并做好同步。Swing界面卡顿所有耗时的操作如加载上千条记录、执行复杂查询、备份数据库都必须在后台线程SwingWorker中执行否则会阻塞事件分发线程EDT导致界面“假死”。中文乱码确保数据库连接字符串包含字符集参数如jdbc:sqlite:my.db?charsetutf8并且你的Java源代码文件本身是UTF-8编码。路径问题在打包成JAR后访问数据库文件或配置文件的相对路径会变化。比较稳妥的做法是使用ClassLoader.getResource()获取资源或将文件存储在用户主目录System.getProperty(user.home)下的固定位置。完成以上步骤你得到的不仅仅是一个记账工具而是一个完整的、由你完全掌控的本地数据管理解决方案的实践样本。它的价值不在于功能有多炫酷而在于它清晰地演示了如何用最基础的砖瓦构建一个可靠、自主、可长期维护的数字工具。当你需要管理其他类型的本地数据如书签、笔记、收藏时这套技术栈和设计思路依然可以成为你坚实的起点。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

语言模型如何学会筛选,世界模型如何学会补全:从人脑感知到人工智能的混沌降维逻辑 2026/9/2 17:41:27

语言模型如何学会筛选,世界模型如何学会补全:从人脑感知到人工智能的混沌降维逻辑

摘要 客观世界的信息量趋近于无限,而任何智能系统的算力都是有限的。人脑解决这一矛盾的方式,是一套「离散采样—预测补全—整合校准」的混沌降维机制;今天最前沿的语言模型与世界模型,正在用另一套材料重走同一条路。本文以人脑感…

阅读更多 →
藿藿不是奶妈!治疗+解控+充能“三合一”生存位使用攻略 2026/9/2 17:41:27

藿藿不是奶妈!治疗+解控+充能“三合一”生存位使用攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
费米悖论新解:宇宙的沉默,是野蛮文明不配远航 2026/9/2 17:41:27

费米悖论新解:宇宙的沉默,是野蛮文明不配远航

前言:困扰人类百年的宇宙谜题 费米悖论,困扰了近代科学界数十年。 浩瀚宇宙,星河亿万,在百亿年的时间尺度里,即便概率再低,也理应诞生无数地外智慧文明。按照人类主流的科技扩张逻辑:文明只要突…

阅读更多 →
Godot 4 编辑器增强插件 Wand-Enhancer 安装与开发指南 2026/9/2 17:41:27

Godot 4 编辑器增强插件 Wand-Enhancer 安装与开发指南

Godot 4 编辑器增强工具 Wand-Enhancer:它到底解决了什么问题,以及如何接入很多从 Blender、Unity 转到 Godot 的开发者,上手后第一个“不适感”不是 GDScript,也不是节点树,而是编辑器操作手感。默认情况下&#xff0…

阅读更多 →
Go 后端开发实战(6):中间件与路由设计 2026/9/2 17:41:27

Go 后端开发实战(6):中间件与路由设计

上一篇把业务用例接到数据库,并明确了事务边界。本篇回到请求链:设计可组合中间件、版本化资源路由、认证授权和限流。目标是让横切能力可观察、可测试,同时避免中间件变成隐藏业务逻辑的杂物间。 一、用函数组合建立请求管道 标准中间件类…

阅读更多 →
Boss直聘招聘数据工程化实践:Playwright+字段标准化+实时可视化 2026/9/2 17:38:26

Boss直聘招聘数据工程化实践:Playwright+字段标准化+实时可视化

简介:本资源是一套面向数据分析初学者与求职者的Python实战项目,聚焦Boss直聘平台中大数据、人工智能等热门岗位的数据采集、清洗、分析与可视化全流程。项目使用Scrapy框架高效爬取全国热门城市相关岗位信息,解决求职者缺乏行业薪资分布、技…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞