拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Java+MyBatis+Swing班费管理系统:从建表到答辩的完整实战指南

简介这套班费管理系统是一份基于JavaMyBatisSwing技术栈的数据库大作业源码包面向计算机相关专业在校学生、老师及企业员工可满足课程设计、毕业设计或项目初期演示等需要。系统采用MyBatis作持久层框架、Swing构建桌面客户端并附带完整SQL数据库脚本与系统示例图片代码已在测试环境下运行通过评审分达94.5分。资源包内含88个文件以Java源码、XML配置与MyBatis映射、JFD界面设计文件为主辅以PNG示例截图、SQL建表及示例数据脚本整体仅约156KB结构紧凑、便于本地部署和学习二次开发。目前已有424人学习/下载适合数据库初学者对照理解Java桌面应用与MyBatis结合的开发流程也能在此基础上扩展功能快速形成自己的课程作业或毕设项目。1. 这个班费管理系统其实是被老师问出来的我带人审课设代码时见过太多班费管理系统——有的只有一张学生表加一张流水表余额全靠程序里临时算有的把金额存在 double 里跑几次浮点运算就开始差几分钱更有甚者一个 Java 文件塞了三屏 SQL 字符串点一次按钮拼一次 SQL。这种作业跑是能跑但老师只要问一句“余额是怎么算的”“删掉一个成员他的历史流水怎么处理”场面就会变得很安静。标题里这个组合——Java MyBatis Swing加上 SQL 脚本和示例图片恰好把课程设计最看重的三件事都占住了有能双击打开的桌面界面有规范的数据访问层有拿得出手的表结构设计和 SQL 语句。所以这篇文章不只是带你复现一个项目更是带你搞清楚这个项目在答辩时如何“立得住”。正在找 Java 课程设计或数据库大作业参考的学生或者想快速搭一套本地桌面版记账工具的开发者都可以照着往下走。2. 技术选型与整体设计为什么是 SwingMyBatis这组合难在哪班费管理系统的业务规模其实很小无非是成员管理、收入登记、支出登记、余额查询这四件事。但需求小不意味着代码可以随便写老师看的是你在小需求里有没有体现出工程化意识是否分层、SQL 是否独立维护、数据一致性靠什么保证。这个组合里Swing 负责“演”MyBatis 负责“讲”Java 负责把两者缝起来。别小看这三者的分工很多翻车项目都是因为没想清楚边界就动手结果界面代码和数据访问代码全糊在一起后面想改一个字段都要在十几个文件里找人。2.1 Swing负责演示效果MyBatis负责逻辑表达Java负责分层整合Swing 是一个被严重低估的界面框架。现在很多学生一上来就选 SpringBoot Vue结果卡在 Node 环境、跨域配置、页面路由上大作业截止前两天才把前后端跑通。Swing 完全没有这些问题JDK 自带、双击启动、JTable 原生支持表格展示演示时直接开窗口点按钮比截图堆在 PPT 里直观得多。但 Swing 也有一个明显短板——它不是给“写 SQL 的人”用的。如果直接在 JButton 的 ActionListener 里写DriverManager.getConnection代码也能跑但答辩现场老师翻代码看到业务逻辑全塞在界面事件里第一个问题就是“如果不点这个按钮这笔支出怎么校验出错了”MyBatis 在这里的价值是把数据访问从界面代码里剥离出来SQL 写在 XML 文件里统一管理Mapper 接口对应明确的增删改查方法。你改表结构时动 XML 和实体类就够了Swing 界面代码一行都不用碰。Java 则是中间的黏合剂。因为持久层和界面层都是 Java 类型DAO 返回的实体对象可以直接塞进 JTable 的 TableModel不需要 JSON 序列化那套。类型安全这件事在 Swing 里体现得特别明显——JTable 单元格里你想放 Integer 就放 Integer放 BigDecimal 就放 BigDecimal不会像前端表格那样动不动变成字符串。2.2 先按四包一资源理解项目结构别急着写代码我一般会建议把项目从功能上拆成四层entity 放数据库表对应的实体类dao 放 MyBatis 的 Mapper 接口service 放业务逻辑比如收入入账时要同时更新汇总逻辑ui 放 Swing 界面。resources 下放 MyBatis 配置和 SQL 映射文件。一个典型的包结构长这样src/main/java ├── com.example.bfms │ ├── entity │ │ ├── Member.java │ │ ├── Income.java │ │ └── Expense.java │ ├── dao │ │ ├── MemberMapper.java │ │ ├── IncomeMapper.java │ │ └── ExpenseMapper.java │ ├── service │ │ ├── IncomeService.java │ │ └── BalanceService.java │ ├── ui │ │ ├── MainFrame.java │ │ ├── IncomePanel.java │ │ └── MemberTablePanel.java │ └── util │ └── MyBatisUtil.java └── resources ├── mybatis-config.xml └── mapper ├── MemberMapper.xml └── IncomeMapper.xmlentity 里的属性名要和数据库字段对应上。数据库字段一般用下划线命名如member_name实体类属性用驼峰命名如memberName然后通过 MyBatis 的mapUnderscoreToCamelCase设置自动映射。这一步做好了后面写 resultMap 的工作量直接减半栏位多的表感受尤其明显。Maven 依赖建议用稳定版本MyBatis 3.5.xMySQL 驱动 8.0.xJUnit 4.13 做单元测试。驱动版本最好和你本机安装的 MySQL 大版本一致这是绕开“ClassNotFoundException”和 “Public Key Retrieval is not allowed” 这类奇葩报错的最省钱做法。pom.xml 里的关键依赖如下dependencies dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.16/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version scoperuntime/scope /dependency dependency groupIdjunit/groupId artifactIdjunit/artifactId version4.13.2/version scopetest/scope /dependency /dependenciesscope参数值得说明一下MySQL 驱动标成runtime表示编译期不需要打包时会带上JUnit 标成test表示只在测试目录里有效不会混进最终交付的 Jar 里。这是很多大作业代码里忽视的细节但答辩时老师看你依赖配置“干净”印象分会不一样。2.3 一条数据从窗口到数据库的完整路径梳理清楚一条数据的流动路径对后面写代码很有帮助。用户在收入登记窗口输入金额和备注点击“保存”按钮界面层把数据封装成 Income 实体传给 IncomeServiceService 里通过 SqlSession 获取 IncomeMapper 代理对象调用insertIncome方法MyBatis 根据 XML 里的 SQL 语句把实体的属性值绑定到#{}占位符上发送给 MySQL 执行执行成功后Service 返回结果给界面层界面层再刷新 JTable 数据。这段链路在源码里对应的 Service 代码核心逻辑大致是这样的public class IncomeService { public boolean addIncome(Income income) { // 每次操作都新建 SqlSession保证拿到的是最新连接 try (SqlSession session MyBatisUtil.openSession()) { IncomeMapper mapper session.getMapper(IncomeMapper.class); int rows mapper.insertIncome(income); session.commit(); // 手动提交事务防止异常时留下半截数据 return rows 0; } } }注意try-with-resources的写法SqlSession 实现了 Closeable 接口这样写能保证方法无论正常返回还是抛异常连接都会被关闭。在 Swing 这种长时间运行的程序里漏关 SqlSession 会导致连接池耗尽程序用着用着就“假死”这个坑后面专门讲。Service 层存在的意义之一就是统一管理事务边界收入插入成功后必须 commit否则界面提示成功、数据库里却没数据这种“薛定谔的保存”在答辩演示时非常尴尬。3. 先备好数据库层建库建表SQL与MyBatis连接配置数据库是班费管理系统的大作业得分主战场老师看表结构设计的认真程度往往比看界面多得多。班费管理的核心离不开三件事钱从哪来收入、花到哪去支出、现在剩多少余额。基于这个逻辑我一般会设计三张业务表加一个视图而不是把所有字段堆在一张大表里。表拆得太细增加 JOIN 复杂度拆得太粗又没法回答“哪笔钱是谁交的”这种基本问题。3.1 三张表加一个视图表结构设计的得分点三张表分别是成员表、收入流水表、支出流水表。成员表记录谁交了钱、属于哪个小组收入流水表记录每一笔进账支出流水表记录每一笔开销。余额视图通过聚合函数把收支两边汇总相减得到好处是余额永远基于明细实时计算不会出现“流水多了但余额没变化”的数据不一致问题。下面是可直接执行的建库建表 SQL我强烈建议你新建一个schema.sql文件保存这些语句交付时一并提交CREATE DATABASE IF NOT EXISTS bfms DEFAULT CHARSET utf8mb4; USE bfms; -- 成员表 CREATE TABLE t_member ( id INT NOT NULL AUTO_INCREMENT, member_name VARCHAR(50) NOT NULL COMMENT 成员姓名, student_no VARCHAR(20) DEFAULT NULL COMMENT 学号, group_name VARCHAR(30) DEFAULT 默认小组 COMMENT 小组名, deleted TINYINT NOT NULL DEFAULT 0 COMMENT 逻辑删除0正常 1删除, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB COMMENT班级成员表; -- 收入流水表 CREATE TABLE t_income ( id INT NOT NULL AUTO_INCREMENT, member_id INT NOT NULL COMMENT 交费成员ID, amount DECIMAL(10,2) NOT NULL COMMENT 收入金额精确到分, income_type VARCHAR(20) NOT NULL COMMENT 费用类型班费/活动费/罚金, remark VARCHAR(200) DEFAULT NULL COMMENT 备注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_member_id (member_id), CONSTRAINT fk_income_member FOREIGN KEY (member_id) REFERENCES t_member (id) ) ENGINEInnoDB COMMENT收入流水表; -- 支出流水表 CREATE TABLE t_expense ( id INT NOT NULL AUTO_INCREMENT, amount DECIMAL(10,2) NOT NULL COMMENT 支出金额, expense_type VARCHAR(20) NOT NULL COMMENT 支出类型打印/活动/奖品/其他, payee VARCHAR(50) DEFAULT NULL COMMENT 收款方, remark VARCHAR(200) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB COMMENT支出流水表; -- 余额视图收入总和 - 支出总和 CREATE OR REPLACE VIEW v_balance AS SELECT (SELECT IFNULL(SUM(amount), 0) FROM t_income) AS total_income, (SELECT IFNULL(SUM(amount), 0) FROM t_expense) AS total_expense, (SELECT IFNULL(SUM(amount), 0) FROM t_income) - (SELECT IFNULL(SUM(amount), 0) FROM t_expense) AS balance;三处设计细节要特别说明。第一金额字段用DECIMAL(10,2)而不是 FLOAT 或 DOUBLE这是避免余额差几分钱问题的根本手段。浮点数是二进制近似存储DECIMAL 是字符串格式化存储做财务数据时后者才是正确选择这个点答辩时拿来回答“金额类型为什么选这个”非常加分。第二外键约束要建上老师检查表设计时会看关联关系的完整性。第三成员表加了deleted逻辑删除字段而不是物理删除这样历史流水的关联数据还在统计不会被破坏。删除成员时置deleted1余额自动少掉这个人的历史贡献意义不大但逻辑一致。3.2 mybatis-config.xml 的核心配置项逐一说明MyBatis 的全局配置是整个项目的数据访问中枢。很多课程设计里把这个文件写得能跑就行但里面几个参数的开关其实决定了程序能不能稳定运行。下面这份配置是我会直接采用的模板?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/ !-- 控制台打印SQL日志排查问题时非常有用 -- setting namelogImpl valueSTDOUT_LOGGING/ /settings environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /dataSource /environment /environments mappers package namecom.example.bfms.dao/ /mappers /configurationJDBC URL 里通常要带三个参数否则新版驱动会报错或者中文乱码jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/bfms?useSSLfalsecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的数据库密码characterEncodingutf8解决中文乱码serverTimezoneAsia/Shanghai避免 MySQL 8 的时区报错useSSLfalse关闭 SSL 警告。logImplSTDOUT_LOGGING是开发期的神配置它会把你每条 SQL 和参数值直接打到控制台呈现形式类似“ Preparing: insert into t_income…”比用调试器打断点快多了。等项目跑顺了再把这个配置去掉或换成 log4j2因为控制台日志打印本身有一点性能开销。mappers标签里用package扫描 dao 包比逐个列 XML 路径省心得多前提是 Mapper 接口和 XML 文件必须同名且同包。3.3 SqlSessionFactory 必须单例这是 Swing 程序的保命符Swing 程序可能有多个窗口每个窗口都可能触发数据库操作。如果每个窗口都重新读取 mybatis-config.xml 并创建 SqlSessionFactory不仅资源浪费严重还会在窗口频繁开关时遇到奇怪的初始化失败。正确的做法是写一个 MyBatisUtil用静态代码块保证工厂只初始化一次public class MyBatisUtil { private static SqlSessionFactory factory; static { try { // 从 classpath 读取配置只初始化一次 InputStream is Resources.getResourceAsStream(mybatis-config.xml); factory new SqlSessionFactoryBuilder().build(is); } catch (IOException e) { e.printStackTrace(); throw new ExceptionInInitializerError(MyBatis初始化失败请检查mybatis-config.xml); } } public static SqlSessionFactory getFactory() { return factory; } // 默认关闭自动提交事务边界由业务层决定 public static SqlSession openSession() { return factory.openSession(false); } }这里的关键参数是openSession(false)括号里的 false 表示关闭自动提交。为什么不启用自动提交因为收入登记往往不是一个单表操作——插入流水后可能还要同步更新某个汇总字段两步必须在一个事务里同时成功或同时失败。如果用自动提交第一步插入成功、第二步更新失败就会出现“钱记上了但余额没变”的数据问题。每次调用openSession()都会从连接池拿一个独立连接用完记得在 finally 或 try-with-resources 里关闭释放回连接池。这个“用完即关”的习惯对 Swing 这种长生命周期程序来说是铁律。4. 把界面和数据连通Mapper XML到JTable刷新的完整链路前面的准备做好后剩下的工作就是把 Swing 界面上用户的每一次点击翻译成一条带参数的 SQL 执行再把查询结果塞回表格。这个链路听着简单实际操作中有三个关键点容易出错XML 映射的语法是否正确余额数据用不用视图以及 Swing 线程模型和数据库耗时操作怎么共存。4.1 收入流水 Mapper从接口定义到动态 SQL先在 dao 包里定义接口然后在 resources/mapper 目录下写同名 XML。接口方法名、参数类型、返回类型都要和 XML 中的 id、parameterType、resultType 对齐这是 MyBatis 能“自动找到”映射关系的约定。一个典型的收入流水 Mapper 如下public interface IncomeMapper { int insertIncome(Income income); ListIncome selectIncomeByCondition(Param(memberId) Integer memberId, Param(incomeType) String incomeType); ListMapString, Object selectIncomeWithMember(); BigDecimal sumIncome(); }对应的 XML 文件?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN https://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.bfms.dao.IncomeMapper insert idinsertIncome parameterTypecom.example.bfms.entity.Income INSERT INTO t_income (member_id, amount, income_type, remark) VALUES (#{memberId}, #{amount}, #{incomeType}, #{remark}) /insert select idselectIncomeByCondition resultTypecom.example.bfms.entity.Income SELECT id, member_id, amount, income_type, remark, create_time FROM t_income where if testmemberId ! null AND member_id #{memberId} /if if testincomeType ! null and incomeType ! AND income_type #{incomeType} /if /where ORDER BY create_time DESC /select select idselectIncomeWithMember resultTypemap SELECT i.id, m.member_name, i.amount, i.income_type, i.create_time FROM t_income i JOIN t_member m ON i.member_id m.id ORDER BY i.create_time DESC /select select idsumIncome resultTypedecimal SELECT IFNULL(SUM(amount), 0) FROM t_income /select /mapper写 SQL 时要注意#{}和${}的区别。#{}是预编译占位符MyBatis 会把它翻译成?再由 JDBC 的 PreparedStatement 参数化赋值能有效防止 SQL 注入${}是字符串拼接直接把值拼进 SQL存在注入风险。班费管理项目里几乎可以全用#{}只有排序字段名这种必须动态拼接的地方才考虑${}并且要用白名单校验字段名。这个知识点是面试八股文里的常客你在项目里写对了答辩时能压住场子。selectIncomeWithMember返回ListMapString, Object而不是实体对象是因为这条 SQL 查的是两张表的组合数据没有现成的实体类对应。返回 Map 虽然类型不够“面向对象”但胜在省去额外建视图实体类的步骤适合课程设计这种规模的项目。有精力的话建一个IncomeWithMemberVO会更规范但演示效果差别不大。4.2 余额展示用数据库视图比 Java 里临时累加稳得多种常见的余额计算做法我对比后最推荐数据库视图方案方案实现方式优点缺点查询时聚合每次查两张表的 SUM 再相减逻辑直白SQL 写得散落多处难以统一余额字段在表里维护 balance 字段查询快必须保证事务一致性很容易出现脏数据数据库视图创建 v_balance 视图查询时 SELECT * FROM v_balance复用性强SQL 集中数据量大时求和有点开销班费规模可忽略视图方案在答辩时很讨巧因为老师能直观看到你懂“聚合查询”和“视图”这两个知识点。查询余额的 Mapper 方法也很简单public interface BalanceMapper { MapString, Object selectBalance(); }mapper namespacecom.example.bfms.dao.BalanceMapper select idselectBalance resultTypemap SELECT total_income, total_expense, balance FROM v_balance /select /mapper拿到这个 Map 后在 Swing 的标签上直接显示balance字段即可。注意BigDecimal的toString()在金额较大时会输出科学计数法比如1.2E3显示时需要toPlainString()转成普通字符串这是金额显示里最容易踩的格式化坑。4.3 JTable刷新的正确姿势别在事件线程里跑SQLSwing 是单线程模型所有界面刷新都要在 Event Dispatch ThreadEDT上执行。如果直接在按钮的 ActionListener 里执行数据库查询数据库响应慢的时候整个窗口会被“冻结”拖拽、关闭统统失灵这就是传说中的界面假死。正确处理方式是用 SwingWorker 把耗时操作放到后台线程执行执行完再切回 EDT 刷新界面。下面这段代码演示了“点击查询按钮 → 后台查数据库 → 刷新表格”的标准写法JButton queryBtn new JButton(查询流水); queryBtn.addActionListener(e - { // 后台线程执行数据库操作避免阻塞EDT导致界面卡死 SwingWorkerListMapString, Object, Void worker new SwingWorker() { Override protected ListMapString, Object doInBackground() { try (SqlSession session MyBatisUtil.openSession()) { IncomeMapper mapper session.getMapper(IncomeMapper.class); return mapper.selectIncomeWithMember(); } } Override protected void done() { try { ListMapString, Object data get(); refreshTable(data); // 在EDT上刷新表格 } catch (Exception ex) { JOptionPane.showMessageDialog(MainFrame.this, 查询失败 ex.getMessage()); } } }; worker.execute(); });doInBackground里的代码运行在后台线程可以放心执行数据库操作done方法自动切回 EDT 线程这时才能安全地操作 JTable 模型。很多教程喜欢用new Thread(() - { ... }).start()然后在 run 方法里直接操作界面组件这种做法严格来说线程不安全可能引起界面刷新异常。SwingWorker 是官方提供的线程桥接工具虽然看着有点模板化但是最稳的。4.4 表格刷新把 Map 列表转成 TableModel 数据拿到查询结果后让 JTable 显示数据需要把 List 转成二维数组和列名数组。不要每刷新一次就 new 一个 JTable 替换旧的那样会破坏表格的列宽设置和选中状态正确做法是更新现有 JTable 的 TableModel。一个通用的刷新方法大致如下private void refreshTable(ListMapString, Object data) { // 列名固定展示成员流水时只需要这几列 String[] columns {ID, 姓名, 金额, 类型, 时间}; Object[][] rows new Object[data.size()][columns.length]; for (int i 0; i data.size(); i) { MapString, Object row data.get(i); rows[i][0] row.get(id); rows[i][1] row.get(member_name); rows[i][2] row.get(amount); rows[i][3] row.get(income_type); rows[i][4] String.valueOf(row.get(create_time)); } // 复用JTable的模型保留列宽与排序设置 table.setModel(new DefaultTableModel(rows, columns) { Override public boolean isCellEditable(int row, int column) { return false; // 表格不可直接编辑数据入口在表单里 } }); }isCellEditable返回 false 这个细节值得单独说。如果不重写这个方法默认 TableModel 的单元格是可以双击进入编辑状态的用户误点一下表格就能改数字但改动没有走 Service 层也不会写回数据库会造成“界面上改了、数据库没改”的错觉。重写为 false 后表格只读数据修改只能通过表单和按钮触发符合这类管理系统的操作习惯。5. 班费系统避坑记录五处让大作业翻车的细节这章写给所有准备在截止日期前熬夜交付的同学。下面五条全是这类课程设计里反复出现的真实问题每个都按“现象 → 原因 → 解决”的结构拆开讲。这些问题单独看都不难但它们往往同时出现叠加在一起的排错成本会很高。5.1 点按钮后窗口“假死”拖都拖不动现象点击“保存”或“查询”后程序无响应标题栏显示“未响应”等待几秒甚至十几秒才恢复。原因数据库操作直接写在 ActionListener 里执行时占用了 Swing 的 EDT 线程。EDT 负责所有界面事件处理和绘制一旦被长时间的 SQL 操作阻塞窗口自然就“冻结”了。班费系统数据量不大正常情况下 SQL 执行只要几十毫秒所以这个问题在演示时不一定复现但数据量一大或者数据库第一次连库握手较慢时会明显暴露。解决所有涉及数据库读写、网络等待的操作统一放到 SwingWorker 的 doInBackground 里执行完成后在 done 里刷新界面。这是我前面反复强调的做法也是 Swing 程序的基本素养。演示时如果不想让数据库连接拖慢启动速度可以在主窗口加载后用 SwingWorker 后台预连接一次数据库。5.2 余额总是差几分钱合计数对不上现象界面显示的收支合计和用计算器按出来的结果差几分钱或者显示成12.300000000001这种诡异数字。原因实体类的金额属性用了 double 或 float。浮点数在计算机内部用二进制近似表示0.1 0.2这种十进制的简单计算在二进制体系下会得到一个无限循环小数最终精度丢失。哪怕数据库字段建成了 DECIMAL如果 Java 传进来的是 doubleMySQL 在转换时也会有一定风险。解决Java 侧金额一律用java.math.BigDecimal类型数据库侧一律用DECIMAL(10,2)两层保持一致。前台用户在文本框里输入的金额是字符串先用new BigDecimal(inputText)构造再传给实体对象做加减运算时调用add和subtract方法不要用和-。显示给用户看时用setScale(2, RoundingMode.HALF_UP)保留两位小数并做四舍五入。5.3 数据库里明明有数据界面却一直显示旧结果现象用 MySQL 客户端直接插入两条测试数据回到程序点“查询”表格里还是老数据。关闭程序重开数据才出现。原因这是 MyBatis 一级缓存和 SqlSession 生命周期共同作用的结果。MyBatis 默认开启一级缓存同一个 SqlSession 内执行的相同查询会直接返回缓存结果。如果你在代码里复用了同一个 SqlSession 对象做多次查询第二次查询就不会真正访问数据库。另外如果你用SqlSessionFactory.openSession(true)开启了自动提交但没及时关闭 session连接对象被归还连接池时缓存可能未清表现出的现象也是“查询结果没更新”。解决坚持“每次操作都新开 SqlSession用完即关”的原则。查询和写操作都不要长时间持有一个 SqlSession。写完数据后手动session.commit()再关闭会话MyBatis 的缓存默认基于SqlSession生命周期会话关闭后缓存自然清空。这样的代价是每次操作都新建连接但对班费系统的访问频率来说完全没有压力换来的是数据一致性。5.4 换台电脑就启动失败链接数据库直接报错现象在自己电脑上运行正常打包成可执行 Jar 或把项目复制到老师的电脑上启动后报Access denied for user或Unknown database。原因大部分课程设计的数据库连接参数硬编码在mybatis-config.xml里账号密码写的是你本机的值。换电脑后MySQL 账号、密码、端口甚至安装路径都不一样连接失败是必然的。还有一种情况是 JDBC 驱动版本和对方 MySQL 版本不匹配你本机 MySQL 5.7 用的 5.1.x 驱动到老师电脑上是 MySQL 8.0驱动不认识新版本认证协议直接抛异常。解决把数据库连接参数提取到db.properties文件里程序启动时读取方便在任何机器上修改配置提交作业时把配置文件单独放一份并在 README 里写明“修改 db.properties 为本地数据库账号”。JDBC 驱动直接统一用mysql-connector-java8.0.x它对 MySQL 5.7 和 8.0 都能兼容避免驱动版本和数据库版本错配的尴尬。舍近求远地讲这也是比“重装数据库”更好的思路。5.5 删除成员时弹出外键约束错误代码逻辑直接中断现象在成员管理里删除一个已交过班费的同学程序抛 SQLIntegrityConstraintViolationException提示外键冲突删除失败。你明明加了 try catch界面却没有任何反应。原因t_income表里通过外键引用了t_member.id直接 DELETE 成员记录会触发数据库的外键约束检查有子记录存在时不允许删除父记录。这是数据库保护数据完整性的正常行为但业务上你可能只是想把这个人移出班级并不想删掉历史缴费记录。用 try catch 把异常吞掉的做法更不可取用户完全不知道发生了什么。解决改成逻辑删除删除操作执行UPDATE t_member SET deleted 1 WHERE id ?而不是物理删除。查询成员列表时一律加WHERE deleted 0条件。这样历史流水和成员表的关联依然存在统计汇总也不会因为删除记录发生偏差。报表或成员下拉框里需要注意过滤deleted0的条件否则已删除的成员会出现在新流水的候选人列表里那是另一种 bug。6. 答辩前最后一步造数、配置分页与截图验证要走一遍项目能跑通只是及格线答辩时让老师觉得“这学生考虑事情比较全面”反而在最后这几个细节上。演示用的数据要“真实”。手工往数据库里插十几条记录是远远不够的老师最容易挑刺的是为什么全是同一笔金额我一般会写一个小工具类用循环造 3040 条流水覆盖月初交班费、班委集中采购、期末退费几个典型时间段金额设置成 15.50、128.00、56.30 这种带小数的值。这样余额视图展示出来才像回事。造数脚本放在测试目录里甚至可以留在项目里作为演示数据生成器答辩时现场跑一遍会显得很扎实。MyBatis 分页插件的用法也是高频追问点。Swing 程序虽然不一定真需要分页但老师可能会问“流水表多了怎么办”。引入 PageHelper 依赖后在 Service 查询前加一行PageHelper.startPage(pageNum, pageSize)紧接着的查询会自动带上 LIMIT 参数再配合一个PageInfo就能把当前页码和总记录数一起返回。这个插件在 SpringBoot 项目里很常用原理是基于 MyBatis 的拦截器在查询执行前改写 SQL。顺手了解一下底层机制比单纯背用法更能扛住追问。截图素材也要按流程准备完整系统启动后的主界面、新增收入录入前的空白表单、录入后的流水表页面、余额查询页面再到图表或统计页面每个页面截一张若有报错解决后再补一张运行正常的。截图命名按“01-主界面.png”这种顺序编号放 Word 报告里直接串起一条演示主线。我当年交付前忽然改了一个字段类型导致视图失效无脑连跑了几轮造数脚本才恢复平衡从那以后每次动表结构我都会把建表 SQL、造数脚本、三个关键查询各执行一遍才敢点“打包”这个习惯也建议你保留。最后准备三个问题的口答一是为什么用 MyBatis 而不是原生 JDBC回答“对 SQL 进行集中管理、参数预编译防注入、自动完成结果集到实体的映射”二是事务怎么保证回答“通过openSession(false)关闭自动提交业务层手动 commit 或 rollback”三是余额为什么用视图回答“保证余额是基于明细的实时聚合避免人工维护余额字段引发的不一致”。把这三个问题背到条件反射你的班费管理系统就不仅能跑而且能“站得住”。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门