资讯动态

Java实验设计实战:基于JDBC与Socket的小型档案管理系统

发布时间:2026/10/16 1:51:44 来源:尧图企业网站定制
简介这是一份面向高校计算机专业学生的Java综合实验设计资源围绕C/S架构的小型档案管理系统展开适合正在学习Java面向对象、Socket通信与多线程编程的读者用于课程设计或实验参考。压缩包共47个文件约1.37MB包含14个java源文件、15个class编译文件、2个sql建库脚本、1个MySQL驱动jar包以及readme说明和实验指导文档覆盖客户端与服务器端两套工程结构。系统要求用户登录验证并按角色划分权限支持档案录入、条件查询、文件上传下载与删除用户信息与档案属性存入关系数据库档案文件保存在服务器目录通信基于Socket并由服务器端多线程响应多客户端请求。目前已有875人学习下载读者可据此理解C/S分层设计、数据库连接与多线程并发处理的完整实现思路。1. 从一次“实验课翻车”说起为什么档案管理系统值得认真做很多同学拿到“Java实验设计”这个题目时第一反应是去网上找一个现成的增删改查项目改改交差。我带过几届课程设计见过太多这样的作品界面能跑数据库能连但一问“档案的借阅状态怎么流转”“并发修改同一条档案怎么办”“Socket 通信断线了怎么恢复”就答不上来。小型档案管理系统看起来简单实际上它同时踩中了 Java 实验设计里最核心的几个考点JDBC 数据库操作、Swing 或控制台界面、Socket 网络通信、多线程与文件存储。把这几个点串成一个完整闭环比堆十个零散的小实验更能体现工程能力。这套方案面向两类人一是需要交 Java 课程设计或实验报告的学生二是想用一个真实场景练手 JDBC Socket 的初学者。我会按“需求 → 设计 → 实现 → 验收”的顺序把一个小型档案管理系统从零拆到能答辩的程度包括表结构、模块划分、关键代码和常见翻车点。2. 需求分析与技术选型先想清楚档案到底怎么管2.1 功能需求拆解档案管理系统的核心不是“增删改查”四个字而是档案的生命周期。我一般会把需求分成三层第一层是基础数据管理。档案本身包含编号、名称、类别、密级、存放位置、创建时间、状态等字段。用户需要能新增档案、修改档案信息、按条件查询、逻辑删除不是物理删除档案管理里物理删除是禁忌。第二层是借阅流转。档案可以被借出和归还借出时要记录借阅人、借出时间、预计归还时间归还时更新状态。这里涉及状态机在库 → 借出 → 逾期 → 归还。状态流转必须用事务保证否则会出现“档案显示已借出但借阅记录不存在”的脏数据。第三层是操作审计。谁在什么时候对哪条档案做了什么操作必须留痕。这一层很多同学会忽略但恰恰是答辩时老师最爱问的点。2.2 技术选型与理由技术点选型理由开发语言Java 8 或 11课程环境通用语法稳定数据库MySQL 5.7 / 8.0免费、资料多、JDBC 驱动成熟数据访问原生 JDBC PreparedStatement实验设计通常要求手写 SQL不用 ORM 框架界面Swing桌面端或控制台菜单Swing 能体现事件驱动控制台更聚焦逻辑网络通信Socket 多线程实现客户端/服务端分离满足“网络编程”实验要求构建工具纯 javac 或 Maven看课程要求Maven 管理依赖更方便选原生 JDBC 而不是 MyBatis是因为实验设计的评分点往往在“你是否理解连接、语句、结果集的处理流程”。用框架反而掩盖了这些细节。Socket 部分我建议做成 C/S 结构服务端持有数据库连接客户端通过 Socket 发送请求指令服务端处理后再返回结果。这样既练了网络编程又避免了每个客户端直连数据库的安全问题。提示如果课程只要求单机版可以把 Socket 层做成可选模块但建议保留答辩时是加分项。2.3 数据库表设计-- 档案主表 CREATE TABLE archive ( id INT PRIMARY KEY AUTO_INCREMENT, archive_no VARCHAR(32) NOT NULL UNIQUE COMMENT 档案编号, name VARCHAR(128) NOT NULL COMMENT 档案名称, category VARCHAR(32) COMMENT 类别, security_level TINYINT DEFAULT 1 COMMENT 密级 1-公开 2-内部 3-秘密, location VARCHAR(64) COMMENT 存放位置, status TINYINT DEFAULT 0 COMMENT 0-在库 1-借出 2-逾期, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, is_deleted TINYINT DEFAULT 0 COMMENT 逻辑删除标记 ); -- 借阅记录表 CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, archive_id INT NOT NULL, borrower VARCHAR(64) NOT NULL, borrow_time DATETIME NOT NULL, expect_return_time DATETIME, actual_return_time DATETIME, status TINYINT DEFAULT 0 COMMENT 0-借出中 1-已归还, FOREIGN KEY (archive_id) REFERENCES archive(id) ); -- 操作日志表 CREATE TABLE operation_log ( id INT PRIMARY KEY AUTO_INCREMENT, operator VARCHAR(64), action VARCHAR(32), target_id INT, detail VARCHAR(255), op_time DATETIME DEFAULT CURRENT_TIMESTAMP );这三张表是整个系统的骨架。archive 表用 is_deleted 做逻辑删除borrow_record 表通过 archive_id 外键关联operation_log 表记录所有写操作。注意 status 字段的取值要和借阅记录联动这个联动逻辑必须放在 Service 层用事务包起来。3. 总体架构与模块划分三层结构怎么落地3.1 分层设计系统按经典三层架构组织表现层ViewSwing 窗口或控制台菜单负责接收用户输入、展示结果。不写任何 SQL。业务层Service处理借阅规则、状态流转、权限校验。所有事务控制在这一层。数据层DAO封装 JDBC 操作每个方法对应一条或一组 SQL。只做数据存取不做业务判断。包结构建议这样组织com.archive ├── view // 界面类 ├── service // 业务逻辑 ├── dao // 数据访问 ├── model // 实体类 ├── net // Socket 通信 └── util // 数据库连接池、工具类3.2 数据库连接管理不要每次操作都 DriverManager.getConnection那样性能差且容易泄漏。写一个简单的连接工具类public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/archive_db?useSSLfalseserverTimezoneUTC; private static final String USER root; private static final String PASSWORD your_password; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 统一关闭资源避免泄漏 public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (stmt ! null) stmt.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }这段代码的关键点是静态块加载驱动、统一关闭方法。参数说明URL 里的 serverTimezone 必须加否则 MySQL 8 会报时区错误useSSLfalse 在本地实验环境可以关掉。3.3 借阅业务的事务控制借阅操作要同时做两件事更新档案状态为“借出”插入一条借阅记录。这两步必须在一个事务里public boolean borrowArchive(int archiveId, String borrower) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 检查档案是否在库 Archive archive archiveDao.findById(conn, archiveId); if (archive null || archive.getStatus() ! 0) { conn.rollback(); return false; } // 2. 更新档案状态 archiveDao.updateStatus(conn, archiveId, 1); // 3. 插入借阅记录 borrowDao.insert(conn, archiveId, borrower); conn.commit(); return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ignored) {} e.printStackTrace(); return false; } finally { DBUtil.close(conn, null, null); } }注意 DAO 方法都接收 Connection 参数这样才能共用同一个事务。如果每个 DAO 方法自己获取连接事务就失效了。这是 JDBC 事务最常见的翻车点。4. Socket 通信与多线程让客户端和服务端说上话4.1 通信协议设计客户端和服务端之间用简单的文本协议一行一条指令字段用|分隔LOGIN|admin|123456 QUERY|archive_noA001 BORROW|archive_id5|borrower张三 RETURN|archive_id5服务端解析指令后调用对应的 Service 方法返回OK|结果或FAIL|原因。这种设计简单直观调试时用 telnet 就能测。4.2 服务端多线程处理public class ArchiveServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket new ServerSocket(8888); System.out.println(服务端启动监听8888端口); ExecutorService pool Executors.newFixedThreadPool(10); while (true) { Socket socket serverSocket.accept(); pool.execute(new ClientHandler(socket)); } } } class ClientHandler implements Runnable { private Socket socket; public ClientHandler(Socket socket) { this.socket socket; } Override public void run() { try (BufferedReader in new BufferedReader( new InputStreamReader(socket.getInputStream())); PrintWriter out new PrintWriter(socket.getOutputStream(), true)) { String line; while ((line in.readLine()) ! null) { String response CommandParser.parse(line); out.println(response); } } catch (IOException e) { System.out.println(客户端断开 e.getMessage()); } } }线程池用 newFixedThreadPool(10)避免每个连接都新建线程导致资源耗尽。ClientHandler 里用 try-with-resources 自动关闭流。CommandParser 是解析指令的类根据指令前缀分发到不同的 Service 方法。4.3 客户端连接与超时处理public class ArchiveClient { private Socket socket; private PrintWriter out; private BufferedReader in; public boolean connect(String host, int port) { try { socket new Socket(); socket.connect(new InetSocketAddress(host, port), 3000); // 3秒超时 out new PrintWriter(socket.getOutputStream(), true); in new BufferedReader(new InputStreamReader(socket.getInputStream())); return true; } catch (IOException e) { System.out.println(连接服务端失败 e.getMessage()); return false; } } public String sendCommand(String cmd) throws IOException { out.println(cmd); return in.readLine(); // 阻塞等待响应 } }连接超时设 3 秒避免网络不通时界面卡死。sendCommand 是同步阻塞的简单场景够用。如果要做异步可以把读取响应的部分放到单独线程里通过回调更新界面。5. 避坑与常见问题排查5.1 中文乱码现象档案名称存入数据库变成问号或 Socket 传输后中文显示乱码。原因数据库字符集不是 utf8mb4或 Socket 流没有指定编码。解决建库时用CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ciSocket 流用new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)明确指定编码。5.2 事务不生效现象借阅操作中更新状态成功但插入记录失败数据不一致。原因DAO 方法各自获取 Connection不在同一个事务里。解决Service 层获取 Connection 后传给 DAO所有操作共用这个连接最后统一 commit 或 rollback。5.3 资源泄漏现象程序运行一段时间后报“Too many connections”。原因Connection、Statement、ResultSet 没有在 finally 里关闭。解决用 try-with-resources 或在 finally 中调用 DBUtil.close()。养成“谁打开谁关闭”的习惯。5.4 Socket 断线后客户端卡死现象服务端重启后客户端发送指令一直无响应。原因Socket 连接已断开但客户端仍在等待 readLine() 返回。解决设置 socket.setSoTimeout(5000)超时后抛出 SocketTimeoutException捕获后提示用户重连。5.5 并发修改同一条档案现象两个客户端同时借阅同一档案都显示成功。原因检查状态和更新状态之间有间隙没有加锁。解决在 SQL 层面用UPDATE archive SET status1 WHERE id? AND status0根据返回的受影响行数判断是否成功。这是乐观锁的思路比在 Java 里加 synchronized 更可靠。6. 验收与答辩怎么证明你的系统真的能跑验收不是演示一遍增删改查就完事。我一般会按下面这个清单逐项过验收项操作预期结果数据库连接启动服务端控制台输出“服务端启动”无异常新增档案客户端输入完整信息数据库 archive 表新增一条is_deleted0重复编号用相同 archive_no 新增返回失败提示编号已存在借阅档案对在库档案执行借阅archive.status 变为 1borrow_record 新增记录重复借阅对已借出档案再次借阅返回失败提示档案不在库归还档案对借出档案执行归还archive.status 变回 0borrow_record 更新归还时间逻辑删除删除一条档案is_deleted1查询列表不再显示操作日志执行任意写操作operation_log 表有对应记录断线重连关闭服务端再启动客户端提示超时重连后恢复正常并发借阅两个客户端同时借同一档案只有一个成功另一个提示失败答辩时老师最爱问的三个问题提前准备好答案第一“你的事务是怎么控制的”——答 Service 层统一管理 ConnectionDAO 共用。第二“Socket 通信为什么用多线程”——答避免单线程阻塞导致其他客户端无法连接。第三“档案删除为什么不做物理删除”——答档案管理需要留痕和可追溯逻辑删除保留数据完整性。最后说一个我自己的习惯每次提交前我都会把数据库清空从建表脚本开始重新跑一遍完整流程。因为开发过程中手动改过数据很容易掩盖初始化脚本的 bug。从那以后我每次交课程设计都强制走一遍“清库 → 建表 → 启动 → 全流程操作 → 检查日志”这个习惯帮我省了至少三次答辩现场翻车。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑