尧图精选

Java Swing + MyBatis + SQL 班费管理系统:课程设计实战与避坑指南

🕒 发布时间:2026/10/1 3:03:33 📁 来源:尧图网络
简介这是基于Java、MyBatis与Swing开发的一套班费管理系统完整源码包面向计算机相关专业学生、老师或企业员工适用于课程设计、毕业设计与项目初期演示。项目代码已测试通过评审分达94.5分可直接导入IDE运行也可在此基础上灵活改动实现更多功能。压缩包共88个文件大小仅156KB核心内容涵盖36个Java源码文件、20个XML配置与映射文件、14个Swing界面设计文件、12张系统运行示例图片、2个SQL数据库脚本以及Maven配置和项目说明文档结构清晰、易读易用。目前已有424人学习下载适合需要快速掌握Java桌面应用与MySQL数据库整合开发的学习者。资源提供了完整的数据库建表与示例数据脚本配合界面图片可直观了解系统运行效果从界面、数据访问层到数据库均有完整配套是理解MyBatis与Swing协同开发的实用参考尤其适合数据库课程设计和毕业设计场景。1. 班费管理系统长什么样一个 Swing 窗口背后是 Java、MyBatis、SQL 数据库的三层配合班费管理系统在大学数据库课程设计里很常见班级收班费、登记支出、查余额和明细功能边界清晰正好覆盖了单表增删改查到多表关联的全部基本功。标题里 Java MyBatis Swing SQL 数据库四个词已经把技术栈交代完整Swing 负责窗口交互MyBatis 管 Java 对象和数据库之间的读写映射SQL 数据库负责把每一笔记录落盘。这类源码包加上 SQL 数据库脚本和系统示例图片最值得看的不是界面效果而是那条完整链路——按钮事件触发后经过 Service 调用 Mapper SQL数据写库再刷新回界面。跑通一次比看十遍概念都管用适合正在做数据库大作业的学生也适合想用完整小项目串起 Swing 和 MyBatis 的 Java 入门者。2. 为什么是这个组合Swing MyBatis 在课程设计里的取舍2.1 Swing 是不是过时了对课设来说它仍是最省事的选择Swing 确实不再年轻。Java 桌面开发这些年替代品不少JavaFX 至今仍是官方在推的方向社区里也有不少用 Web 技术套壳做桌面端的方案。但数据库大作业的约束和商业项目不一样课时短、运行环境不确定、要在教室电脑上直接跑起来还要在答辩现场经得起重新编译的考验。Swing 恰恰是 JDK 自带、不需要额外下载运行时、一个 main 方法就能拉起来的方案。JavaFX 要引入模块描述和额外的构建配置换一台机器启动失败的几率远高于 Swing对以数据库设计为评分重点的课程来说属于不必要的复杂度。Swing 的另一个好处是事件模型直观。按钮点击走 ActionListener表格选择走 ListSelectionListener整个交互链路在代码里能一步一步顺着事件源找到处理逻辑。数据库大作业答辩时老师问“点这个按钮之后程序做了什么”顺着监听器—Service—Mapper 这条链路讲比对着 Web 框架的请求转发路径讲要容易接受得多。对第一次写完整项目的学生来说Swing 是理解“界面和数据库之间怎么协作”的简单载体界面朴素不影响把逻辑讲清楚。不过它也有真实的坑默认观感偏旧跨屏幕分辨率时组件布局偶尔错位代码里如果把 setBounds 写死绝对坐标换一台电脑后按钮可能被裁切。我一般会在 main 方法开头加一句 UIManager.setLookAndFeel让窗口套用当前系统外观演示观感会正常不少布局也建议用 BorderLayout 或 GridBagLayout 来控制别每个组件都写死位置。结论很简单如果你的目标是“做一个能跑、能演示、数据确实落进 SQL 数据库的桌面系统”Swing 是最稳妥的起点省下的环境成本和框架学习成本都能花到数据库设计本身。2.2 MyBatis 比 JDBC 多解决什么SQL 从代码里挪出来连接池不用自己管用 JDBC 写班费系统的 insert流程大概是Class.forName 加载驱动、DriverManager 拿连接、PreparedStatement 占位、逐个 setString 设置参数、executeUpdate、finally 里关连接。一个插入操作十几行里面跟业务真正相关的只有那条 SQL。MyBatis 的核心价值就是把 SQL 搬进 mapper XMLJava 端只剩一句 mapper.insert(record)参数由 #{} 占位符传递返回结果由 resultMap 自动映射成对象。数据源管理也是被很多课设忽略的差异点。MyBatis 内置的 POOLED 数据源会维护连接池SqlSessionFactory 启动时创建一次后续每次 openSession 复用底层连接。直接写 JDBC 的人如果对“用完必须关连接”没有意识程序跑一段时间就会把 MySQL 连接数占满报 Too many connections——这属于掉进自己挖的坑功能看着正常某次点击突然全体报错。MyBatis 还有一个对调试非常友好的配置logImpl 设为 STDOUT_LOGGING 后每一条 SQL 执行时控制台都会打印 Preparing 和 Parameters。班费系统数据简单但真遇到条件查询结果不对看打印出来的实际 SQL 和参数值立刻能判断是参数没传进去还是 WHERE 条件有问题比在界面和代码之间反复对照高效得多。关于缓存MyBatis 一级缓存默认在同一个 SqlSession 生命周期内开启同一个 session 里查两次相同 SQL 会命中缓存。桌面应用常见的写法是每次操作 openSession 一次、用完即关缓存命中机会并不多别指望它掩盖性能问题。更要留心的是 #{} 和 ${} 的区别前者生成预编译占位符后者直接拼接字符串班费系统的业务查询都应该用占位符写法。如果看到有人把排序字段或表名拼进 ${}先确认那个位置不接触用户输入再继续。对课程设计来说选 MyBatis 的价值说到底就是让代码短、让 SQL 看得见、让资源管理有兜底。这三个好处直接对应答辩时最常被问的三类问题参数怎么传的、SQL 怎么写的、连接怎么管的。2.3 数据库设计班费管理最少需要哪几张表字段怎么定班费系统的表不需要多但边界要清楚。常见做法是拆成三层班级表 class_info、学生表 student、收支记录表 fee_record。班级表一般只有 id、班级名称、备注学生表是 id、班级外键、姓名、入班日期fee_record 表是核心记录类型、金额、事由、经手人、发生时间、关联学生。三张表各管各的避免把“这个学生交没交钱”这种状态直接写进学生表里。这里有两个设计判断值得在答辩前想明白。第一个余额不设字段。班费余额完全由 fee_record 中收入和支出累计 SUM 得出而不是表里存一个 balance 列。把余额做成字段任何一笔记录的修改、删除都会产生同步负担时间一长明细和余额对不上答辩完全经不起追问。从明细实时汇总数据永远自洽这个思路在记账类系统中都能站住脚。第二个判断金额用 DECIMAL(10,2)Java 侧用 BigDecimal。浮点数累计求和的精度问题属于基础常识0.1 加 0.2 不等于 0.3放到班费这种常年多笔累计的场景迟早出事。DECIMAL 是 MySQL 的精确小数类型配合 BigDecimal 能原样映射不需要自定义处理逻辑。网上常有人问 MyBatis 中 TypeHandler 的工作流程图、什么时候要自己写一个——班费系统的字段设计就是反例类型对得上内建映射就够了只有把钱存成 VARCHAR、日期存成字符串这种自找麻烦的写法才需要 TypeHandler 去救场。时间字段也别含糊。记录创建时间用 DATETIME精确到秒只要“发生日期”就用 DATE。type 字段的约定最好在建表时用 COMMENT 写清楚1 是收入、0 是支出用 TINYINT 存储。全项目的统计 SQL 都会依赖这个约定写反了汇总表直接翻车——这类问题在代码里不容易看出来只有对着真实数据做手工核对才会暴露。3. 从零跑通班费管理系统建库、配置、启动一条线3.1 建库建表SQL 脚本的组织与导入顺序拿到源码压缩包后别急着开 IDE。先把 SQL 数据库准备好再改数据源配置最后启动程序这是最不容易出错的顺序。SQL 脚本一般会放在根目录或 sql 子目录下常见命名是 class_fee.sql内容通常包含建库、建表、初始数据三部分。以 MySQL 为例最核心的一段建表逻辑大概是CREATE DATABASE IF NOT EXISTS class_fee DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE class_fee; CREATE TABLE class_info ( id INT PRIMARY KEY AUTO_INCREMENT, class_name VARCHAR(50) NOT NULL, remark VARCHAR(200) ); CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, class_id INT NOT NULL, name VARCHAR(30) NOT NULL, join_date DATE, CONSTRAINT fk_student_class FOREIGN KEY (class_id) REFERENCES class_info(id) ); CREATE TABLE fee_record ( id INT PRIMARY KEY AUTO_INCREMENT, type TINYINT NOT NULL COMMENT 1收入 0支出, amount DECIMAL(10,2) NOT NULL, reason VARCHAR(200), operator_name VARCHAR(30), record_time DATETIME, student_id INT, CONSTRAINT fk_fee_student FOREIGN KEY (student_id) REFERENCES student(id) );这段脚本的核心不是表多而是字符集和类型。utf8mb4 是 MySQL 8 下的默认选择能存中文和 Emoji避免建表后写入中文报错金额用 DECIMAL(10,2)外键要慎用fee_record.student_id 如果没法确保每一笔支出都能对应到具体学生宁可设成可空或干脆不留外键只存经办人字符串。外键能体现表间关系但也会在插入数据时限制操作顺序课设里用一到两个即可别处处都加。导入时最稳的命令行方式是mysql -u root -p class_fee.sql用 Navicat 图形客户端直接右键运行 SQL 文件也可以但要先确认脚本里有没有 CREATE DATABASE有的话它会自己建库导入目标选择哪个不影响结果。Windows 下命令行导入 UTF-8 脚本前先执行 chcp 65001否则中文注释会乱码。导入完成后执行 SHOW TABLES应该能看到三张表再 SELECT COUNT(*) FROM student如果脚本带了示例数据会有几行初始记录这就是后面验证系统连通性的直接参照。3.2 MyBatis 全局配置数据源、mapper 注册、日志输出怎么配数据库就绪后下一步是改项目里的连接配置。压缩包解压后通常是 Maven 工程src/main/resources 下会有一个 mybatis-config.xml也可能把数据源单独拆成 db.properties。两种方式本质一样最终都被 SqlSessionFactoryBuilder 读取。一个能直接跑的最小配置如下?xml version1.0 encodingUTF-8 ? !DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN https://mybatis.org/dtd/mybatis-3-config.dtd configuration settings setting namemapUnderscoreToCamelCase valuetrue/ setting namelogImpl valueSTDOUT_LOGGING/ /settings environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/class_fee?useUnicodetrueamp;characterEncodingutf8amp;serverTimezoneAsia/Shanghai/ property nameusername valueroot/ property namepassword value123456/ /dataSource /environment /environments mappers mapper resourcemapper/StudentMapper.xml/ mapper resourcemapper/FeeRecordMapper.xml/ /mappers /configuration请注意 URL 里的 符号XML 语法要求写成 。很多同学直接粘贴带 的连接串启动时报 URL 字符不合法就是这里少了转义。serverTimezoneAsia/Shanghai 是 MySQL 8 时区问题的标准解法缺失时常见报错是一长串不认得的时区描述。driver 用 com.mysql.cj.jdbc.Driver 对应 MySQL 8这是新版驱动的类名如果你用的是 MySQL 5.x可能还是 com.mysql.jdbc.Driver两者不能混用。logImpl 设为 STDOUT_LOGGING 的作用前面提过让 MyBatis 把所有 SQL 直接打到标准输出。打开日志跑一次收支新增控制台会依次出现 Preparing、Parameters、Updates对照着传入的 Java 对象看参数值是否正确比在界面上瞎猜高效得多。如果只想在排查时开日志也可以改用 Log4j 并按需调级别但对课设来说 STDOUT_LOGGING 已经够用。mappers 节点要和 resources/mapper 目录下的 XML 一一对应。如果项目是 Maven 结构但你把 XML 放在 src/main/java 目录下打包时还需在 pom.xml 里加 resource 声明否则运行时找不到 XML。这个坑在 IDE 里跑不容易察觉打包成 jar 或换机器后立刻暴露。3.3 程序入口到登录界面Main 方法到底做了什么Swing 程序入口和 Web 项目不同没有容器、没有 Servlet只有一个 public static void main。班费系统里 main 要做的事一般就两步加载 MyBatis 配置得到 SqlSessionFactory然后创建登录窗口并显示。public class Main { public static void main(String[] args) { SqlSessionFactory factory; try { factory new SqlSessionFactoryBuilder() .build(Resources.getResourceAsStream(mybatis-config.xml)); } catch (IOException e) { JOptionPane.showMessageDialog(null, 读取 mybatis-config.xml 失败请检查配置文件); return; } LoginFrame login new LoginFrame(factory); login.setVisible(true); } }注意这里把 SqlSessionFactory 传进了 LoginFrame而不是在窗口里重新读配置。SqlSessionFactory 是重量级对象整个应用生命周期只创建一次所有窗口和 Service 都复用它来 openSession。有些初学者把 factory 写成每次 new 一个程序能跑但连接资源开一批关一批日志里全是连接创建和销毁记录答辩时也容易被问到资源管理。Resources.getResourceAsStream 是 MyBatis 自带的工具方法从 classpath 找配置文件省去手写路径的麻烦。配置读取失败时用 JOptionPane 弹错误框而不是直接打印异常符合桌面程序的交互习惯也避免窗口没弹出来还以为是程序卡死。再往下一层LoginFrame 构造器里一般会加载用户信息或校验登录。但很多班费系统实现里并没有真正的用户表登录框只是判断输入非空或者写死 admin/123456。演示前先查清楚这一点免得答辩现场输错密码被卡住。系统示例图片通常也会包含登录界面和主界面的截图先看图片再对照代码能快速定位登录逻辑在哪里。3.4 登记一笔班费到看到余额变动最小业务闭环跑通程序后先用示例图片里展示的流程走一遍核心操作点收入登记输入金额和事由保存然后在明细列表看到这条记录余额数字跟着变化。这个过程涉及三条链路UI 事件、Service 层调用、MyBatis 写库。新增收入记录的 Service 写法常见如下public class FeeRecordService { private final SqlSessionFactory factory; public FeeRecordService(SqlSessionFactory factory) { this.factory factory; } public void addIncome(BigDecimal amount, String reason, String operator) { FeeRecord record new FeeRecord(); record.setType(1); record.setAmount(amount); record.setReason(reason); record.setOperatorName(operator); record.setRecordTime(new Date()); try (SqlSession session factory.openSession()) { FeeRecordMapper mapper session.getMapper(FeeRecordMapper.class); mapper.insert(record); session.commit(); } } }这里最值得讲的是提交时机。MyBatis 的 openSession() 默认 autoCommit 为 false任何写操作必须显式 commit 才会真正落库。如果你看到代码里有的新增方法没调 commit 数据却写进去了多半是 openSession(true) 开了自动提交。对单条插入来说自动提交影响不大但一旦涉及多表多步操作每步单独提交会让回滚变得困难事务也就失去了意义。界面层拿到结果后一般会重新执行一次查询来刷新表格。这里的关键是不要在新增成功后只改内存里的模型而不重新查库。余额是从明细 SUM 出来的只有重新跑统计查询余额才对得上。数据库大作业的答辩老师十有八九会问“余额怎么算的”能答出“每次从明细实时汇总”比答“有一个余额字段”更符合数据一致性原则。4. 读懂源码的四个层次实体类、Mapper、Service、Swing UI 各管什么4.1 实体类和 Mapper 接口一张表对应一份代码别省班费系统的源码结构虽然各有不同但分层思路基本一致实体类放在 entity 或 model 包Mapper 接口放在 mapper 包XML 放在 resources/mapper 目录Service 封装业务UI 类在 view 包。student 表对应的实体类大概是这个风格public class Student { private Integer id; private Integer classId; private String name; private Date joinDate; public Student() { } public Student(String name, Integer classId) { this.name name; this.classId classId; } // getter / setter 省略建议使用 IDE 自动生成 }实体类有一个约定值得注意数据库列是 int 类型时Java 字段建议用 Integer 而不是基本类型 int。原因是 SQL 查询结果可能为 NULL基本类型在 MyBatis 赋值时会直接抛异常而引用类型能正常接住 NULL。日期字段统一用 java.util.Date不要在这个项目里混用 LocalDate 和 Date否则 Mapper XML 里不写 typeHandler 时容易出现映射错乱。对应的 Mapper 接口一般长这样public interface StudentMapper { ListStudent selectAll(); Student selectById(Integer id); ListStudent selectByClassId(Integer classId); int insert(Student student); int update(Student student); int deleteById(Integer id); }Mapper 接口不写实现类这是 MyBatis 和传统 DAO 的最大区别。运行期 MyBatis 通过 JDK 动态代理生成实现方法名、参数列表必须和 XML 里定义的 id、parameterType 对应。理解这一点能帮你避开最常见的运行时报错接口方法查不到对应 SQL启动时不报错直到第一次调用才抛 BindingException。4.2 Mapper XML 和 resultMapSQL 不要用 select * 收尾写完接口就写 XML。下面是一份常见的 student 表查询映射mapper namespacecom.example.mapper.StudentMapper resultMap idstudentMap typecom.example.entity.Student id propertyid columnid/ result propertyclassId columnclass_id/ result propertyjoinDate columnjoin_date/ /resultMap select idselectByClassId resultMapstudentMap SELECT id, class_id, name, join_date FROM student WHERE class_id #{classId} /select /mapperXML 的 namespace 必须等于接口全限定名select 的 id 必须等于接口方法名。resultMap 里把下划线列名映射成 Java 驼峰属性class_id 到 classId、join_date 到 joinDate。全局配置里开了 mapUnderscoreToCamelCasetrue 之后很多简单查询可以不写 resultMap但课设里显式写有一个好处答辩时能直接讲清楚“数据库字段和 Java 属性命名规范不同靠 resultMap 做映射”这是加分项。为什么强调不要用 select *班费系统表少select * 在初跑时确实省事但日后给表加一个字段实体类没跟上多出的列可能被忽略排查起来很别扭。把列名写全既是向老师展示你对 SQL 有控制力也是给自己留清晰的调试路径。4.3 Service 层事务边界该划在哪有些课设代码把数据库操作直接写在按钮事件里界面类和 SQL 揉成一团。这样跑通容易但有两个问题相同查询逻辑在多个窗口重复出现改动时容易漏一个事件方法里连续写两张表中间任何一步失败数据只剩一半。以收班费为例一次操作可能涉及两件事给 fee_record 插一条记录再更新学生缴费状态。两步要么都成功要么都不成功Service 层正是干这个的public void recordExpense(Integer studentId, BigDecimal amount, String reason, String operator) { try (SqlSession session factory.openSession()) { StudentMapper studentMapper session.getMapper(StudentMapper.class); FeeRecordMapper feeMapper session.getMapper(FeeRecordMapper.class); feeMapper.insert(record); // 1. 写支出明细 studentMapper.updatePaid(studentId); // 2. 更新学生状态 session.commit(); // 3. 都成功才提交 } }这里的关键是同一个 SqlSession 里拿到的两个 Mapper 共享同一个数据库连接也就共享同一个事务。第二步万一抛异常由于没有执行 commit连接关闭时事务自动回滚第一步的明细不会残留。这就是为什么事务要放在 Service 层而不是放在两个各自 openSession 的 Mapper 调用里。另一个容易被忽略的细节不要在循环里对每条数据都 openSession 并 commit。班费系统如果支持批量导入批量插入应该在同一个 session 内分批提交而不是一千条数据开一千个连接。课设数据量小性能差异不明显但这是资源管理习惯面试聊项目时也常被追问。4.4 Swing 界面和 JTable 刷新能用但要知道事件线程边界界面部分的核心是 JFrame 和 JTable。班费明细用 JTable 展示配套模型是 DefaultTableModel。每次数据变更后刷新表格最简单的写法是private void refreshTable() { try (SqlSession session factory.openSession()) { ListFeeRecord records session.getMapper(FeeRecordMapper.class).selectAll(); DefaultTableModel model (DefaultTableModel) table.getModel(); model.setRowCount(0); for (FeeRecord r : records) { model.addRow(new Object[]{ r.getId(), r.getType() 1 ? 收入 : 支出, r.getAmount(), r.getReason(), r.getRecordTime() }); } } catch (Exception ex) { JOptionPane.showMessageDialog(this, 刷新失败 ex.getMessage()); } }这段代码的直接问题是数据库查询发生在事件分发线程上数据量大或数据库慢时界面会卡住。班费系统数据量小课设演示场景下直接写没问题但如果在演示时一边连库一边拖动窗口建议用 SwingWorker 把查询放到后台线程避免演示当场出现窗口无响应的尴尬。SwingWorker 是 JDK 自带方案几十行能改完。另外JTable 默认是可编辑的双击单元格就能改而班费记录应该只读。初始化表格时重写 isCellEditable 返回 false或者直接 setEnabled(false)能避免演示时误触修改表格内容。这个细节虽小却是很多版本里常见的疏漏运行界面看起来一样的鼠标点上去行为完全不同。5. 班费管理系统避坑指南连不上、映射不到、乱码、卡死的五个排查点5.1 Invalid bound statement (not found)接口和 XML 对不上调用时才报错现象程序能启动窗口能打开点某个按钮触发查询时控制台抛出 BindingException: Invalid bound statement (not found)。原因最常见的有三种。Mapper XML 的 namespace 没写成接口全限定名写成了 FeeRecordMapper 或漏了包名XML 里 select 的 id 和接口方法名不一致mybatis-config.xml 的 mappers 节点没注册这个 XMLMyBatis 只知道接口存在不知道 SQL 在哪。解决按顺序查三层。先看 XML 第一行 namespace 是否和接口全限定名一致再看 select/insert/update/delete 的 id 是否和接口方法一一对应最后确认 mybatis-config.xml 的 mappers 节点里有对应 resource。如果 XML 放在 src/main/java 下而项目采用 Maven 打包还要在 pom.xml 里补 resource 声明否则 IDE 内正常运行、打成 jar 后报找不到 XML这种隐藏差异最容易在答辩换机器时翻车。5.2 数据库连接被拒服务没起、密码写错、时区没配现象程序启动后第一次查询时弹窗提示 Communications link failure或者 Access denied for user rootlocalhost。原因Communications link failure 多数是连不上端口。MySQL 服务没启动、端口被占用、连接 URL 写了错误主机名或端口三类最常见Access denied 是用户名或密码与数据库实际账号不一致报错里出现 time zone value is unrecognized 是 MySQL 8 的时区问题。解决先用命令行 mysql -u root -p 连接确认账号密码和服务状态连接成功后执行 SHOW VARIABLES LIKE port确认端口是默认 3306再把 mybatis-config.xml 的 URL 补上 serverTimezoneAsia/Shanghai。如果本机命令行能连、程序连不上检查 Windows 防火墙是否拦截 3306本地开发一般不出现但用虚拟机或远程数据库做演示时容易被忽略。5.3 中文乱码连接参数、数据库字符集、界面字体三层分开查现象界面上输入中文保存重新查询后变成问号或者 SQL 脚本里的中文注释、示例数据导入后显示乱码。原因乱码根因往往来自三层之一。连接 URL 缺 characterEncodingutf8MyBatis 向 MySQL 发送的字符集不对库或表默认字符集是 latin1中文存储后无法还原Swing 界面字体不支持某些字形显示层直接渲染成方块或问号。解决先给 URL 加 useUnicodetrue 和 characterEncodingutf8再确认建库建表时显式指定 DEFAULT CHARACTER SET utf8mb4而不是依赖服务器默认最后排查界面。Windows 下 Swing 默认字体显示中文通常没问题Linux 下缺中文字体才会出现方块字安装 fonts-noto-cjk 即可解决。判断层级的技巧是先用命令行插入一条中文记录再查一次命令行正常而界面乱是显示层问题命令行就乱是存储或连接问题。5.4 窗口卡死数据库查询占住了事件分发线程现象点击“查询明细”后窗口无法拖动、按钮无响应过几秒突然恢复看起来像卡死。原因Swing 的界面刷新都在事件分发线程上执行数据库查询如果直接写在按钮事件里查询期间这个线程被占住所有界面事件都在排队。班费系统数据量小复现概率不高一旦遇到数据库卡顿或查询条件没带、全表扫描慢查询症状立刻出现。解决数据量小的时候可以接受直接查但按钮事件必须加 try-catch至少别让异常打断事件线程更稳的做法是把查询放进 SwingWorker 的 doInBackground查询完成后在 done 中更新 JTable。课程设计不需要引入复杂并发库SwingWorker 是 JDK 自带方案二十行以内能改完演示时还能提一句“用了后台线程避免界面阻塞”属于典型的加分项。5.5 金额和日期显示不对从数据库类型到实体类再到格式化现象金额显示成 3.0 而不是 3.00或者日期显示成 Fri Jun 20 10:00:00 CST 2025 这种英文格式。原因金额多半是实体类里用了 DoubleMyBatis 映射后丢失 DECIMAL(10,2) 预设的精度和两位小数展示日期则是直接把 Date 的 toString 输出到 JTable没有做格式化。解决金额字段在实体类里改用 BigDecimalJTable 展示时用 DecimalFormat 统一两位小数。日期先格式化再放进表格SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss); model.addRow(new Object[]{..., sdf.format(r.getRecordTime())});不要试图通过自定义 TypeHandler 把 Date 转成字符串存库那是把显示问题下沉到存储层反而制造新麻烦。显示格式化放 UI 层数据库始终存标准 DATETIME。网上常有人问 MyBatis 中 TypeHandler 的工作流程图班费系统里你只需要知道内建映射够用即可——自定义 TypeHandler 是“字段类型和 Java 类型确实对不齐”时才需要的工具不是所有字段的必经之路。6. 再往前走一步给班费系统加月度收支统计整套系统跑通之后最常见的扩展需求是“这个月收了多少、花了多少”。班费数据都沉淀在 fee_record 里按月份分组汇总是一条 SQL 的事但接进 Swing 界面需要同时改三个地方增一个 DTO 或直接用 Map 接结果、Mapper XML 加统计查询、UI 加一个只读表格展示。统计 SQL 这么写SELECT DATE_FORMAT(record_time, %Y-%m) AS month, SUM(CASE WHEN type 1 THEN amount ELSE 0 END) AS income, SUM(CASE WHEN type 0 THEN amount ELSE 0 END) AS expense, SUM(CASE WHEN type 1 THEN amount ELSE -amount END) AS balance FROM fee_record GROUP BY DATE_FORMAT(record_time, %Y-%m) ORDER BY month DESC;三个细节可以讲给答辩老师听收入和支出用条件聚合拆开避免在 Java 里二次计算余额从累计推导而不是依赖存量字段ORDER BY month DESC 让最新月份排最前符合界面展示习惯。把这个查询放进 Mapper 后Swing 里的展示逻辑和前面 JTable 刷新完全一致setRowCount(0) 清空旧数据再 addRow。验证这套统计是否准确最可靠的办法是拿原始明细手工核对挑两个月把明细逐条加一遍收入、支出、结余再对照统计表格。明细对得上汇总才说明条件聚合和数据口径没有问题对不上优先检查 type 字段的值是否符合建表时的约定以及有没有空金额、空时间的脏数据混进去。整个班费系统最容易翻车的从来不是功能复杂而是数据口径不统一。我自己的习惯是每次改完表结构或新增统计功能先把示例数据重建一遍再跑一遍全流程最后用系统示例图片同款的角度截一张图留档。视觉结果对得上代码逻辑差不到哪去。这条经验在课程设计和小型桌面项目里都适用希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →