简介这套班费管理系统基于Java语言、MyBatis持久层框架与Swing界面工具包开发是面向计算机相关专业学生和教师的数据库课程设计项目能够解决班级日常收支记录、费用统计与账目管理等实际问题适用于课程作业、毕业设计或项目初期参考。压缩包内含文件总数88个整体体积约156KB其中Java源代码36个用于实现业务逻辑XML配置文件20个用于MyBatis映射与项目设置JFD界面设计文件14个用于Swing窗口布局SQL数据库脚本2个用于建表与初始化数据PNG示例图片12个展示系统运行效果另附Markdown说明文档帮助阅读。目前已有424人学习下载代码经作者测试运行成功评审分达94.5分具备较高的参考价值与良好的稳定性。资源提供完整班费管理功能源码、数据库建表与初始化数据脚本以及系统运行截图可帮助读者快速理解持久层映射、界面交互与基础增删改查实现适合上手实践或在此基础上二次开发。1. 班费管理系统名字听着简单为什么很多人卡在“能编译却跑不起来”看到“班费管理系统”这个标题大家第一反应多半是“又是个最简单的学生项目”。但做数据库大作业的人心里清楚真正卡进度的从来不是业务逻辑而是 Java、MyBatis、Swing 这三个东西怎么粘在一起。SQL 建好了代码也编译通过了一运行报 Invalid bound statement程序启动正常表格里却永远查不到刚插入的记录。这个标题给出的项目本质上是一个典型的桌面型数据库系统Swing 承担界面展示MyBatis 管理数据访问SQL 脚本负责建库建表和初始化数据。数据库大作业的评分点通常就落在表设计规不规范、SQL 能不能自圆其说、界面操作顺不顺手这三件事上。这篇笔记按我实际做完这类系统的顺序把技术选型、数据库脚本、MyBatis 配置到 Swing 表格刷新这些直接影响分数的细节过一遍适合拿来做课程设计改造也适合想理清“界面—持久层—数据库”三层怎么协作的初学者。2. 技术选型与项目骨架为什么是 Swing MyBatis目录怎么搭2.1 为什么不是 Spring Boot Vue而是 Swing 桌面端数据库大作业的评分重点在 SQL 和表结构GUI 更多是展示手段。常见做法是用 Swing 做桌面客户端免部署、不依赖浏览器、演示时打开就能跑老师看的是界面和数据库交互的完整链路而不是前端框架的花样。Swing 虽然是老技术但正因为它老参考资料多、代码结构直观课堂上讲的监听器和组件模型刚好拿来答辩。持久层这块MyBatis 比纯 JDBC 省掉大量样板代码。纯 JDBC 的 PreparedStatement、ResultSet 遍历、资源关闭写起来又臭又长而 MyBatis 把 SQL 独立到 XML 里答辩时老师问“这条查询是怎么写的”打开 Mapper 文件逐行讲就行。对比一下三种方案在答辩现场的表现方案答辩现场表现隐藏成本纯 JDBC老师问“这行代码干嘛的”你要解释一套 JDBC 规范代码量大连接管理容易翻车MyBatis打开 XML 说 SQL清晰直白需要理解 namespace、映射规则Spring Data JPA老师问“SQL 在哪”你说“框架自动生成”反而不容易讲清楚2.2 包结构和依赖清单按最小可运行来配置我一般会按三层加工具类的思路建包看起来也比较像企业项目的缩小版banfei-system/ ├── src/ │ ├── main/ │ │ ├── java/com/example/banfei/ │ │ │ ├── domain/ # 实体类Student、Bill、ClassInfo │ │ │ ├── dao/ # MyBatis 的 Mapper 接口 │ │ │ ├── service/ # 业务逻辑层界面只调这一层 │ │ │ ├── ui/ # Swing 界面登录窗体、主窗体 │ │ │ ├── utils/ # MyBatisUtil、DBUtil 等工具类 │ │ │ └── App.java # 程序入口 │ │ └── resources/ │ │ ├── mapper/ # BillMapper.xml、StudentMapper.xml │ │ ├── mybatis-config.xml │ │ └── db.properties │ ├── sql/ # 建库建表脚本 初始化数据 │ └── docs/ # 系统示例图片、说明文档 └── lib/ # 非 Maven 工程手动放 jar 包依赖上核心只有三样mybatis 的 jar、对应数据库的 JDBC 驱动MySQL 用 mysql-connector-javaSQL Server 用 sqljdbc、以及日志包。如果用 Mavenpom 里加上 mybatis 和 mysql 驱动两个依赖即可版本以你本机能拉到的为准。不需要引入 SpringSwing 项目里一旦带上 Spring 容器启动流程和类扫描对学生来说反而成了新负担。手动导包也完全可行只是要把用到的 jar 统一放进 lib 目录并在 README 里写清楚运行时要引入哪些包、JDK 版本是多少。提示java 环境变量没配好双击启动脚本会直接报“找不到主类”或“java 不是内部命令”这类问题九成出在 PATH 里没加 JDK 的 bin 目录演示前务必先跑一遍java -version。2.3 正确的打开顺序先看示例图片再写代码拿到这套东西我建议不要先翻代码而是先看系统示例图片。图片里通常展示了登录界面、主窗体布局、收支录入和明细表格四个核心界面。先照图片还原界面布局再写数据库脚本然后是 Mapper 和 Service。顺序反了的话你会在写完代码后不断回头调 UI 组件改一处布局崩一片事件。Swing 布局我一般用 BorderLayout 做外层中间放 JScrollPane 包 JTable南北两侧放操作按钮和输入面板。窗口标题和按钮文案这些固定字符串放进常量类后面做课程设计改造时只需要改一处。IDEA 里可以给 resources 装上 mybatis 插件比如 MyBatisX让 XML 里的 SQL 有高亮和跳转功能排查映射错误会省很多时间。3. 数据库设计班费账目该建几张表字段怎么定才不怕老师追问3.1 三张核心表之间的关系班费管理系统最常见的设计是三张表班级表、学生表、收支记录表。班级表存班级名称和班长信息学生表挂到班级下每笔收支记录关联一个学生。为什么不要四张表再加一个“支出项目表”因为大作业的边界是“能用、能讲、能改”把收支类别用字段约束或应用层枚举管起来比多设计一张字典表在答辩时更简洁老师追问“如果类别要扩展怎么办”你再补充说明可以拆表反而能体现你的思考。金额字段必须用 DECIMAL(10,2)不要用 float 或 double。浮点数在计算累加时会出 0.10.20.30000000000000004 这种问题班费统计一旦对不上账演示当场尴尬。类型字段用 TINYINT1 代表收入、2 代表支出配合 COMMENT 注释写清楚含义。学生表和班级表之间、收支表和学生表之间都要加外键约束数据库大作业不建外键老师会觉得你没理解关系型数据库的核心概念。3.2 建表 SQL 与初始化数据脚本CREATE DATABASE IF NOT EXISTS banfei DEFAULT CHARACTER SET utf8mb4; USE banfei; DROP TABLE IF EXISTS bill; DROP TABLE IF EXISTS student; DROP TABLE IF EXISTS class_info; CREATE TABLE class_info ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 班级ID, class_name VARCHAR(50) NOT NULL UNIQUE COMMENT 班级名称, monitor_name VARCHAR(20) DEFAULT NULL COMMENT 班长姓名, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB COMMENT班级表; CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 学生ID, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, student_name VARCHAR(20) NOT NULL COMMENT 姓名, class_id INT NOT NULL COMMENT 所属班级ID, phone VARCHAR(11) DEFAULT NULL COMMENT 联系电话, CONSTRAINT fk_student_class FOREIGN KEY (class_id) REFERENCES class_info(id) ) ENGINEInnoDB COMMENT学生表; CREATE TABLE bill ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 记录ID, student_id INT NOT NULL COMMENT 学生ID, amount DECIMAL(10,2) NOT NULL COMMENT 金额, type TINYINT NOT NULL COMMENT 1收入 2支出, category VARCHAR(20) NOT NULL COMMENT 用途/类别, remark VARCHAR(200) DEFAULT NULL COMMENT 备注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 发生时间, CONSTRAINT fk_bill_student FOREIGN KEY (student_id) REFERENCES student(id) ) ENGINEInnoDB COMMENT收支记录表;脚本逻辑先删子表再删父表避免外键约束导致 DROP 失败。每个字段都加了 COMMENT一方面是为了 Jan 建表后自己看字段含义不迷糊另一方面课程设计文档里可以直接截图。ENGINE 指定 InnoDB是为了让事务和外键生效这一点在答辩时值得主动说出来。初始化数据按“先班级再学生后收支”的顺序插入并且要保证收支金额分布有收入有支出演示时才看得到列表效果和统计效果INSERT INTO class_info (class_name, monitor_name) VALUES (计算机2101, 张三); INSERT INTO student (student_no, student_name, class_id, phone) VALUES (2021001, 张三, 1, 13800138000), (2021002, 李四, 1, 13800138001); INSERT INTO bill (student_id, amount, type, category, remark) VALUES (1, 500.00, 1, 班费, 学期初收取), (2, 3.50, 2, 打印资料, 期中考试资料);这段数据和示例图片里的展示尽量保持一致演示时老师看到的界面效果和数据库内容对得上可信度会高很多。另外交作业避免用 SQL Server 的 bak 备份文件高版本备份到低版本恢复不了比如 SQL Server 2012 的备份往 2008 上恢复就会报版本不支持还是交 .sql 脚本最稳。3.3 mybatis-config.xml 与 Mapper 映射文件持久层配置是整套系统最容易出错的部分。核心是 mybatis-config.xml里面开驼峰映射和 SQL 日志输出?xml version1.0 encodingUTF-8 ? !DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN https://mybatis.org/dtd/mybatis-3-config.dtd configuration properties resourcedb.properties/ settings setting namemapUnderscoreToCamelCase valuetrue/ setting namelogImpl valueSTDOUT_LOGGING/ /settings typeAliases package namecom.example.banfei.domain/ /typeAliases environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver value${driver}/ property nameurl value${url}/ property nameusername value${username}/ property namepassword value${password}/ /dataSource /environment /environments mappers package namecom.example.banfei.dao/ /mappers /configurationmapUnderscoreToCamelCase 打开后数据库里的 student_name 自动映射到实体的 studentName 属性省去在 resultMap 里逐列写映射的麻烦。logImpl 配 STDOUT_LOGGING 后MyBatis 会在控制台打印执行 SQL这个配置在调试阶段是后悔药级别的存在——插入没生效、条件写错看一眼控制台就明白了。typeAliases 配包名后XML 里写 resultTypecom.example.banfei.domain.Bill 可以简写为 resultTypeBill。Mapper 接口和 XML 同包同名是规范。下面是一个收支记录 Mapper 的核心写法mapper namespacecom.example.banfei.dao.BillMapper select idselectList resultTypeBill SELECT b.id, b.amount, b.type, b.category, b.remark, b.create_time, s.student_name FROM bill b JOIN student s ON b.student_id s.id ORDER BY b.create_time DESC /select insert idinsert parameterTypeBill useGeneratedKeystrue keyPropertyid INSERT INTO bill (student_id, amount, type, category, remark) VALUES (#{studentId}, #{amount}, #{type}, #{category}, #{remark}) /insert update idupdateById parameterTypeBill UPDATE bill SET amount #{amount}, type #{type}, category #{category}, remark #{remark} WHERE id #{id} /update delete iddeleteById parameterTypeint DELETE FROM bill WHERE id #{id} /delete /mapper参数全部用 #{} 占位不要用 ${} 拼接字符串。#{} 走的是 PreparedStatement 预编译能防 SQL 注入${} 是字符串替换只在动态表名、排序列名等极少数场景才用并且必须做白名单校验。单机桌面程序的 MyBatis 本地缓存对业务帮助不大我不建议在 Mapper 上开二级缓存否则改完数据界面读到的还是旧值排查起来反而浪费时间。4. MyBatis 整合高频翻车点现象、原因、处理4.1 现象启动报 Invalid bound statement (not found)运行程序后堆栈抛 org.apache.ibatis.binding.BindingException提示某个 Mapper 方法找不到对应的 SQL。第一次遇到这个问题的人很容易怀疑是代码写错实际上绝大多数是 XML 文件没被编译到 classpath。Maven 工程默认只把 resources 目录下的文件打包进 target如果你把 Mapper XML 放在 java 源码目录下编译后 classpath 里根本没有它。提示Mapper 接口的包名、XML 的 namespace、XML 文件名三者必须完全一致少一个都对不上。检查顺序看 target/classes 下有没有对应 XML再看 namespace 是否写了全限定名。解决方法是两选一要么把 XML 放到 src/main/resources/mapper 目录并在 mybatis-config.xml 里用package namecom.example.banfei.dao/扫描要么在 pom 里显式声明把 xml 打包进 classes。推荐前者目录结构清爽也不依赖构建配置。4.2 现象insert 返回了 1重启后数据消失新增记录时 Mapper.insert 明明返回 1界面也提示成功但重启应用后记录不见了。原因是 MyBatis 默认事务不自动提交sqlSession 关闭时如果没有 commit事务直接回滚。很多初学者用 MyBatis 时忽略了事务边界看着代码没错实际上数据从来没真正写进数据库。解决是在打开 SqlSession 时传入自动提交参数或者手动调用 committry (SqlSession session MybatisUtil.openSession(true)) { BillMapper mapper session.getMapper(BillMapper.class); mapper.insert(bill); refreshTable(); }openSession(true) 表示自动提交适用于增删改后不需要回滚的桌面程序场景。第 2 个参数传 false 或省略就必须在方法结束前 session.commit()否则数据只停留在内存事务里。调试时建议先用自动提交保证功能跑通再加事务控制。4.3 现象界面中文显示正常存进数据库的却是 ? 或乱码中文乱码的坑一般出现在三个位置数据库字符集、JDBC 连接串、控制台编码。建库时已经指定 utf8mb4那问题多半出在连接串上。MySQL 8 的驱动对连接串要求严格不带 serverTimezone 会直接报时区错误不带 characterEncoding 则中文写入变问号。我统一用这条连接串drivercom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/banfei?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai usernameroot passwordyour_password排查顺序先看连接串再看数据库表的 COLLATION 是否为 utf8mb4 系最后看 IDEA 控制台编码。示例图片里的中文界面如果显示乱码操作系统层面还要保证项目文件本身是 UTF-8 编码Windows 默认记事本存的中文可能是 GBK写代码统一用编辑器设置 UTF-8 保存。4.4 现象硬拼 SQL 做分页越写越乱给明细列表加分页时用字符串拼接 LIMIT 参数LIMIT pageNum , pageSize。页面一翻查出来的数据对不上号。问题不在 SQL而是参数在拼接过程中没有正确绑定且 MyBatis 的 #{} 在这种场景没法用。本质上是对“mybatis 的分页插件的用法”不熟。按我的经验班费管理这个量级的数据通常只有几十条根本不需要分页用 JScrollPane 包住表格就够用。如果老师明确要求加分页再引入 PageHelper 5.x使用方式很简单PageHelper.startPage(pageNum, pageSize); ListBill list billMapper.selectList(); PageInfoBill pageInfo new PageInfo(list);startPage 调用后紧跟第一条查询语句分页只对这一次查询生效。同一线程里第二次查询前不能重复调用 startPage否则会覆盖前一页参数。PageHelper 的版本要和 MyBatis 版本兼容3.x 和 4.x 的用法还有差异大作业场景我建议别在这个点上冒险能用表格展示就不要给分页加戏。4.5 现象程序跑几分钟后按钮点击无响应控制台报连接失败连续操作几次后点击查询或保存按钮界面假死日志里出现 Connection refused 或 Too many connections。原因是每个按钮事件里都 new 了一个 SqlSession用完没有 close。桌面程序不像 Web 应用有容器管生命周期忘了关连接连接池很快被耗尽。治本的方法是封装一个 MybatisUtil且所有操作都用 try-with-resourcespublic class MybatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try (InputStream is Resources.getResourceAsStream(mybatis-config.xml)) { sqlSessionFactory new SqlSessionFactoryBuilder().build(is); } catch (IOException e) { throw new ExceptionInInitializerError(e); } } public static SqlSession openSession(boolean autoCommit) { return sqlSessionFactory.openSession(autoCommit); } }SqlSessionFactory 是整个应用生命周期内只创建一次的重量级对象放静态代码块。每次数据库操作单独开一个 SqlSession 并立刻关闭这是和 JDBC 时代 Connection 一样的使用习惯。写完代码后自查一遍每个openSession是不是都在同方法的 try 里闭合。5. Swing 界面落地的三个关键点表格刷新、事件拆分和线程模型5.1 表格刷新不更新DefaultTableModel 的正确用法Swing 里最让人困惑的是“数据查到了但界面不更新”。常见错误是每次刷新都重新new JTable()再 setModel 到原来的位置结果旧的表格引用被丢弃界面行为变得混乱。正确做法是一开始就建好 DefaultTableModel后续只动 Model 的数据String[] columns {编号, 姓名, 类型, 金额, 用途, 时间}; DefaultTableModel model new DefaultTableModel(columns, 0); JTable table new JTable(model); void refreshTable(ListBill list) { model.setRowCount(0); for (Bill b : list) { Object[] row { b.getId(), b.getStudentName(), b.getType() 1 ? 收入 : 支出, b.getAmount(), b.getCategory(), b.getCreateTime() }; model.addRow(row); } }setRowCount(0) 是清空表格数据的标准写法它会触发表模型的数据变更事件JTable 自动重绘。逐行 removeRow 也能清空但每删一行界面就刷新一次数据多的时候会闪烁。这里的关键是始终持有同一个 model 引用事件监听器里调 refreshTable 即可。类型字段在界面上显示成“收入/支出”而不是 1/2这种展示层转换放在 UI 代码里做不要污染实体类。5.2 按钮事件里只做三件事取参、调 Service、刷界面新增收支记录按钮的事件写法决定了代码后续改起来累不累。我见过不少写法是把 JOptionPane 弹窗、拼 SQL、调 Mapper 全塞进一个 ActionListener窗体类动辄上千行。这里我会把按钮逻辑控制到最小范围btnAdd.addActionListener(e - { String amountText amountField.getText().trim(); if (amountText.isEmpty()) { JOptionPane.showMessageDialog(frame, 金额不能为空); return; } Bill bill new Bill(); bill.setStudentId((Integer) studentCombo.getSelectedItem()); bill.setAmount(new BigDecimal(amountText)); bill.setType(typeCombo.getSelectedIndex() 0 ? 1 : 2); bill.setCategory(categoryField.getText().trim()); bill.setRemark(remarkField.getText().trim()); billService.add(bill); refreshTable(); });校验放在监听器最前面通过后组装实体调用 Service最后刷新表格。注意监听器里不能出现任何 JDBC 或 Mapper 相关代码那属于持久层职责耦合在一起后老师追问分层逻辑时就讲不清楚了。BigDecimal 构造入参建议用字符串直接传 double 会在金额精度上踩坑。5.3 Swing 单线程模型为什么耗时操作会让界面“假死”Swing 的组件访问只能在 Event Dispatch ThreadEDT事件分发线程上执行所有按钮点击、窗口拖动都排队在这个线程里处理。如果你在按钮事件里直接做数据库查询整个 EDT 被阻塞界面就呈现出“没死但也不动”的假死状态。数据库操作属于 IO 耗时哪怕本地查询只有几十毫秒连点几次按钮后卡顿也会非常明显。加分做法是用 SwingWorker 把查询挪到后台线程完成后切回 EDT 刷新界面new SwingWorkerListBill, Void() { Override protected ListBill doInBackground() { return billService.listAll(); } Override protected void done() { try { refreshTable(get()); } catch (Exception ex) { JOptionPane.showMessageDialog(frame, 查询失败 ex.getMessage()); } } }.execute();doInBackground 在后台线程执行done 在 EDT 执行所以刷新表格的代码必须放在 done 里。get() 会取回后台任务的返回值并处理异常。这个模式不复杂但写出来后和纯同步查询的代码对比明显答辩时主动讲出“避免阻塞 EDT”这个点比展示十个 JTable 的功能更让老师印象深刻。6. 演示与交付让系统在老师面前站得住脚课程设计演示最怕的是临时出状况我用一套固定脚本规避这个问题。上台前清空数据库里的演示脏数据重新执行一遍初始化 SQL保证界面和数据库状态一致。演示顺序固定为三段式先启动系统进入列表页展示整体数据再新增一笔收入、一笔支出演示表格刷新和金额计算最后演示修改和删除并打开数据库客户端给老师看记录同步变化。这三个来回走完增删改查全覆盖时间也控制在三分钟内。系统示例图片的整理值得单独说一下。我习惯按界面功能命名01_login.png、02_main.png、03_bill_add.png、04_statistics.png放进 docs 目录并在课程设计文档里按“操作步骤 预期结果 截图”的格式排版。截图前把示例数据统一成“计算机2101班”姓名、金额不要出现测试垃圾数据老师看到的第一印象会好很多。另外给 credits 一个习惯主窗体底部状态栏显示当前登录用户和当前时间这个小细节做起来不难但数据库课设里很少有人做反而容易成为记忆点。我第一次交这类课设也翻过车代码在本机能跑换了演示机的 JDK 版本直接启动失败原因是用了高版本 JDK 编译再丢到低版本环境跑。后来我把编译目标设置为 1.8并在 README 里写好运行环境要求这类问题再没出现过。做班费管理系统这种大作业稳比炫重要数据库设计规范、SQL 自圆其说、界面操作顺畅这三点占住了分数就丢不了。希望帮到你。本文还有配套的精品资源点击获取
