恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Java课程设计实战:基于Socket与JDBC的小型档案管理系统源码解析
首页
资讯中心
/
Java课程设计实战:基于Socket与JDBC的小型档案管理系统源码解析
Java课程设计实战:基于Socket与JDBC的小型档案管理系统源码解析
发布时间:2026/10/16 1:52:12
简介这是一份面向高校Java课程实验与课程设计的完整源码方案围绕C/S架构实现小型档案管理系统适合正在学习Java面向对象、Socket通信与多线程编程的学生参考。系统包含客户端与服务器端两个子系统支持用户登录验证、角色权限区分、档案文件上传下载、条件查询与用户信息管理服务器端采用多线程同时响应多个客户端请求数据通过关系数据库存储档案文件则保存在服务器目录中。压缩包共47个文件约1.37MB以java源码与class编译文件为主另含sql建库脚本、txt说明文档、jar依赖包及docx实验指导书结构清晰便于对照学习。目前已有875人学习下载。读者可从中获取完整的实验实现思路、客户端与服务器端通信代码、数据库连接与多线程处理范例以及档案上传下载、权限控制等核心模块的参考写法适合作为课程实验提交或二次开发的基础。1. 从一次课程设计翻车说起小型档案管理系统到底要解决什么带过几届课程设计最常看到的场景是选题定了“档案管理系统”学生上来就建三张表、拖两个窗体结果中期检查被问“档案的借阅状态怎么流转”“并发操作同一份档案怎么保证不冲突”当场卡壳。问题不在编码能力在于一开始没把需求边界划清楚。这套 Java 实验设计要做的是一个能跑通“录入—归档—检索—借阅—归还”完整闭环的小型档案管理系统用 Java 源码实现数据落 SQL 数据库客户端与服务端之间走 Socket 通信。它适合两类人正在做 Java 课程设计、需要一份结构完整可参考方案的学生以及想用一个小型项目把 Java 网络编程、JDBC、多线程串起来的初学者。核心不是功能多而是每个环节都能讲清楚为什么这么设计。2. 需求拆解与技术选型为什么是 Java SQL Socket 这套组合2.1 功能需求与非功能需求的分界档案管理系统的需求通常分两层。功能层面至少要覆盖档案的增删改查、按条件检索、借阅与归还状态流转、用户登录与权限区分。非功能层面课程设计里最容易被忽略但答辩必问的是多客户端同时操作时数据不能错乱、服务端不能因为一个客户端卡死而整体挂掉、数据库连接不能每次操作都重新建立。我一般会把需求写成一张表左边是功能点右边是对应的技术约束。这样做的目的是让后续每个模块都能追溯到具体需求而不是凭感觉堆代码。需求编号功能描述技术约束F1用户登录与角色区分密码加密存储角色字段控制操作权限F2档案录入与修改字段校验编号唯一性约束F3多条件组合检索SQL 动态拼接防止注入F4借阅与归还状态流转状态机控制禁止非法跳转F5多客户端并发访问Socket 多线程处理连接池管理F6操作日志记录关键操作落库便于追溯这张表看起来简单但它决定了后面所有模块的边界。比如 F4 的状态流转如果不在需求阶段定清楚“已借出”不能直接改成“已归档”后面代码里就会到处补 if-else越写越乱。2.2 技术选型为什么不用 Web 框架很多人第一反应是 Spring Boot 前端页面但对于“Java 实验设计”这个定位Web 框架反而会掩盖掉最该练的东西。Socket 编程、多线程、JDBC 原生操作这些才是实验设计要考察的核心能力。用 Web 框架请求响应由容器管数据库由 ORM 管学生写完也不知道底层发生了什么。所以这套方案的技术栈定得很明确JDK 8 及以上Swing 或 JavaFX 做客户端界面ServerSocket 做服务端监听每个客户端连接开一个独立线程处理MySQL 或 PostgreSQL 做数据存储JDBC 直连配合手写连接池。这套组合的好处是每一层都看得见摸得着调试的时候能清楚知道问题出在网络层、业务层还是数据层。提示如果课程允许数据库选 MySQL 5.7 或 8.0 都行但驱动包版本要和数据库版本匹配否则会报时区或 SSL 相关的连接错误。2.3 通信协议的设计自定义消息格式Socket 通信最怕的是“粘包”和“半包”。TCP 是字节流不保证一次 send 对应一次 receive。我一般会在应用层定义一个简单的消息格式前 4 个字节表示消息体长度后面跟 JSON 或分隔符拼接的字符串。这样服务端先读长度再按长度读满消息体就不会出现解析错位。// 消息编码长度前缀 内容 public static byte[] encode(String message) { byte[] body message.getBytes(StandardCharsets.UTF_8); ByteBuffer buffer ByteBuffer.allocate(4 body.length); buffer.putInt(body.length); // 前4字节写长度 buffer.put(body); // 后面写实际内容 return buffer.array(); } // 消息解码先读长度再读内容 public static String decode(InputStream in) throws IOException { DataInputStream dis new DataInputStream(in); int length dis.readInt(); // 阻塞读满4字节 byte[] body new byte[length]; dis.readFully(body); // 阻塞读满length字节 return new String(body, StandardCharsets.UTF_8); }这段代码的关键在于readFully它会一直阻塞直到读满指定字节数避免了read返回 -1 或读一半的情况。长度前缀用putInt写入占 4 字节能表示最大 2GB 的消息体对档案管理系统来说绰绰有余。实际项目中消息体里再放一个type字段区分是登录请求、查询请求还是借阅请求服务端根据 type 分发到不同的处理逻辑。3. 总体架构与数据库设计三层结构与五张核心表3.1 分层架构客户端、服务端、数据层的职责划分整个系统按三层来组织。客户端层负责界面展示和用户交互把用户操作封装成请求消息发给服务端服务端层负责业务逻辑接收请求、校验权限、操作数据库、返回结果数据层就是数据库本身负责持久化存储。服务端内部再细分网络监听线程只负责 accept 新连接每个连接交给一个独立的 ClientHandler 线程ClientHandler 解析消息后调用对应的 Service 方法Service 层做业务校验和事务控制通过 DAO 层访问数据库。这样分的好处是网络层改协议不影响业务层业务层换数据库不影响网络层。// 服务端主循环监听端口为每个连接开线程 public class ArchiveServer { private static final int PORT 8888; private final ExecutorService pool Executors.newFixedThreadPool(20); public void start() throws IOException { ServerSocket serverSocket new ServerSocket(PORT); System.out.println(服务端启动监听端口 PORT); while (true) { Socket client serverSocket.accept(); // 阻塞等待连接 pool.submit(new ClientHandler(client)); // 交给线程池处理 } } }这里用线程池而不是每个连接 new Thread是为了控制并发数量。课程设计里客户端不会太多20 个线程足够。如果直接用 new Thread连接数一多线程创建销毁的开销会很明显而且不好管理。3.2 数据库表设计五张表撑起核心业务数据库设计遵循第三范式但不过度拆分。核心表五张用户表、档案表、借阅记录表、操作日志表、分类表。-- 用户表存储登录信息和角色 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(128) NOT NULL, -- 存加密后的密码 role VARCHAR(20) NOT NULL DEFAULT USER, -- ADMIN / USER create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 档案表核心业务数据 CREATE TABLE t_archive ( id INT PRIMARY KEY AUTO_INCREMENT, archive_no VARCHAR(50) NOT NULL UNIQUE, -- 档案编号业务唯一键 title VARCHAR(200) NOT NULL, category_id INT, status VARCHAR(20) NOT NULL DEFAULT IN_STORE, -- IN_STORE / BORROWED / ARCHIVED create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (category_id) REFERENCES t_category(id) ); -- 借阅记录表记录每一次借还 CREATE TABLE t_borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, archive_id INT NOT NULL, user_id INT NOT NULL, borrow_time DATETIME NOT NULL, return_time DATETIME, status VARCHAR(20) NOT NULL DEFAULT BORROWED, -- BORROWED / RETURNED FOREIGN KEY (archive_id) REFERENCES t_archive(id), FOREIGN KEY (user_id) REFERENCES t_user(id) );档案表里的status字段是状态机的核心。IN_STORE表示在库BORROWED表示已借出ARCHIVED表示已归档不再流通。借阅操作只能把IN_STORE改成BORROWED归还操作只能把BORROWED改成IN_STORE其他跳转一律拒绝。这个约束放在 Service 层做不要只靠前端控制。借阅记录表单独建而不是在档案表里加个borrower_id字段是因为一份档案会被多次借阅每次借还都要留痕。这是课程设计里很容易被忽略的点答辩时老师一问“借阅历史怎么查”就能区分出设计深度。3.3 连接池与事务两个容易被忽视的细节JDBC 直连如果不做连接池每次操作都DriverManager.getConnection频繁创建销毁连接在并发场景下性能很差。课程设计里可以手写一个简单的连接池用BlockingQueue存连接取的时候 poll用完 offer 回去。// 简易连接池初始化时创建固定数量连接 public class SimplePool { private final BlockingQueueConnection pool; public SimplePool(int size, String url, String user, String pwd) throws SQLException { pool new LinkedBlockingQueue(size); for (int i 0; i size; i) { pool.offer(DriverManager.getConnection(url, user, pwd)); } } public Connection borrow() throws InterruptedException { return pool.poll(3, TimeUnit.SECONDS); // 最多等3秒 } public void giveBack(Connection conn) { if (conn ! null) pool.offer(conn); } }事务控制方面借阅操作涉及两张表的更新档案表状态改为BORROWED借阅记录表插入一条新记录。这两步必须在一个事务里要么都成功要么都回滚。我一般会在 Service 层手动控制setAutoCommit(false)执行完两个 DAO 操作后commitcatch 到异常就rollback。4. 核心模块实现登录鉴权、档案检索与借阅状态流转4.1 登录鉴权密码加密与角色校验登录流程是客户端发送用户名和密码服务端查库比对。密码不能明文存用 SHA-256 加盐或者 BCrypt 都行。课程设计里用 SHA-256 加固定盐就够了重点是体现“不存明文”这个意识。// 密码加密SHA-256 盐值 public static String hashPassword(String raw, String salt) { try { MessageDigest md MessageDigest.getInstance(SHA-256); md.update(salt.getBytes(StandardCharsets.UTF_8)); byte[] digest md.digest(raw.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(digest); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(加密算法不可用, e); } }登录成功后服务端返回一个 token 或者直接在会话里标记用户角色。后续每个请求都带上这个标识服务端在处理前先校验权限。比如删除档案只有ADMIN角色能操作普通用户只能查询和借阅。这个校验放在 ClientHandler 分发请求之前统一做不要散落在每个 Service 方法里。4.2 多条件检索动态 SQL 与防注入档案检索通常支持按编号、标题、分类、状态组合查询。动态拼接 SQL 的时候千万不要用字符串拼接把用户输入直接塞进去必须用PreparedStatement的占位符。// 动态条件查询用List收集条件用占位符传参 public ListArchive search(String no, String title, Integer categoryId, String status) { StringBuilder sql new StringBuilder(SELECT * FROM t_archive WHERE 11); ListObject params new ArrayList(); if (no ! null !no.isEmpty()) { sql.append( AND archive_no LIKE ?); params.add(% no %); } if (title ! null !title.isEmpty()) { sql.append( AND title LIKE ?); params.add(% title %); } if (categoryId ! null) { sql.append( AND category_id ?); params.add(categoryId); } if (status ! null !status.isEmpty()) { sql.append( AND status ?); params.add(status); } // 执行查询params按顺序填充占位符 return jdbcTemplate.query(sql.toString(), params.toArray(), Archive.class); }WHERE 11是个小技巧让后续每个条件都能以AND开头不用判断是不是第一个条件。LIKE查询的%拼在参数值里而不是拼在 SQL 字符串里这样PreparedStatement才能正确转义。如果直接把用户输入拼进 SQL一个 OR 11就能把全表拉出来这是课程设计答辩时老师最爱问的安全点。4.3 借阅状态流转状态机与并发控制借阅操作的核心是状态校验。Service 层先查当前档案状态如果是IN_STORE才允许借阅改成BORROWED并插入借阅记录如果是BORROWED则拒绝提示“该档案已借出”。并发场景下两个客户端同时借同一份档案如果只是先查后改会出现都查到IN_STORE然后都改成BORROWED的情况。解决办法是用数据库的行锁在查询时加FOR UPDATE或者用乐观锁版本号。// 借阅操作事务 行锁保证并发安全 public boolean borrow(int archiveId, int userId) { Connection conn null; try { conn pool.borrow(); conn.setAutoCommit(false); // 加行锁查询当前状态 String checkSql SELECT status FROM t_archive WHERE id ? FOR UPDATE; String status queryStatus(conn, checkSql, archiveId); if (!IN_STORE.equals(status)) { conn.rollback(); return false; // 状态不允许借阅 } // 更新档案状态 updateStatus(conn, UPDATE t_archive SET statusBORROWED WHERE id?, archiveId); // 插入借阅记录 insertRecord(conn, archiveId, userId); conn.commit(); return true; } catch (Exception e) { rollbackQuietly(conn); return false; } finally { pool.giveBack(conn); } }FOR UPDATE会在查询时锁住这一行另一个事务的相同查询会阻塞直到第一个事务提交或回滚。这样就能保证同一份档案不会被同时借出两次。这个点在课程设计里属于加分项能讲清楚为什么需要行锁比单纯说“用了事务”更有说服力。5. 避坑与常见问题排查那些年踩过的 Socket 和 JDBC 的坑5.1 客户端断开后服务端线程不释放现象客户端关闭后服务端对应的 ClientHandler 线程还在跑连接数一多线程池就被占满。原因read方法在客户端断开时返回 -1但代码里没有处理这个返回值循环继续执行导致空转。解决在读取循环里判断read返回值如果是 -1 就跳出循环关闭 Socket 和流线程自然结束。同时给 Socket 设置setSoTimeout避免永久阻塞。5.2 数据库连接用完没归还导致连接池耗尽现象系统跑一段时间后所有数据库操作都超时日志显示连接池为空。原因DAO 方法里borrow了连接但异常路径下没有giveBack连接泄漏。解决把giveBack放在finally块里确保无论成功失败都归还。更好的做法是用 try-with-resources 包装连接但连接池场景下手动管理更直观。5.3 中文乱码客户端与服务端编码不一致现象档案标题里的中文传到服务端变成问号或乱码。原因Socket 流默认用平台编码客户端和服务端环境不同就会乱。解决两端统一用UTF-8在getBytes和new String时显式指定字符集。数据库连接 URL 里也加上useUnicodetruecharacterEncodingutf8。5.4 事务未提交导致数据“消失”现象借阅操作返回成功但数据库里查不到借阅记录。原因setAutoCommit(false)之后忘记commit连接归还时默认回滚。解决在 Service 方法的正常路径末尾显式commit异常路径rollback。可以在代码审查时专门检查每个setAutoCommit(false)后面有没有对应的提交或回滚。5.5 界面卡死在事件线程里做网络请求现象点击查询按钮后界面无响应直到服务端返回才恢复。原因Swing 的事件分发线程里直接调用了阻塞的 Socket 读写。解决把网络请求放到独立线程里执行结果通过SwingUtilities.invokeLater回传到界面线程更新。这是桌面客户端开发的基本功但课程设计里经常被忽略。6. 验收与答辩怎么证明这套系统真的能跑验收不是把功能点一遍就完事。我一般会按三个层次来准备功能演示、异常场景、设计追问。功能演示走正常流程登录、录入档案、检索、借阅、归还、查借阅记录。每一步都要能看到数据库里的变化最好开着数据库客户端同步展示。异常场景是拉开差距的地方借已借出的档案、用普通用户删档案、客户端中途断线再重连、两个客户端同时借同一份档案。这些场景能跑通说明状态校验和并发控制是真的做了不是摆设。答辩时老师常问的几个问题提前准备好答案为什么用 Socket 不用 HTTP、连接池怎么实现的、事务边界在哪里、密码怎么存的、并发借阅怎么防冲突。每个问题都能对应到代码里的具体位置打开 IDE 直接指给老师看。验收项操作步骤预期结果登录鉴权错误密码登录提示失败不进入主界面权限控制普通用户删除档案提示无权限数据库无变化状态流转借阅已借出档案提示已借出状态不变并发安全两客户端同时借同一档案只有一个成功另一个提示已借出断线重连客户端强制关闭后重开服务端线程释放新连接正常数据一致性借阅后查借阅记录记录存在且档案状态为已借出从那以后我每次带课程设计都要求学生先把这张验收表填满再写代码功能可以少做但表里每一项必须能演示。希望帮到你。本文还有配套的精品资源点击获取