尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java Swing与SQLite实战:从零构建跨平台本地记账本应用

Java Swing与SQLite实战:从零构建跨平台本地记账本应用 大家好我是专注于Java桌面应用开发的技术博主。在日常开发中我们常常需要一些轻量级的本地工具来管理个人或小型团队的数据比如记账、笔记、待办事项等。使用Java Swing配合SQLite数据库可以快速构建出跨平台、无需安装、数据本地的桌面应用是巩固Java基础和GUI编程的绝佳实战项目。本文将手把手带你从零开始使用Java Swing和SQLite打造一个功能完整的可视化本地记账本。通过这个项目你将掌握Swing界面布局、事件处理、SQLite数据库的CRUD操作、以及如何将数据与界面进行绑定。无论你是Java初学者想通过项目练手还是有一定经验的开发者想回顾桌面应用开发都能从中获得清晰的实践路径。1. 项目背景与核心技术选型在开始编码之前我们先明确一下为什么要选择这样的技术组合以及它们各自解决了什么问题。1.1 为什么是Java SwingJava Swing是Java SE平台的一部分是一个用于构建图形用户界面GUI的工具包。尽管如今Web和移动端开发更为流行但Swing在开发需要离线运行、对系统资源控制要求高、或者作为内部工具的桌面应用时依然有其独特的优势跨平台性一次编写可以在Windows、macOS、Linux等系统上运行得益于Java虚拟机JVM。成熟稳定作为Java标准库的一部分API稳定社区资料丰富。无需额外依赖JDK自带无需像JavaFX那样可能需要单独处理模块化或依赖。学习价值理解Swing的MVC模型-视图-控制器思想、事件驱动模型对理解其他GUI框架大有裨益。我们的记账本不需要复杂的动画和特效Swing完全能够胜任且能让开发者更专注于业务逻辑。1.2 为什么是SQLiteSQLite是一个嵌入式的关系型数据库引擎它与我们常见的MySQL、PostgreSQL等客户端-服务器数据库不同。零配置、无服务器数据库就是一个本地文件如account.db无需安装和启动任何数据库服务。轻量级整个库体积小巧直接链接到应用程序中。事务支持完全支持ACID事务保证数据的一致性这对于记账应用至关重要。适合本地应用正是“本地记账本”的绝配数据文件可以随应用一起拷贝、备份。在Java中我们可以通过JDBC驱动来操作SQLite数据库其使用方式与操作其他数据库基本一致学习成本低。1.3 项目整体架构视图本项目将采用典型的三层结构进行开发表示层View由Swing组件构成包括窗口JFrame、按钮JButton、表格JTable、文本框JTextField等负责与用户交互。业务逻辑层Controller/Service处理用户操作如点击“添加”按钮调用数据访问层并将结果更新到表示层。数据访问层Model/Dao负责与SQLite数据库直接对话执行增删改查CRUD操作。我们将通过JDBC将这三层串联起来实现一个完整的应用程序。2. 开发环境准备在开始写代码前请确保你的开发环境已就绪。2.1 基础环境操作系统Windows 10/11, macOS, 或 Linux发行版均可。Java开发工具包JDK版本8或以上推荐JDK 11或17长期支持版本。在终端输入java -version和javac -version验证安装。集成开发环境IDE推荐使用IntelliJ IDEA社区版免费或Eclipse。它们能极大提升开发效率特别是管理依赖和调试。SQLite可视化工具可选推荐DB Browser for SQLite。这是一个图形化工具可以方便地查看、编辑我们创建的数据库文件用于调试。你可以从其官网下载。2.2 项目与依赖管理我们将使用Maven来管理项目依赖这样能自动下载所需的SQLite JDBC驱动。创建Maven项目 在你的IDE中新建一个Maven项目选择maven-archetype-quickstart填写GroupId如com.example和ArtifactId如account-book。添加SQLite JDBC依赖 打开项目根目录下的pom.xml文件在dependencies节点内添加以下依赖dependencies !-- SQLite JDBC 驱动 -- dependency groupIdorg.xerial/groupId artifactIdsqlite-jdbc/artifactId version3.42.0.0/version !-- 请使用最新稳定版本 -- /dependency /dependencies保存后IDE会自动下载该jar包。如果你不使用Maven需要手动下载sqlite-jdbc-*.jar并添加到项目的构建路径中。2.3 项目目录结构规划一个清晰的项目结构有助于代码管理。创建完成后你的项目目录应类似如下account-book/ ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ └── accountbook/ │ │ ├── MainApp.java // 程序入口 │ │ ├── model/ │ │ │ ├── Account.java // 账目实体类 │ │ │ └── AccountType.java // 枚举收入/支出 │ │ ├── dao/ │ │ │ └── AccountDao.java // 数据访问对象 │ │ ├── service/ │ │ │ └── AccountService.java // 业务逻辑层 │ │ ├── ui/ │ │ │ ├── MainFrame.java // 主窗口 │ │ │ └── AddEditDialog.java // 添加/编辑对话框 │ │ └── util/ │ │ └── DBUtil.java // 数据库连接工具类 │ └── resources/ │ └── (可存放图标等资源文件) └── test/ └── java/ └── (测试代码)3. 数据库设计与实现数据层是应用的基石我们先来创建数据库和表。3.1 设计数据表对于一个简单的记账本account表至少需要以下字段字段名数据类型说明idINTEGER主键自增长typeTEXT类型INCOME(收入) 或EXPENSE(支出)categoryTEXT类别如“餐饮”、“交通”、“工资”amountREAL金额使用实数类型存储对应Java的DoubleremarkTEXT备注可选dateTEXT日期存储为字符串格式yyyy-MM-dd3.2 创建数据库连接工具类我们创建一个工具类来统一管理数据库连接避免代码重复和连接泄漏。// 文件路径src/main/java/com/example/accountbook/util/DBUtil.java package com.example.accountbook.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String DB_URL jdbc:sqlite:account.db; // 数据库文件位于项目根目录 static { try { // 加载SQLite JDBC驱动 Class.forName(org.sqlite.JDBC); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new RuntimeException(Failed to load SQLite JDBC driver, e); } } /** * 获取数据库连接 * return Connection 对象 */ public static Connection getConnection() throws SQLException { return DriverManager.getConnection(DB_URL); } /** * 关闭数据库连接 * param conn 要关闭的连接 */ public static void closeConnection(Connection conn) { if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }3.3 定义实体类与枚举创建对应的Java类来映射数据库表。// 文件路径src/main/java/com/example/accountbook/model/AccountType.java package com.example.accountbook.model; public enum AccountType { INCOME(收入), EXPENSE(支出); private final String displayName; AccountType(String displayName) { this.displayName displayName; } public String getDisplayName() { return displayName; } }// 文件路径src/main/java/com/example/accountbook/model/Account.java package com.example.accountbook.model; import java.time.LocalDate; public class Account { private Integer id; private AccountType type; private String category; private Double amount; private String remark; private LocalDate date; // 使用Java 8的日期API // 构造方法、Getter和Setter省略需自行补充 // 可以使用IDE如IntelliJ IDEA的Generate功能快速生成 // 示例右键 - Generate - Constructor, Getter and Setter }3.4 实现数据访问层DAODAO层封装所有数据库操作。// 文件路径src/main/java/com/example/accountbook/dao/AccountDao.java package com.example.accountbook.dao; import com.example.accountbook.model.Account; import com.example.accountbook.model.AccountType; import com.example.accountbook.util.DBUtil; import java.sql.*; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; public class AccountDao { // 初始化数据库创建表如果不存在 public void initDatabase() { String sql CREATE TABLE IF NOT EXISTS account ( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL, category TEXT NOT NULL, amount REAL NOT NULL, remark TEXT, date TEXT NOT NULL );; try (Connection conn DBUtil.getConnection(); Statement stmt conn.createStatement()) { stmt.execute(sql); System.out.println(Table created or already exists.); } catch (SQLException e) { e.printStackTrace(); } } // 插入一条账目记录 public boolean insert(Account account) { String sql INSERT INTO account(type, category, amount, remark, date) VALUES(?,?,?,?,?); try (Connection conn DBUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, account.getType().name()); pstmt.setString(2, account.getCategory()); pstmt.setDouble(3, account.getAmount()); pstmt.setString(4, account.getRemark()); pstmt.setString(5, account.getDate().toString()); // 存储为 yyyy-MM-dd pstmt.executeUpdate(); return true; } catch (SQLException e) { e.printStackTrace(); return false; } } // 查询所有账目记录 public ListAccount findAll() { ListAccount accountList new ArrayList(); String sql SELECT * FROM account ORDER BY date DESC, id DESC; try (Connection conn DBUtil.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql)) { while (rs.next()) { Account account new Account(); account.setId(rs.getInt(id)); account.setType(AccountType.valueOf(rs.getString(type))); account.setCategory(rs.getString(category)); account.setAmount(rs.getDouble(amount)); account.setRemark(rs.getString(remark)); account.setDate(LocalDate.parse(rs.getString(date))); accountList.add(account); } } catch (SQLException e) { e.printStackTrace(); } return accountList; } // 根据ID删除一条记录 public boolean deleteById(int id) { String sql DELETE FROM account WHERE id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setInt(1, id); int affectedRows pstmt.executeUpdate(); return affectedRows 0; } catch (SQLException e) { e.printStackTrace(); return false; } } // 更新一条记录留作练习本文UI部分暂不实现编辑功能 public boolean update(Account account) { // 实现思路与insert类似使用UPDATE语句WHERE id? // 略... return false; } }4. 构建Swing用户界面界面是用户直接交互的部分我们将创建一个主窗口和一个用于添加记录的对话框。4.1 创建主窗口MainFrame主窗口包含一个表格用于展示所有账目以及几个功能按钮。// 文件路径src/main/java/com/example/accountbook/ui/MainFrame.java package com.example.accountbook.ui; import com.example.accountbook.dao.AccountDao; import com.example.accountbook.model.Account; import com.example.accountbook.model.AccountType; import javax.swing.*; import javax.swing.table.DefaultTableModel; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.List; public class MainFrame extends JFrame { private JTable table; private DefaultTableModel tableModel; private AccountDao accountDao new AccountDao(); private JLabel totalIncomeLabel; private JLabel totalExpenseLabel; private JLabel balanceLabel; public MainFrame() { setTitle(本地记账本); setSize(800, 600); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); // 窗口居中 initUI(); loadAccountData(); calculateSummary(); } private void initUI() { // 使用BorderLayout布局 setLayout(new BorderLayout()); // 1. 顶部按钮面板 JPanel buttonPanel new JPanel(new FlowLayout(FlowLayout.LEFT)); JButton addButton new JButton(添加记录); JButton deleteButton new JButton(删除选中); JButton refreshButton new JButton(刷新); buttonPanel.add(addButton); buttonPanel.add(deleteButton); buttonPanel.add(refreshButton); add(buttonPanel, BorderLayout.NORTH); // 2. 中部表格 String[] columnNames {ID, 类型, 类别, 金额, 日期, 备注}; tableModel new DefaultTableModel(columnNames, 0) { Override public boolean isCellEditable(int row, int column) { // 禁止表格单元格直接编辑 return false; } }; table new JTable(tableModel); JScrollPane scrollPane new JScrollPane(table); add(scrollPane, BorderLayout.CENTER); // 3. 底部统计信息面板 JPanel summaryPanel new JPanel(new GridLayout(1, 3)); totalIncomeLabel new JLabel(总收入0.0); totalExpenseLabel new JLabel(总支出0.0); balanceLabel new JLabel(结余0.0); summaryPanel.add(totalIncomeLabel); summaryPanel.add(totalExpenseLabel); summaryPanel.add(balanceLabel); add(summaryPanel, BorderLayout.SOUTH); // 4. 按钮事件监听 addButton.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { AddEditDialog dialog new AddEditDialog(MainFrame.this, accountDao); dialog.setVisible(true); // 显示添加对话框模态 // 对话框关闭后刷新数据 loadAccountData(); calculateSummary(); } }); deleteButton.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { int selectedRow table.getSelectedRow(); if (selectedRow -1) { JOptionPane.showMessageDialog(MainFrame.this, 请先选择要删除的记录, 提示, JOptionPane.WARNING_MESSAGE); return; } // 从表格模型获取IDID列是第0列 int id (int) tableModel.getValueAt(selectedRow, 0); int confirm JOptionPane.showConfirmDialog(MainFrame.this, 确定要删除这条记录吗, 确认删除, JOptionPane.YES_NO_OPTION); if (confirm JOptionPane.YES_OPTION) { boolean success accountDao.deleteById(id); if (success) { JOptionPane.showMessageDialog(MainFrame.this, 删除成功); loadAccountData(); calculateSummary(); } else { JOptionPane.showMessageDialog(MainFrame.this, 删除失败); } } } }); refreshButton.addActionListener(e - { loadAccountData(); calculateSummary(); }); } // 从数据库加载数据并刷新表格 private void loadAccountData() { tableModel.setRowCount(0); // 清空表格 ListAccount accounts accountDao.findAll(); for (Account account : accounts) { Object[] rowData { account.getId(), account.getType().getDisplayName(), // 显示中文 account.getCategory(), account.getAmount(), account.getDate().toString(), account.getRemark() null ? : account.getRemark() }; tableModel.addRow(rowData); } } // 计算总收入、总支出和结余 private void calculateSummary() { double totalIncome 0; double totalExpense 0; ListAccount accounts accountDao.findAll(); for (Account account : accounts) { if (account.getType() AccountType.INCOME) { totalIncome account.getAmount(); } else { totalExpense account.getAmount(); } } double balance totalIncome - totalExpense; totalIncomeLabel.setText(String.format(总收入%.2f, totalIncome)); totalExpenseLabel.setText(String.format(总支出%.2f, totalExpense)); balanceLabel.setText(String.format(结余%.2f, balance)); } }4.2 创建添加/编辑记录对话框这是一个独立的窗口用于输入新的账目信息。// 文件路径src/main/java/com/example/accountbook/ui/AddEditDialog.java package com.example.accountbook.ui; import com.example.accountbook.dao.AccountDao; import com.example.accountbook.model.Account; import com.example.accountbook.model.AccountType; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.time.LocalDate; public class AddEditDialog extends JDialog { private JComboBoxString typeComboBox; private JTextField categoryField; private JTextField amountField; private JTextField dateField; private JTextArea remarkArea; private AccountDao accountDao; public AddEditDialog(JFrame parent, AccountDao accountDao) { super(parent, 添加记账记录, true); // true表示模态对话框 this.accountDao accountDao; setSize(400, 300); setLocationRelativeTo(parent); setLayout(new BorderLayout()); initUI(); } private void initUI() { // 主面板使用GridBagLayout进行灵活布局 JPanel mainPanel new JPanel(new GridBagLayout()); GridBagConstraints gbc new GridBagConstraints(); gbc.insets new Insets(5, 5, 5, 5); // 组件间距 gbc.fill GridBagConstraints.HORIZONTAL; gbc.weightx 1.0; // 类型 gbc.gridx 0; gbc.gridy 0; mainPanel.add(new JLabel(类型), gbc); gbc.gridx 1; typeComboBox new JComboBox(new String[]{AccountType.INCOME.getDisplayName(), AccountType.EXPENSE.getDisplayName()}); mainPanel.add(typeComboBox, gbc); // 类别 gbc.gridx 0; gbc.gridy 1; mainPanel.add(new JLabel(类别), gbc); gbc.gridx 1; categoryField new JTextField(15); mainPanel.add(categoryField, gbc); // 金额 gbc.gridx 0; gbc.gridy 2; mainPanel.add(new JLabel(金额), gbc); gbc.gridx 1; amountField new JTextField(15); mainPanel.add(amountField, gbc); // 日期 gbc.gridx 0; gbc.gridy 3; mainPanel.add(new JLabel(日期 (yyyy-MM-dd)), gbc); gbc.gridx 1; dateField new JTextField(15); dateField.setText(LocalDate.now().toString()); // 默认今天 mainPanel.add(dateField, gbc); // 备注 gbc.gridx 0; gbc.gridy 4; mainPanel.add(new JLabel(备注), gbc); gbc.gridx 1; gbc.gridheight 2; gbc.fill GridBagConstraints.BOTH; gbc.weighty 1.0; remarkArea new JTextArea(3, 15); mainPanel.add(new JScrollPane(remarkArea), gbc); add(mainPanel, BorderLayout.CENTER); // 底部按钮面板 JPanel buttonPanel new JPanel(); JButton saveButton new JButton(保存); JButton cancelButton new JButton(取消); buttonPanel.add(saveButton); buttonPanel.add(cancelButton); add(buttonPanel, BorderLayout.SOUTH); // 保存按钮事件 saveButton.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { if (validateInput()) { saveAccount(); } } }); // 取消按钮事件 cancelButton.addActionListener(e - dispose()); // 关闭对话框 } // 验证输入 private boolean validateInput() { if (categoryField.getText().trim().isEmpty()) { JOptionPane.showMessageDialog(this, 类别不能为空, 输入错误, JOptionPane.ERROR_MESSAGE); return false; } try { double amount Double.parseDouble(amountField.getText().trim()); if (amount 0) { JOptionPane.showMessageDialog(this, 金额必须大于0, 输入错误, JOptionPane.ERROR_MESSAGE); return false; } } catch (NumberFormatException e) { JOptionPane.showMessageDialog(this, 金额格式不正确, 输入错误, JOptionPane.ERROR_MESSAGE); return false; } try { LocalDate.parse(dateField.getText().trim()); // 验证日期格式 } catch (Exception e) { JOptionPane.showMessageDialog(this, 日期格式不正确应为 yyyy-MM-dd, 输入错误, JOptionPane.ERROR_MESSAGE); return false; } return true; } // 保存到数据库 private void saveAccount() { try { Account account new Account(); // 根据下拉框选择设置类型 String selectedType (String) typeComboBox.getSelectedItem(); account.setType(selectedType.equals(AccountType.INCOME.getDisplayName()) ? AccountType.INCOME : AccountType.EXPENSE); account.setCategory(categoryField.getText().trim()); account.setAmount(Double.parseDouble(amountField.getText().trim())); account.setRemark(remarkArea.getText().trim()); account.setDate(LocalDate.parse(dateField.getText().trim())); boolean success accountDao.insert(account); if (success) { JOptionPane.showMessageDialog(this, 保存成功); dispose(); // 关闭对话框 } else { JOptionPane.showMessageDialog(this, 保存失败请重试); } } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(this, 发生错误 e.getMessage(), 错误, JOptionPane.ERROR_MESSAGE); } } }4.3 程序入口最后我们创建程序的主类初始化数据库并启动主窗口。// 文件路径src/main/java/com/example/accountbook/MainApp.java package com.example.accountbook; import com.example.accountbook.dao.AccountDao; import com.example.accountbook.ui.MainFrame; import javax.swing.*; public class MainApp { public static void main(String[] args) { // 确保GUI创建在事件调度线程上这是Swing的最佳实践 SwingUtilities.invokeLater(() - { try { // 可选设置系统默认外观使应用更贴近操作系统风格 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { e.printStackTrace(); } // 初始化数据库表 AccountDao dao new AccountDao(); dao.initDatabase(); // 创建并显示主窗口 MainFrame frame new MainFrame(); frame.setVisible(true); }); } }5. 运行与测试运行程序在IDE中直接运行MainApp类的main方法。首次运行程序启动后会在项目根目录下生成一个account.db数据库文件。你可以用DB Browser for SQLite打开它查看自动创建的account表。功能测试点击“添加记录”输入信息后保存。观察主窗口表格是否刷新底部统计信息是否更新。选中表格中的一行点击“删除选中”确认删除功能。点击“刷新”按钮确认数据重新加载。至此一个具备基本增删查功能的本地记账本就完成了。界面虽然简陋但核心功能完整。6. 功能扩展与优化建议一个基础版本完成后我们可以从多个角度进行增强使其更实用、更健壮。6.1 数据验证与用户体验输入提示为金额、日期字段添加输入提示或格式限制如使用JFormattedTextField。类别管理将类别如餐饮、交通单独建表管理添加记录时从下拉框选择避免手动输入错误。数据导出增加将表格数据导出为Excel或CSV文件的功能便于备份和分析。图表分析引入JFreeChart等图表库绘制月度收支趋势图、类别占比饼图实现真正的“可视化”。6.2 代码结构与工程化服务层剥离在DAO和UI之间引入Service层处理更复杂的业务逻辑如计算统计信息、数据验证等。使用TableModel可以创建一个自定义的AccountTableModel继承自AbstractTableModel将数据列表直接绑定到模型实现更规范的MVC。依赖注入考虑使用简单的依赖注入如手动注入或轻量级框架来管理AccountDao等组件提高可测试性。日志记录使用SLF4J Logback替换System.out.println和e.printStackTrace()记录操作日志和错误信息。6.3 数据层增强编辑功能实现记录的编辑功能。可以在AddEditDialog中增加一个构造方法接收一个Account对象用于填充表单并将保存操作改为更新。条件查询增加按日期范围、类型、类别进行筛选查询的功能。数据备份与恢复提供菜单选项让用户可以手动备份和恢复account.db文件。6.4 部署与分发打包为JAR使用Maven的maven-assembly-plugin或maven-shade-plugin将项目及其所有依赖打包成一个可执行的“胖JAR”uber JAR。制作原生安装包使用jpackage工具JDK 14将JAR打包成平台特定的安装包如Windows的exe、macOS的dmg让用户像安装普通软件一样使用。7. 常见问题与排查思路在开发过程中你可能会遇到以下问题问题现象可能原因解决思路运行后找不到org.sqlite.JDBC类Maven依赖未正确下载或引入。1. 检查pom.xml依赖是否正确。2. 在IDE中执行Maven的Reimport或Reload Project。3. 检查项目外部库中是否有sqlite-jdbc-*.jar。程序启动后界面一片空白或布局错乱Swing组件未在事件调度线程EDT上创建。确保所有Swing组件的创建和修改都在SwingUtilities.invokeLater()中执行。点击按钮无反应事件监听器未正确绑定或监听器内代码有异常被吞没。1. 检查addActionListener是否被调用。2. 在监听器代码内部添加try-catch并打印异常堆栈。数据库操作增删改成功但界面未刷新操作数据库后未调用刷新表格和统计信息的方法。在插入、删除等操作成功后手动调用loadAccountData()和calculateSummary()。日期格式解析错误输入的日期字符串格式与LocalDate.parse期望的格式不符。1. 确保输入格式为yyyy-MM-dd。2. 可以使用DateTimeFormatter定义明确的格式进行解析。表格中中文显示乱码控制台或Swing默认编码与系统不符。1. 启动JVM时添加参数-Dfile.encodingUTF-8。2. 检查IDE和系统的默认编码设置。8. 项目总结与学习路径通过这个“Java Swing SQLite 可视化本地记账本”项目我们串联起了Java桌面应用开发的核心技能链从Swing GUI布局、事件处理到SQLite数据库连接、JDBC CRUD操作再到简单的三层架构设计。核心收获Swing实战掌握了JFrame,JPanel,JTable,JButton,JDialog等核心组件的使用以及BorderLayout,GridBagLayout等布局管理器。数据库操作学会了如何使用JDBC连接和操作SQLite包括创建表、插入、查询、删除数据并理解了PreparedStatement防SQL注入的重要性。MVC模式实践虽然简单但项目清晰地分离了数据Model、界面View和控制逻辑Controller/Listener。工程化思维开始使用Maven管理依赖规划项目包结构编写可复用的工具类。下一步可以深入学习更高级的GUI框架了解JavaFX它是Swing的现代替代者支持CSS样式和FXML声明式布局。持久层框架学习MyBatis或JPA如Hibernate它们能极大简化数据库操作代码。项目架构研究更清晰的分层架构如Controller-Service-Dao以及设计模式在GUI项目中的应用。打包与部署深入研究jpackage和各类Java打包工具制作专业的安装程序。这个项目代码已具备完整功能你可以直接在此基础上进行扩展和美化。动手修改和添加功能是巩固知识的最佳方式例如尝试实现上面提到的“图表分析”或“数据导出”功能。如果在实践过程中遇到任何问题欢迎在评论区交流探讨。
返回列表