ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java Swing个人财务系统源码解析:JDBC连接、MySQL建表与桌面应用实战

Java Swing个人财务系统源码解析:JDBC连接、MySQL建表与桌面应用实战 简介这是一套基于Java Swing开发、采用C/S结构的个人财务收支管理系统源码整体围绕eclipse/Myeclipse与MySQL数据库构建面向Java初学者、课程设计及毕业设计场景。系统覆盖用户管理、类别管理、收入管理、支出管理、个人信息修改、密码修改、登录与退出等模块可清晰演示Swing窗体布局、按钮事件处理、JDBC数据库连接与增删改查操作适合用来理解桌面应用从界面到数据层的完整开发链路。资源包共111个文件包括22个Java源文件、80个class编译文件、5个jar依赖库、1个SQL数据库脚本以及项目配置文件压缩包仅2.91MB结构简洁便于导入开发环境快速运行。系统初始账号为admin/admin数据库脚本为javacaiwu.sql可在此基础上二次开发或学习调试。目前已有207人学习浏览适合需要可运行项目源码并对照学习SwingMySQL开发的读者。1. 一套Java Swing个人财务系统源码先搞清楚它解决什么问题期末课程设计、毕设或者想补Java桌面开发的空最容易碰到的一类题就是“个人财务收支管理系统”。这类系统用Java Swing做界面Eclipse做开发环境MySQL存数据C/S结构——客户端负责交互数据库负责持久化。你拿到源码后先别急着跑它的核心价值不在界面多好看而在于把JDBC连接、表结构设计、增删改查、统计报表这一套完整串起来。适合想快速跑通一个桌面项目、准备答辩演示的人往下读如果你只想抄界面那不如直接去看现成的JavaFX项目更省事Swing的优势恰恰在简单直接、依赖少、跑起来不挑机器。2. 环境与工程骨架Eclipse JDBC MySQL 的选型和组织方式这种CS结构的Swing项目最常见的源码组织方式是Eclipse里的一个Java Project按util数据库连接、ui窗口、dao数据访问、model实体、service业务分包。拿到源码第一件事不是双击运行而是先看包结构和lib目录搞清楚驱动JAR放没放、连接串配在哪再谈跑起来。2.1 版本组合怎么选JDK 8、MySQL 5.7/8.0 与连接驱动的坑个人财务系统是典型的轻量桌面应用JDK 8完全够用Eclipse我用2020年之后的版本配JDK 8最省心。数据库端常见两种组合决定了后面驱动和连接串的写法组合驱动类名连接串是否带serverTimezone适用场景MySQL 5.7 mysql-connector-java 5.1.49com.mysql.jdbc.Driver可不带老机器、旧课设源码MySQL 8.0 mysql-connector-java 8.0.33com.mysql.cj.jdbc.Driver必须带新装环境、新写项目我一般建议直接上MySQL 8.0加配套8.x驱动因为官网默认下载就是8.x用老驱动连新版数据库会触发SSL握手报错处理起来更绕。JAR包放进项目的lib目录然后在Eclipse里选中lib目录右键Build Path - Add to Build Path。这步漏了编译时IDE不会直接报错运行到Class.forName那行才会抛ClassNotFoundException新手容易卡很久。选型时还要注意Eclipse的Run配置里JRE要选JDK而不是纯JRE否则Swing类库加载不全。新手跑这个标题的源码大概率栽在环境组合上先把这层理清后面代码本身反而不难。2.2 源码导入EclipsePackage结构、Build Path和Run配置拿到源码压缩包先解压看有没有.classpath和.project这两个文件。有的话Eclipse里File - Import - Existing Projects into Workspace选中解压目录直接导入没有的话新建一个Java Project把src目录整个拷过去再把lib下的JAR手动加回Build Path。跑之前确认两件事。第一项目的JRE选的是JDK而不是纯JRESwing类库在JDK里才完整。第二Run Configuration的Main class指向带main方法的入口类Swing项目通常叫LoginUI或MainFrame别选成某个测试类。这两步做完绝大多数“导入后跑不起来”的问题就消失了。不少源码压缩包里还带一个finance.sql这样的初始化文件。用Navicat或者mysql命令行执行一遍库和表就建好了不用自己手敲SQL。执行前先打开看一眼字符集如果CREATE DATABASE后面没写DEFAULT CHARACTER SET你自己补上utf8mb4免得后面中文乱码。然后看源码的包分布常见的分层是model放实体、dao放SQL操作、service放业务逻辑、ui放Swing界面。有些课设源码会把所有setVisible和addActionListener全堆在一个类里这种代码能跑但加功能很难受拿到这种源码我一般不动整体结构只在ui包下加新面板。2.3 建库建表收支两张表的最小设计个人财务系统最核心的就两张表用户表t_user和流水表t_record。流水表是重头字段不要贪多能支撑“谁、什么时间、收支类型、分类、金额、备注”就够了。SQL如下CREATE DATABASE IF NOT EXISTS finance DEFAULT CHARACTER SET utf8mb4; USE finance; CREATE TABLE IF NOT EXISTS t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE IF NOT EXISTS t_record ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, type TINYINT NOT NULL COMMENT 0支出 1收入, category VARCHAR(20) NOT NULL, amount DECIMAL(10,2) NOT NULL, remark VARCHAR(200), record_date DATE NOT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_user_date (user_id, record_date), FOREIGN KEY (user_id) REFERENCES t_user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里几个参数值得展开。amount用DECIMAL(10,2)而不是double金额经不起浮点误差10位有效数字、2位小数对个人记账绰绰有余。type用TINYINT比VARCHAR省空间也方便在Java端做映射0是支出1是收入。record_date用DATE只存“哪一天”的账不掺时间统计报表按天分组会简单很多。create_time是系统落库时间和记账日期分开避免用户补录昨天的账时把时间搞乱。KEY idx_user_date是给月统计查询准备的联合索引数据量一大GROUP BY就指着它走。2.4 DBUtil连接工具类连接串、驱动、时区参数一次配好连接工具类每个窗口都要用写一个静态方法返回Connection代码放util包package util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/finance ?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }静态块里Class.forName的目的是让DriverManager注册驱动类MySQL 8的驱动类名是com.mysql.cj.jdbc.Driver5.x是com.mysql.jdbc.Driver这里最容易看错。URL里的useUnicodetrue和characterEncodingutf8保证中文读写正常serverTimezoneAsia/Shanghai配合MySQL 8的时区校验如果用的是5.1.49旧驱动这一项可以不写。账号和密码直接写在常量里学习项目没问题真要答辩或者工作里展示建议改成读取配置文件源码一旦分享出去数据库口令就跟着泄露了。拿到这4个包写第一行代码之前建议先验证DBUtil能不能通写一个带main的临时类调用DBUtil.getConnection()并打印conn.isClosed()。返回false说明驱动、URL、账号密码、MySQL服务四项都没问题再接Swing界面就等于接了个普通Java类没有网络和数据库变量在捣乱。这一步能筛掉后面八成“界面点登录没反应”的诡异问题。3. Swing界面落地登录、主窗口、记账与列表展示Swing项目的界面代码量通常占一半以上但真正考验人的不是布局而是组件之间的数据联动登录成功要跳主窗口主窗口里切Tab要刷新不同面板点保存后表格要立即能看到新记录。这一章按从登录到列表的顺序把四个关键界面的写法和联动原理讲清楚。3.1 登录窗口事件监听与用户校验登录窗口是所有功能的大门。结构上就是一个JFrame上面放两个文本框、一个密码框、一个登录按钮。关键代码在按钮的ActionListener里public class LoginUI extends JFrame { private JTextField userField new JTextField(15); private JPasswordField passField new JPasswordField(15); public LoginUI() { setTitle(个人财务收支管理系统 - 登录); setSize(360, 220); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); JPanel panel new JPanel(new GridBagLayout()); panel.add(new JLabel(用户名), gbc(0, 0)); panel.add(userField, gbc(1, 0)); panel.add(new JLabel(密码), gbc(0, 1)); panel.add(passField, gbc(1, 1)); JButton loginBtn new JButton(登录); loginBtn.addActionListener(e - { String username userField.getText().trim(); String password new String(passField.getPassword()); if (new UserDao().validate(username, password)) { new MainUI(username).setVisible(true); dispose(); } else { JOptionPane.showMessageDialog(this, 用户名或密码错误); } }); panel.add(loginBtn, gbc(1, 2)); setContentPane(panel); } // gbc(int x, int y) 是返回GridBagConstraints的辅助方法 }两个细节值得说。第一密码框用JPasswordField取值时要new String(passField.getPassword())而不是getText()这是Java对安全的老传统拿到的char[]用完要尽快丢。第二userField.getText().trim()把首尾空格去掉避免用户手滑多敲空格导致登录失败。validate方法里SQL是SELECT COUNT(*) FROM t_user WHERE username? AND password?密码在入库时建议做SHA-256摘要别用明文。实际项目里登录逻辑还会加“锁定窗口”或“连续失败次数限制”课设源码一般没有。答辩时想多讲两句可以在validate返回false时统计失败次数同一用户名连续错3次就禁用登录按钮30秒这个细节成本低、说得出原理。3.2 主窗口布局菜单栏、工具栏与卡片式切换登录成功后的主窗口常见做法是JFrame加JTabbedPane把“记一笔”“明细查询”“统计报表”三个面板并排。JTabbedPane的好处是不用自己管理面板切换点Tab就自动显示对应内容public class MainUI extends JFrame { private String username; public MainUI(String username) { this.username username; setTitle(个人财务收支管理系统 - 当前用户: username); setSize(900, 600); setLocationRelativeTo(null); JMenuBar menuBar new JMenuBar(); JMenu menu new JMenu(系统); JMenuItem exitItem new JMenuItem(退出); exitItem.addActionListener(e - System.exit(0)); menu.add(exitItem); menuBar.add(menu); setJMenuBar(menuBar); JTabbedPane tabs new JTabbedPane(); tabs.addTab(记一笔, new RecordPanel(username)); tabs.addTab(明细查询, new QueryPanel(username)); tabs.addTab(统计报表, new ReportPanel(username)); setContentPane(tabs); } }这里的重点是setTitle里带上当前用户名功能简单但答辩时很显眼说明你考虑了多用户隔离。三个面板构造函数都接收username是为了让每个面板的SQL都带WHERE user_id?避免A用户看到B用户的账。很多课设源码犯的错就是把user_id写死成1换个账号登录数据全是同一个人这种问题在演示时一戳就破。3.3 记账表单金额校验与分类下拉记账面板是系统的数据入口。布局上用JPanel配合GridBagLayout或简单的GridLayout放类型下拉、分类下拉、金额输入框、日期输入框、备注文本域和保存按钮。类型和分类之间要做联动typeCombo.addActionListener(e - { boolean isIncome 收入.equals(typeCombo.getSelectedItem()); categoryCombo.removeAllItems(); ListString categories isIncome ? Arrays.asList(工资, 奖金, 理财, 其他) : Arrays.asList(餐饮, 交通, 购物, 居住, 娱乐, 医疗, 其他); for (String c : categories) { categoryCombo.addItem(c); } });分类数据另一种做法是建一张t_category表在数据库里维护适合想扩展的源码。如果你拿到的源码把分类写死在Java数组里也能用但改分类得重新编译。保存按钮的校验逻辑至少要有两层金额是不是合法的数字日期格式是不是yyyy-MM-dd。用BigDecimal的构造方法就能拦掉非法输入saveBtn.addActionListener(e - { try { BigDecimal amount new BigDecimal(amountField.getText().trim()); if (amount.compareTo(BigDecimal.ZERO) 0) { JOptionPane.showMessageDialog(this, 金额必须大于0); return; } LocalDate date LocalDate.parse(dateField.getText().trim()); // 组装Record对象调用RecordDao.insert() JOptionPane.showMessageDialog(this, 保存成功); clearForm(); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, 金额格式不正确); } catch (DateTimeParseException ex) { JOptionPane.showMessageDialog(this, 日期格式应为yyyy-MM-dd); } });用BigDecimal接收金额输入是顺手解决浮点精度问题的好习惯。金额位数超过两位、或者用户输入负数都在前端拦掉数据库里的DECIMAL(10,2)再兜一层底。日期不弹日历选择器的话可以直接在JTextField里按yyyy-MM-dd输入想体验好点用JDateChooser这个第三方组件但课设阶段没必要引入额外jar依赖。3.4 JTable动态刷新TableModel与渲染器明细查询面板的核心是JTable。JTable本身只管显示数据要喂给TableModel。最简单的写法是用DefaultTableModel查完数据重新setModel一次界面就会刷新public void loadData(int userId) { ListRecord list new RecordDao().listByUser(userId); String[] columns {日期, 类型, 分类, 金额, 备注}; Object[][] data new Object[list.size()][5]; for (int i 0; i list.size(); i) { Record r list.get(i); data[i][0] r.getRecordDate().toString(); data[i][1] r.getType() 0 ? 支出 : 收入; data[i][2] r.getCategory(); data[i][3] r.getAmount(); data[i][4] r.getRemark(); } table.setModel(new DefaultTableModel(data, columns)); }这套写法的关键是每次刷新都构建新的DefaultTableModel。如果只改了底层数据而不调setModel表格永远显示旧数据这是Swing新手最常见的“按钮点了没反应”。另一个加分项是给金额列加自定义渲染器让支出显示红色、收入显示绿色table.getColumnModel().getColumn(3).setCellRenderer( new DefaultTableCellRenderer() { Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column); Object type table.getValueAt(row, 1); if (支出.equals(type)) { c.setForeground(new Color(0xCC0000)); } else { c.setForeground(new Color(0x006600)); } return c; } } );渲染器是JTable进阶里最实用的技巧。getValueAt(row, 1)取的是当前行第二列的“类型”值所以颜色判断不会错位。这段代码不加也不影响功能但加了之后演示效果会有明显提升评审看到彩色收入和支出比看一片黑字更容易留下印象。4. 业务逻辑与统计SQL聚合、日期范围和金额精度界面跑通之后系统的骨架就有了。但“个人财务”的关键在“统计”两个字。这一章讲三块按月收支统计怎么写SQL分类汇总怎么为报表准备数据以及多笔记录批量入库时如何用事务保证一致性。4.1 月收入支出的聚合SQL写法按月统计SQL用DATE_FORMAT GROUP BY SUM一句搞定SELECT DATE_FORMAT(record_date, %Y-%m) AS month, type, SUM(amount) AS total FROM t_record WHERE user_id ? GROUP BY DATE_FORMAT(record_date, %Y-%m), type ORDER BY month DESC;返回结果里month是“2026-01”这种字符串type是0或1。在DAO层把它们封装成一个MonthStat对象字段month、type、total再在界面里把收入和支出分别取出来就能画一个简单的月收支柱状图。这里最容易踩的坑是直接在SQL里用MONTH(record_date)而不是DATE_FORMAT那样跨年会串数据——1月的账会把2025年和2026年的一起统计进去。所以宁可让SQL多写几个字符也要带上%Y。DAO层的写法可以配合try-with-resources省去手动关连接的样板代码public ListMonthStat sumByMonth(int userId) throws SQLException { String sql SELECT DATE_FORMAT(record_date, %Y-%m) AS month, type, SUM(amount) AS total FROM t_record WHERE user_id ? GROUP BY DATE_FORMAT(record_date, %Y-%m), type ORDER BY month DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, userId); try (ResultSet rs ps.executeQuery()) { ListMonthStat list new ArrayList(); while (rs.next()) { MonthStat stat new MonthStat(); stat.setMonth(rs.getString(month)); stat.setType(rs.getInt(type)); stat.setTotal(rs.getBigDecimal(total)); list.add(stat); } return list; } } }GROUP BY左边和SELECT左边必须一致MySQL虽然默认开启了ONLY_FULL_GROUP_BY但实际老版本可能放宽写出SELECT * GROUP BY type这种语句也能跑出脏数据。统计场景里SELECT只保留分组字段和聚合函数是底线。4.2 分类汇总与报表数据准备分类汇总回答的是“这个月钱都花在哪了”。SQL按category分组再按金额倒序SELECT category, SUM(amount) AS total FROM t_record WHERE user_id ? AND type 0 AND record_date BETWEEN ? AND ? GROUP BY category ORDER BY total DESC;两个参数分别传这个月的第一天和最后一天。算第一天可以用LocalDate.now().with(TemporalAdjusters.firstDayOfMonth())最后一天是with(TemporalAdjusters.lastDayOfMonth())不要用“01”和“31”写死因为2月不是31天。把结果填进一个JTable或者组合成饼图的Dataset就是一个典型的收支构成展示。排序用ORDER BY total DESC这样表格第一行永远是花得最多的分类不用在Java里再排一遍。如果拿到的源码里统计查询是查出全部记录后在Java内存里for循环累加数据量小也能跑。但表里有了几千条流水后内存累加会拖慢界面SQL聚合是更稳的做法。这也是“源码能不能改造”的关键分水岭统计逻辑留在SQL里界面代码就不用动。类别统计的结果除了填JTable也可以直接组装成饼图的dataset。JFreeChart是Java里最常用的图表库从官网下载jfreechart和jcommon两个jar放进lib用PieDataset接收CategoryStat列表ChartFactory.newPieChart(...)生成ChartPanel放进统计面板代码量不大。但图表只是同一份统计结果的另一种呈现先保证表格里的数字是对的再谈画图。4.3 事务处理批量记账不丢数据批量记账是给“一键导入历史流水”这类功能准备的。多个INSERT要么全成功要么全回滚。JDBC的默认提交模式是每执行一条就commit所以批量插入前要关掉自动提交public void batchInsert(ListRecord records) throws SQLException { Connection conn null; PreparedStatement ps null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); String sql INSERT INTO t_record(user_id, type, category, amount, remark, record_date) VALUES(?,?,?,?,?,?); ps conn.prepareStatement(sql); for (Record r : records) { ps.setInt(1, r.getUserId()); ps.setInt(2, r.getType()); ps.setString(3, r.getCategory()); ps.setBigDecimal(4, r.getAmount()); ps.setString(5, r.getRemark()); ps.setDate(6, java.sql.Date.valueOf(r.getRecordDate())); ps.addBatch(); } ps.executeBatch(); conn.commit(); } catch (SQLException e) { if (conn ! null) { conn.rollback(); } throw e; } finally { if (ps ! null) { ps.close(); } if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } }这段代码的核心是setAutoCommit(false)和最后的commit、rollback配对。addBatch把SQL攒在驱动里executeBatch一次性发给MySQL比循环executeUpdate快一个数量级几千条数据也是秒级。finally里把autoCommit恢复成true是给连接池准备的虽然这里用的是DriverManager直连但养成这个习惯将来换连接池就不会留坑。注意setDate的第二参数要求java.sql.Date不能用java.time.LocalDate直接传需要valueOf转换。这个转换漏了编译直接报类型不兼容。事务的范围只包insert查询操作不进事务这个边界别搞混。批量导入的入口可以放在系统菜单里选一个文本文件一行一条流水解析成List 后调batchInsert整批进来或者整批回滚不会出现导了一半发现重复数据的情况。5. 避坑/常见问题/排查Swing MySQL 项目里最容易翻车的5个点这一章把我在类似项目里反复遇到的5个问题按“现象、原因、解决”写清楚。这些问题不解决功能写得再好都跑不起来。5.1 现象MySQL 8连接报时区错误现象项目第一次启动登录窗口没弹出来控制台抛SQLNonTransientConnectionException提示The server time zone value xxx is unrecognized。原因MySQL 8的时区校验比5.7严格连接串里没有serverTimezone参数驱动就无法跟服务器对齐时区。解决在DBUtil的URL末尾加上serverTimezoneAsia/Shanghai或者改成serverTimezoneGMT%2B8。前者更推荐因为能处理夏令时。如果驱动是5.x换成mysql-connector-java 8.0.33类名同步改成com.mysql.cj.jdbc.Driver。5.2 现象中文乱码现象程序跑通后界面输入中文保存到数据库没问题但表格或者导出的CSV里中文全是问号。原因三个环节任一环节没对齐——数据库表字符集不是utf8mb4、JDBC连接串没带characterEncodingutf8、导出CSV时用了系统默认编码。解决按顺序排查。建库SQL加DEFAULT CHARACTER SET utf8mb4连接串加useUnicodetruecharacterEncodingutf8导出CSV时用OutputStreamWriter(new FileOutputStream(...), StandardCharsets.UTF_8)。如果CSV是要给Excel打开的还要在文件开头写一个BOM头(EF BB BF)否则Excel会当成ANSI解析导致中文乱码。这一个BOM的坑我在导出功能上翻过两次车。5.3 现象JTable修改后不刷新现象在“记一笔”面板保存一条新记录切到“明细查询”面板表格里看不到刚记的账。原因JTable绑定的是旧的DefaultTableModel实例数据源变了但模型没换表格自然不更新。Swing不会自动感知数据库变化。解决在明细查询面板的构造方法里、以及每次从其它Tab切回来时都调用loadData()重新setModel。最省事的方案是在MainUI里给JTabbedPane加一个ChangeListenerTab切换到明细查询时就刷新一次。另一个更贴合的方案是保存成功后直接调用queryPanel.loadData()主动刷新。两种都行推荐后者代码路径更明确。5.4 现象eclipse找不到或无法加载主类现象右键运行项目Eclipse弹框提示“找不到或无法加载主类”控制台有时还会出现org.apache.catalina.startup.Bootstrap。原因这个工程是从Web项目改过来的或者Eclipse的Run Configuration残留了旧配置项目里混入了Tomcat相关的Classpath条目。解决Project Properties - Java Build Path - Libraries把Tomcat相关的条目Remove掉确认没有引用错误。然后右键项目 - Maven - Update Project如果是Maven工程或者Project - Clean。最后检查Run Configuration把Main class重新选成LoginUI。纯Java桌面项目不依赖Tomcat容器看到Bootstrap就要清理。5.5 现象金额计算出现0.30000000000000004现象记一笔“买早饭3元买咖啡3元”统计支出显示6.000000000000001。原因Java的double是二进制浮点无法精确表示0.1、0.3这类十进制小数累加后误差累积就露出来了。解决金额字段全链路用BigDecimal数据库列用DECIMAL(10,2)Java端接收用BigDecimal前端输入校验也走BigDecimal构造。加法不要直接add到double上而是声明一个BigDecimal total循环add。这条解决了财务报表的数字才敢拿给人看。6. 进阶给系统加数据备份与CSV导出让源码真正可用6.1 用mysqldump做每日备份个人财务系统跑起来后最怕的不是界面丑是数据库文件坏了或者误删。MySQL自带mysqldump一行命令就能把整个finance库导出成SQL文件mysqldump -u root -p finance finance_backup_$(date %Y%m%d).sqlWindows上没有date命令可以写在批处理里用%date:~0,4%%date:~5,2%%date:~8,2%拼日期。恢复的时候执行mysql -u root -p finance finance_backup_20260101.sql。mysqldump导出的是完整建库建表语句加INSERT比手动复制data目录靠谱因为后者在MySQL运行状态下拷贝容易得到损坏的快照。这个脚本放到计划任务里每天凌晨跑一次账就不会白记。6.2 导出CSVCSV导出是用户最常要的功能把明细表按日期倒序导出到文件try (BufferedWriter writer new BufferedWriter(new OutputStreamWriter( new FileOutputStream(records.csv), StandardCharsets.UTF_8))) { writer.write(\uFEFF); // UTF-8 BOM让Excel正确识别中文 writer.write(日期,类型,分类,金额,备注\n); for (Record r : list) { writer.write(String.join(,, r.getRecordDate().toString(), r.getType() 0 ? 支出 : 收入, r.getCategory(), r.getAmount().toPlainString(), r.getRemark()) \n); } }注意三件事。第一UTF-8 BOM是为了Excel用Windows默认编码打开时中文不乱码。第二金额用toPlainString()而不是toString()因为BigDecimal.toString在科学计数法场景会输出带E的字符串Excel里会显示成奇怪的数字。第三备注里如果含逗号要用双引号包起来上面这段为了简洁没处理真要上线得加个escape函数。导出菜单挂在系统菜单栏里选完路径就写文件功能虽小却是从“能跑”走向“能用”的一步。我自己的习惯是凡是涉及MySQL的桌面项目源码交付前一定把备份和导出这两个功能补上哪怕界面糙一点。数据没备份演示现场电脑死机一次几个月的记录就全没了。这段逻辑不复杂但能让你这套源码从“能跑”变成“敢用”。希望帮到你。本文还有配套的精品资源点击获取
返回列表