☰
Java课程设计:用Swing+JDBC实现小型档案管理系统
2026/9/30 5:36:45 网站建设 项目流程

简介:一份基于Java实现的小型档案管理系统实验设计资源,采用C/S架构,覆盖用户登录、角色权限、档案上传下载、条件查询与个人信息维护等完整流程。系统将用户分为系统管理人员、档案录入人员、档案浏览人员三类,客户端与服务器端通过Socket通信,服务器端多线程可同时响应多个请求,适合作为高校Java课程设计、多线程与Socket编程实验的参考范例。资源包含完整Eclipse工程源码与编译后的class文件,client/server双端目录清晰,另有SQL建表脚本、MySQL驱动JAR包、使用说明文档及实验指导书docx文档,便于按步骤部署运行。压缩包共47个文件,以java源码、class字节码、txt文档为主,整体仅1.37MB,轻量易取,目前已有873人学习下载。读者可获得可运行的C/S通信示例、多线程并发处理思路、三种角色权限管理的数据库设计,以及文件上传下载与元数据分离存储的实践方案,并能对照工程结构快速完成实验环境搭建与功能扩展。

1. 拿到“Java实验设计-实现一个小型档案管理系统”这个题目,先别慌:它难在哪,又能带来什么

拿到“Java实验设计-实现一个小型档案管理系统”这个题目时,多数人的第一反应是“又要连数据库,又要画界面,我从哪头开始”。这个选题看起来小,实则把Java基础、面向对象编程Java、JDBC和Swing事件驱动串成了一条完整的练习链,正好可以作为Java学习路线里的第一个有业务闭环的练手项目。它适合课程设计、期末实验,也适合准备Java基础面试题前,做一个能讲出设计亮点的作品。

我会采用一套最保守也最可控的技术方案:Java SE + Swing + JDBC + MySQL。不引入Spring,不写前端页面,单人一周内可以从建库跑到答辩演示。整个方案把“档案管理”收敛为登录、档案增删改查、借阅登记、数据导出几个模块,既满足实验评分,也不至于让项目失去边界。

本文按“需求拆解 → 技术选型 → 分层实现 → 界面落地 → 避坑排查 → 答辩进阶”的顺序展开。目标很明确:你不仅能跑通这个项目,还能在被老师问到“为什么这样设计”“并发时怎么办”时,给出有底气的回答。

2. 小型档案管理系统的需求拆解与技术选型:先画好边界再写代码

很多课程设计翻车,不是代码写错,而是需求没锁住。档案管理系统听上去很大,但“小型”两个字就是给你划好了边界:不需要流程审批、不需要多级权限、不需要微服务。你只需要做成一个能让一个管理员完成日常纸质档案电子化、查询和借阅登记的单机桌面程序。

在动笔前,我会先把功能清单列在纸上,并对照课程设计评分表逐项打勾。这样写代码时不会东一榔头西一棒子,答辩时也能说清“每一个功能对应的验收标准是什么”。

2.1 从课程设计评分表反推功能清单

大多数Java课程设计的评分维度是:功能完整度、界面可用性、数据库设计、代码规范性、演示流畅度。围绕这五点,一个能站稳脚跟的档案管理系统至少要包含:

  • 用户登录:区分管理员和普通用户,密码不能明文硬编码在业务代码里;
  • 档案台账:以表格形式展示所有档案,支持编号、标题、分类检索;
  • 新增档案:录入编号、标题、内容摘要、分类、录入人;
  • 编辑与删除:修改档案信息,删除前必须二次确认;
  • 借阅登记:记录借出人和借出时间,归还时更新归还时间;
  • 日志或审计线索:哪怕只是记录“谁在什么时候操作了哪条档案”,也是加分项。

我一般会做成一个三行三列的功能矩阵:模块 / 最低验收点 / 加分项。例如登录模块最低验收点是“错误密码进不去”,加分项是“输错三次锁定30秒”;查询模块最低验收点是“按标题模糊查询”,加分项是“组合条件查询”。把这个矩阵写在设计说明里,答辩老师一眼就能看出你做过需求分析。

很多同学一上来就做复杂的权限模型,结果界面堆了七八个窗口,最后只演示了登录和跳出。要记住,实验设计的目的是验证你知道怎么组织一个完整的小系统,而不是模拟Oracle ERP。功能范围控制在“一个人一周能完成”的状态,留出时间打磨异常处理和答辩话术,性价比最高。

2.2 技术选型:为什么是Swing + JDBC,而不是Spring全家桶

先回答一个高频问题:现在企业都写Spring Boot + Vue,为什么课程设计还要用Swing和JDBC?因为实验课的时间、运行环境和评分标准都不支持你去搭一套前后端分离工程。Swing是JDK自带的GUI框架,打包成jar后双击即可运行,不需要安装Tomcat;JDBC是Java访问关系型数据库的原始接口,它能让你看清SQL的执行过程,而不是被MyBatis的映射文件包住。

从Java学习路线的角度看,先掌握Swing + JDBC,再去看Spring的JdbcTemplate和事务管理,理解成本会低很多。Java基础面试题里常问的PreparedStatement防SQL注入、try-with-resources自动关闭、线程与事务,在这个项目中都能亲手验证一次。如果你直接跳到Spring Boot,这些底层细节反而不容易讲清楚。

数据库我选MySQL 8.x,驱动是mysql-connector-java 8.x。连接URL里必须带serverTimezone=Asia/Shanghai,否则新版驱动会报时区错误。开发环境需要先配置好JAVA_HOME和PATH,这个步骤属于Java环境变量配置,有人会在IDE里能运行,但命令行java -version报错,最后在连接数据库时卡住。所以第一件事是确认命令行能敲出java -version和javac -version,再继续往下走。

另外一个需要理性看待的选型是JavaFX。Swing虽然老,但在课程设计中更常见,遇到问题容易搜到资料;JavaFX的布局更现代,但新电脑上可能要额外配置OpenJFX模块。如果你只有五天时间,选Swing的试错成本最低。界面丑可以通过布局和配色补救,“跑不起来”才是真翻车。

2.3 数据库表设计:三张表把档案业务说清楚

档案管理系统的核心数据可以抽象成三个概念:操作者、档案本身、借阅记录。档案分类不需要单独建表,用varchar字段存“人事档案/行政档案/技术档案”即可,这是小型系统里很常见的简化做法。下面是我推荐的建表语句,直接用MySQL 8执行即可。

CREATE DATABASE IF NOT EXISTS archives DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE archives; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', username VARCHAR(32) NOT NULL UNIQUE COMMENT '登录名', password VARCHAR(64) NOT NULL COMMENT '密码,实验阶段存原文,生产环境必须加密', role VARCHAR(16) NOT NULL DEFAULT 'USER' COMMENT 'ADMIN/USER' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_archive ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '档案ID', archive_no VARCHAR(32) NOT NULL UNIQUE COMMENT '档案编号,业务唯一键', title VARCHAR(128) NOT NULL COMMENT '标题', content TEXT COMMENT '内容摘要', category VARCHAR(32) DEFAULT '未分类' COMMENT '档案分类', user_id INT COMMENT '录入人ID', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_borrow ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '借阅ID', archive_id INT NOT NULL COMMENT '档案ID', borrower VARCHAR(32) NOT NULL COMMENT '借用人', borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '借出时间', return_time DATETIME DEFAULT NULL COMMENT '归还时间,为空表示未归还' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

执行完这段SQL后,可以顺手插入一条管理员数据和两条样例档案,方便后面调试界面:

INSERT INTO t_user(username, password, role) VALUES('admin', '123456', 'ADMIN'); INSERT INTO t_archive(archive_no, title, content, category, user_id) VALUES('DA-2024-0001', '员工入职登记表', '张三,2024年3月入职', '人事档案', 1);

这段DDL的关键点有三个。第一,t_archive的archive_no字段用了UNIQUE约束,对应业务规则“档案编号不允许重复”,这比在Java代码里先查询再判断要可靠得多。第二,t_borrow不建物理外键,只保留逻辑外键archive_id,因为实验演示时删除档案会被外键约束拦住,老师会觉得你的系统“删不掉数据”。第三,create_time用DATETIME DEFAULT CURRENT_TIMESTAMP,避免在Java层手动拼时间,少一个出错点。

关于密码字段,实验阶段用原文可以,但你要能主动说出“正式系统应该用BCrypt或SHA-256加盐”,这句话在答辩时是明显的加分项。数据一致性方面,后续在Service层用事务保证借阅登记的原子性,这里先不展开。

三张表的粒度正好对应三个业务模块:认证、档案台账、借阅。如果以后项目要扩展成Web版,直接把这套模型平移到Spring Boot + JPA,几乎不需要改表结构。

3. 用面向对象分层把核心业务写扎实:实体类、DAO与Service的落地

技术选型确定后,最容易犯的错是把所有JDBC代码都塞进Swing事件监听器里。一个登录按钮里既写SQL又写ResultSet解析,短期能跑,后期改需求时只能重构。这里我会采用教科书式的分层:界面层只调用Service,Service只调用DAO接口,DAO实现细节完全被隔离。这也是面向对象编程Java里“面向接口编程”的直观演练。

包结构建议这样建:

com.archives ├── entity // Archive、User、BorrowRecord实体 ├── dao // ArchiveDao接口 ├── dao.impl // ArchiveDaoImpl ├── service // ArchiveService ├── ui // LoginFrame、MainFrame └── util // DBUtil

3.1 实体类与连接工具:把面向对象编程Java的封装落到实处

实体类对应数据库表,是Java基础里“类与对象”最直接的体现。以档案实体为例,所有属性私有,对外只暴露getter/setter,这就是封装。不写公共字段,更不允许直接操作数据库列名。下面的Archive实体保留核心字段,ID用Integer而不是int,是为了方便判断“尚未保存”的空值状态。

public class Archive { private Integer id; private String archiveNo; private String title; private String content; private String category; private Integer userId; private String createTime; // getter/setter省略,建议手写而非依赖IDE生成 // 便于在setter中添加空值校验 public void setTitle(String title) { if (title == null || title.trim().isEmpty()) { throw new IllegalArgumentException("标题不能为空"); } this.title = title; } }

实体类里可以做轻量校验,比如setTitle抛IllegalArgumentException,把数据合法性检查从界面层前置到模型层。这种做法会让你在答辩时被问到“谁负责校验”时,回答“实体层做基础校验,Service层做业务校验,UI层只负责展示提示”,这就是清晰的分层职责。

连接工具类我一般写成这样,读取classpath根目录下的db.properties,避免把数据库账号密码写死在Java代码里。

public class DBUtil { private static String url; private static String user; private static String password; static { try (InputStream in = DBUtil.class.getClassLoader() .getResourceAsStream("db.properties")) { Properties props = new Properties(); props.load(in); Class.forName("com.mysql.cj.jdbc.Driver"); url = props.getProperty("url"); user = props.getProperty("user"); password = props.getProperty("password"); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } }

db.properties放在src目录下,内容为:

url=jdbc:mysql://localhost:3306/archives?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai user=root password=你的密码

这段代码有一个细节容易被新手忽略:Class.forName注册驱动只执行一次,放在static块里最合适。getConnection每次都创建新连接是实验项目的正常做法,但如果后续要撑高并发,就要换成连接池,我在最后一张会提替代方案。

3.2 DAO接口与JDBC实现:用PreparedStatement把SQL注入挡在门外

DAO层的第一个决策是定义一个接口,而不是直接写实现类。接口定义了“这个模块对外承诺提供哪些能力”,实现类可以被替换,这就是面向对象编程Java里的“多态”在架构层面的体现。下面是ArchiveDao接口:

public interface ArchiveDao { List<Archive> findAll() throws SQLException; Archive findById(int id) throws SQLException; int insert(Archive archive) throws SQLException; int update(Archive archive) throws SQLException; int deleteById(int id) throws SQLException; List<Archive> search(String keyword) throws SQLException; }

为什么方法要声明throws SQLException?因为把异常抛给Service层统一处理,比在DAO里打印吃掉更利于事务回滚。这是Java基础面试题里常问的“受检异常vs运行时异常”的实际应用。

实现类ArchiveDaoImpl里,我重点演示insert方法,其余方法同构。

public class ArchiveDaoImpl implements ArchiveDao { @Override public int insert(Archive archive) throws SQLException { String sql = "INSERT INTO t_archive(archive_no, title, content, category, user_id) " + "VALUES (?, ?, ?, ?, ?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, archive.getArchiveNo()); ps.setString(2, archive.getTitle()); ps.setString(3, archive.getContent()); ps.setString(4, archive.getCategory()); ps.setInt(5, archive.getUserId()); return ps.executeUpdate(); } } }

参数说明:PreparedStatement的setString/setInt从1开始编号,顺序和SQL里的问号一一对应。try-with-resources会在方法结束时自动调用close,即使执行中抛异常也不会连接泄漏。这里不要用Statement拼接字符串,因为形如“1' or '1'='1”的输入可以直接改写SQL语义。把参数用问号占位符传给数据库,等于把“数据”和“SQL结构”彻底分开,这是Java基础里一个必须拿得出手的安全实践。

findAll的常见写法是循环遍历ResultSet,把每一行映射成一个Archive对象,再放入List。这里有一个性能细节:ResultSet的默认游标只能向前,如果你在遍历时执行了另一个查询,连接可能被占用。所以要在同一个try块内完成查询和对象映射,尽早释放Statement。我见过有人把ResultSet直接存进List,关闭连接后再取数据,结果报“Closed Statement”错误,这是很典型的踩坑点。

3.3 Service层的更新边界:对象深度拷贝与事务的初步使用

DAO只负责“一条SQL跑完”,但业务上经常需要“多条SQL一起成功”。例如借阅登记时,要先在t_borrow里插入一条记录,再在内存里维护“该档案已借出”的状态。如果第二条SQL失败,第一条已经提交,账就会对不上。Java面试题里常出现的“怎么保证数据一致性”,答案就是事务。

日常项目里,我通常不会把JDBC事务代码直接写在DAO里,而是放在Service层统一控制。借阅登记的简化代码如下:

public void borrowArchive(int archiveId, String borrower) throws SQLException { String insertBorrow = "INSERT INTO t_borrow(archive_id, borrower) VALUES (?, ?)"; String updateArchive = "UPDATE t_archive SET status = 1 WHERE id = ?"; boolean success = false; try (Connection conn = DBUtil.getConnection()) { conn.setAutoCommit(false); try (PreparedStatement ps1 = conn.prepareStatement(insertBorrow); PreparedStatement ps2 = conn.prepareStatement(updateArchive)) { ps1.setInt(1, archiveId); ps1.setString(2, borrower); ps1.executeUpdate(); ps2.setInt(1, archiveId); ps2.executeUpdate(); conn.commit(); success = true; } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); } } finally { // 可以在这里打日志记录 borrowArchive 成功/失败 } }

参数说明:setAutoCommit(false)关闭自动提交,所有SQL在同一个数据库事务内执行;commmit或rollback后要恢复自动提交,否则连接归还到池子时会带着错误状态。这段代码在答辩时,是回答“数据一致性”问题的最佳素材。老师再追问“两条SQL执行过程中有人同时查询怎么办”,你可以进一步说需要配合锁或隔离级别,但实验课能讲到事务这层已经很扎实。

Service层还牵扯到一个Java对象图常识:对象深度拷贝。比如从数据库查出一条Archive,界面打开了编辑框,用户点了“取消”,如果编辑框里的对象就是原对象的引用,那么原对象已经被污染了。常见做法是在Service层提供copyForEdit方法,new一个Archive再逐个字段赋值。这里别用Object.clone(),因为默认是浅拷贝,如果实体里持有List或自定义对象,浅拷贝会出现两个对象共享同一个子对象的副作用。课程设计里用构造器或手动拷贝方法最直观,也能讲清楚。

4. 用Swing把增删改查变成可演示的界面:从登录到档案台账

分层代码写完之后,Swing界面就变成了“表现层”。很多同学喜欢先把界面画得天花乱坠,再补业务逻辑,结果发现按钮事件里塞了500行JDBC代码。正确顺序是:先有Service的方法,再设计窗口调用它们。下面我会给出一个能复制的登录框架和JTable数据刷新模板。

4.1 用SwingUtilities.invokeLater启动程序:登录窗口的完整写法

Swing不是线程安全的,界面必须在事件分发线程(EDT)上创建。如果直接在main里写new LoginFrame(),在部分操作系统上会出现随机闪烁或控件失效。安全写法是:

public class App { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { LoginFrame frame = new LoginFrame(); frame.setVisible(true); }); } }

invokeLater让界面的创建任务排队到EDT执行,这是Java事件机制的基础。你可以在构造LoginFrame时加载用户列表或者初始化主题,耗时操作要放到后台线程。

登录按钮的监听器是Swing事件编程最典型的例子:

loginBtn.addActionListener(e -> { String username = usernameField.getText().trim(); String password = new String(passwordField.getPassword()); if (username.isEmpty() || password.isEmpty()) { JOptionPane.showMessageDialog(LoginFrame.this, "用户名和密码不能为空"); return; } boolean ok = userService.checkLogin(username, password); if (ok) { new MainFrame(username).setVisible(true); dispose(); // 关闭登录窗口 } else { JOptionPane.showMessageDialog(LoginFrame.this, "用户名或密码错误"); } });

这里有两个细节:JPasswordField的getPassword返回char数组而不是String,这是为避免密码字符串滞留在堆内存中;校验放在Service层,界面只负责触发和展示结果。错误提示不要用System.out.println打印到控制台,因为演示时没人看后台,要用JOptionPane弹窗。

4.2 JTable与DefaultTableModel:把档案列表变成可刷新界面

档案台账是系统的门面,我通常用JTable + DefaultTableModel实现。JTable本身不存数据,数据模型才是数据源,这也是MVC模式的最小实践。

DefaultTableModel model = new DefaultTableModel( new String[]{"ID", "档案编号", "标题", "分类", "录入人ID", "创建时间"}, 0); JTable table = new JTable(model); public void loadData() { model.setRowCount(0); List<Archive> list = archiveService.listAll(); for (Archive a : list) { model.addRow(new Object[]{ a.getId(), a.getArchiveNo(), a.getTitle(), a.getCategory(), a.getUserId(), a.getCreateTime() }); } }

每次刷新前先model.setRowCount(0)清空旧数据,再addRow重新填充。这个模式虽然性能不高,但课程设计的数据量通常只有几十条,完全够用。如果你固定JTable的列数,可以在setAutoResizeMode(JTable.AUTO_RESIZE_OFF)时手动设置列宽,避免最后一列被压缩。

JTable选中行后通过table.getSelectedRow()可以拿到视图行号。这里有一个常见坑:如果对表格做排序或过滤,selectedRow是视图索引,不能直接用它去访问原始List,需要table.convertRowIndexToModel()转换。我建议课程设计的演示不要开启排序,减少一个变量。

4.3 事件监听里的三个细节:保存、清空与错误提示

新增档案是JDialog里的一组JTextField,点“保存”后需要触发Service。下面是保存事件的模板:

saveBtn.addActionListener(e -> { String no = noField.getText().trim(); String title = titleField.getText().trim(); if (no.isEmpty() || title.isEmpty()) { JOptionPane.showMessageDialog(this, "编号和标题为必填项", "校验", JOptionPane.WARNING_MESSAGE); return; } Archive archive = new Archive(); archive.setArchiveNo(no); archive.setTitle(title); archive.setContent(contentArea.getText()); archive.setCategory(categoryBox.getSelectedItem().toString()); archive.setUserId(currentUserId); try { archiveService.insert(archive); loadData(); clearFields(); JOptionPane.showMessageDialog(this, "保存成功"); } catch (SQLException ex) { JOptionPane.showMessageDialog(this, "保存失败:" + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); ex.printStackTrace(); } });

这里组合使用了StringBuilder拼日志吗?其实不需要。如果异常信息很长,用StringBuilder组装JOptionPane的text最合适,例如:

StringBuilder msg = new StringBuilder("保存失败:"); msg.append(ex.getMessage()); msg.append("\n请检查档案编号是否重复");

这个点在面试里会被延伸:“字符串拼接用+还是StringBuilder?”在循环内拼接必须用StringBuilder,这里只有一个append,用+也可读。关键是你能说明自己的选择依据。

清空按钮直接对每个JTextField调用setText(""),刷新表格时不要重新new DefaultTableModel赋给JTable,否则会丢失表头样式。错误提示统一用Error级别图标,区别于“请输入内容”的Warning图标,用户一看图标就知道是什么性质的提示。

Swing的老规矩是:不要在监听器里写耗时超过几百毫秒的操作。如果借阅登记要实时校验外部系统,应该放到SwingWorker的doInBackground中。课程设计中不一定用得到,但你要能说“我知道事件线程不能阻塞”,这句话足够让老师对你刮目相看。

5. 档案管理系统实现中的避坑记录:4个常见的翻车现场与排查思路

这一节我把做同类项目时踩过的坑整理成清单,每条都按“现象→原因→解决”来写。只要你能按顺序排查,这个项目基本不会在演示当天当机。

5.1 报错ClassNotFoundException: com.mysql.cj.jdbc.Driver

现象:在IDEA里运行正常,直接双击jar包或打包后运行,提示找不到MySQL驱动类。有些同学在代码里写Class.forName("com.mysql.jdbc.Driver"),也报一样的错。

原因:把mysql-connector-java的jar包导入了IDE的Library,但打包时没有把它带进classpath。或者驱动类名用错:MySQL 8.x驱动类是com.mysql.cj.jdbc.Driver,旧驱动名在新版里已经被移除。

解决:项目结构里把驱动jar放在一个lib目录,并在IDEA的Project Structure > Modules > Dependencies中设为“Compile”。如果打包成可执行fat jar,用Ant或Maven的shade插件把驱动类并入jar。临时启动时,命令行用java -cp "lib/*;classes" com.archives.App,确保通配符能覆盖lib下的所有jar。

这里也提醒一点:配置驱动之前,先用命令行验证java和javac存在;如果只有java但没有javac,说明只装了JRE没装JDK,需要回看Java环境变量配置。这是很多人忽略的前置条件。

5.2 中文写入数据库变成问号,Swing界面显示也是乱码

现象:控制台打印中文正常,但插入MySQL后变成??;或者在Swing里刚输入的中文马上就变成乱码。

原因:数据库连接URL没有指定characterEncoding=utf8,并且建表时用了默认的latin1字符集。MySQL服务器、连接层、Java内存三者字符集不一致,数据就在转换中丢失了。

解决:建库建表统一使用utf8mb4;连接URL改成jdbc:mysql://localhost:3306/archives?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai。如果已经建错表,用ALTER TABLE t_archive CONVERT TO CHARACTER SET utf8mb4修复。Swing自身的乱码通常是JVM默认编码问题,在启动参数里加-Dfile.encoding=UTF-8即可。

判断问题出在哪一步,按顺序查:先在Navicat手工插入中文,看数据库本身支不支持;再在Java代码里直接打印SQL,看传给PreparedStatement的中文是否正确;最后看ResultSet读回来的中文是否正常。这三分支能快速定位责任层,避免一上来就改数据库。

5.3 点击“登录”后整个窗口卡死,数据库一慢就假死

现象:登录按钮按下后,窗口鼠标变成忙状态,拖动不了,过一会才恢复。如果数据库连接超时,窗口可能卡到必须强制关闭。

原因:监听器里直接执行了JDBC查询,而JDBC的获取连接和SQL执行是阻塞式IO。EDT线程被阻塞后无法处理重绘和鼠标事件,所以整个窗口看起来死掉了。

解决:最轻量的做法是new Thread(() -> { boolean ok = userService.checkLogin(...); SwingUtilities.invokeLater(() -> { ... }); }).start()。更规范的是使用SwingWorker:

SwingWorker<Boolean, Void> worker = new SwingWorker<>() { @Override protected Boolean doInBackground() { return userService.checkLogin(username, password); } @Override protected void done() { boolean ok = false; try { ok = get(); } catch (Exception ex) { ex.printStackTrace(); } if (ok) { ... } } }; worker.execute();

这里注意:在doInBackground里不能直接触碰JButton或JLabel,需要把结果带回EDT的done方法里再更新界面。我当年就犯过这个错:新线程里调用model.addRow,结果表格刷新时灵时不灵。SwingWorker的好处是回调都落在EDT,不需要你手动invokeLater。

5.4 借阅登记执行到一半失败,第一条SQL却生效了

现象:borrowArchive方法里先insert借阅记录,再update档案状态;第二条SQL因字段长度超限失败,但第一条已经提交,结果查到了借阅记录,档案状态却没变。

原因:JDBC默认自动提交每条SQL,多条SQL之间没有事务边界。这不是“并发导致”,而是最基本的原子性问题。

解决:使用第3.3节的事务写法,在同一个Connection上setAutoCommit(false),两条SQL都成功后再commit;失败则rollback。要让这段代码真正可靠,必须把两个PreparedStatement拿到同一个try块里,不能各取一个连接。如果直接用DAO里的两个方法,各开各的连接,事务根本不成立。

一个有经验的工程师会把这段逻辑收进Service层,并用一个Boolean返回“是否借出成功”给界面。遇到重复借阅同一档案的场景,还需要在SQL层面加条件:UPDATE t_archive SET status=1 WHERE id=? AND status=0,返回值0表示档案已被借出。这比在Java里先查一下再更新更可靠,因为“查”和“更新”之间仍然有竞态窗口。

6. 让项目从“能跑”到“能答辩”:POI导出、连接池与验证清单

课程设计展示只跑通功能是及格分,要拿高分,你得在“项目能跑”之外再提供一两个亮点。最稳妥的加分项是用Apache POI把档案台账导出成Word或Excel,以及用一个清晰的自测清单证明你考虑过边界情况。

6.1 用Apache POI生成Word档案台账,以及图表那个提问怎么答

POI不是JDK自带的,需要在Maven里引入poi-ooxml依赖。这里不贴完整代码,给出能生成Word表格的最小例子:

XWPFDocument doc = new XWPFDocument(); XWPFTable table = doc.createTable(2, 4); table.getRow(0).getCell(0).setText("档案编号"); table.getRow(0).getCell(1).setText("标题"); table.getRow(0).getCell(2).setText("分类"); table.getRow(0).getCell(3).setText("创建时间"); // 第二行填充真实数据 FileOutputStream out = new FileOutputStream("档案台账.docx"); doc.write(out); out.close(); doc.close();

这个例子中,createTable的行数需要预先确定,如果后面要动态追加行,用table.createRow()更灵活。POI对Word里生成图表支持很弱,原生API没有直接生成chart的方法,网上问“java poi word能生成图表吗”的答案通常是“要手动拼DrawingML XML”。所以在答辩时,如果老师问图表,更好的回复是“我选择在Excel中生成图表,或者先用Java绘图生成图片再插入Word”,而不是硬啃XML规范。用POI导出Excel并插入Chart也是可行的,但课程设计时间紧张,做一个Word表格台账足够体现你的工程扩展能力。

6.2 一份答辩前的半小时自测清单

我会在项目交付前按这份清单过一遍:

  • 登录错误密码时,能否弹窗且不退出程序?
  • 档案编号重复时,数据库报的Duplicate Entry错误有没有被转成可读提示?
  • 空数据时档案列表是否显示“暂无数据”,而不是白屏?
  • 删除档案是否有二次确认?多选批量删除是否只删选中的行?
  • 借阅登记后,能不能在档案列表或借阅记录里看到状态变化?
  • 快速点击“保存”按钮,会不会产生重复提交?
  • 导出Word文件后,用WPS打开是否出现编码错乱?
  • 把所有jar打成可执行包,换一台没有JDBC环境的电脑能否运行?

这些点每项都可能成为老师追问“你考虑过XXX吗”的引子。我现在做这类项目,习惯先把异常路径的录屏准备好,再演示正常流程,因为答辩的本质是证明你对失败情况的预判能力。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询