Java课程设计实战:基于Socket与JDBC的档案管理系统解析

发布时间:2026/10/21 8:26:17
Java课程设计实战:基于Socket与JDBC的档案管理系统解析 简介一份面向Java课程设计与综合实验的小型档案管理系统源码包基于C/S架构客户端与服务器端通过Socket通信服务器采用多线程并发处理请求用户信息与档案属性存放于关系数据库档案文件保存在服务器目录中。系统内置三类角色覆盖登录验证、个人资料查询修改、用户增删改权限管理、档案录入上传、条件查询与下载保存等功能模块适合学习Java网络编程、多线程、JDBC整合及GUI开发的读者参考实践。资源共47个文件以14个Java源码和15个class文件为主辅以2个SQL建表脚本、8个txt说明文件、MySQL驱动jar包及docx实验指导书压缩包仅1.37MB导入Eclipse即可查看运行结构。该资源已有875人学习结合源码、SQL脚本和说明文档可快速理解C/S程序整体设计掌握Socket多线程通信、权限控制和文件传输的具体实现对档案管理类课程设计或综合实验有直接参考价值。1. 一个小型档案管理系统课程设计里最容易被追问的那一个某开发者当年交课程设计选了这个题目。理由很简单——别人都在做图书管理、学生信息管理界面打开就是增删改查考官扫一眼就过。这个项目把 Socket 通信引进来客户端和服务端分离数据通过自定义协议走 TCP 传输底层用 MySQL 存储已经是“联机系统”的样子。答辩时考官问的第一个问题是“你的数据库在哪一层”这正好是整套系统的亮点通道是网络存储是 SQL界面只是一个收发请求的壳。这份资源适合两类人一类是做 Java 实验设计需要完整可运行项目的人另一类是正在学 Socket 和 JDBC、想把知识点串成一个整体的人。它能让你看到一个小型联机系统该有的骨架网络层、协议层、持久层以及它们是怎么粘在一起的。2. 整体架构与模块拆分Socket 是主轴JDBC 是底座2.1 为什么选 C/S 结构而不是纯单机程序档案管理系统常见做法是做单机版界面直接连数据库。这套项目的设计者刻意选了 C/S 结构服务端持有数据库连接客户端只做界面和交互。原因很直接实验设计的要求里同时出现 java、sql、socket 三个关键词意味着不能只做一个本地增删改查。把 Socket 加进来以后系统被拆成三个进程域客户端进程捕获用户操作把动作编码成请求文本发出服务端进程监听端口、分派线程处理请求、解析指令、访问数据库数据库进程负责实际存储。这样的分工让每一层的职责都很简单。因为这个设计答辩时能讲的东西也多了很多网络超时、并发连接、数据一致性都能展开。从这里能看出这套项目作为实验的教学意图不追求复杂框架而是逼着你把 Java 的 IO、线程、集合、JDBC 全部用一遍同时还要理解数据是怎么跨进程流动的。相比单机版程序这份代码多了一层网络通信跑通它需要理解的东西就更完整这也是它值得下载下来反复拆的原因。2.2 目录结构与职责边界整个项目按标准 Maven 工程组织常见划分如下archive-system/ ├── pom.xml # 依赖mysql-connector-j、junit ├── src/main/java/ │ └── com.demo.archive/ │ ├── client/ │ │ ├── ClientMain.java # 客户端启动入口 │ │ ├── ConsoleUI.java # 控制台交互界面 │ │ └── RequestSender.java # 封装 Socket 收发 │ ├── server/ │ │ ├── ServerMain.java # 服务端启动入口 │ │ ├── ClientHandler.java # 单连接处理线程 │ │ └── RequestRouter.java # 指令路由匹配到具体业务方法 │ ├── common/ │ │ ├── Protocol.java # 协议常量与报文格式定义 │ │ └── JsonUtil.java # 轻量 JSON 封装/解析 │ ├── dao/ │ │ ├── DBConnection.java # JDBC 连接管理 │ │ ├── ArchiveDao.java # 档案 CRUD │ │ └── UserDao.java # 登录账户校验 │ └── model/ │ ├── Archive.java # 档案实体 │ └── User.java # 用户实体 ├── src/main/resources/ │ └── db.properties # 数据库连接配置 └── sql/ └── init.sql # 建库建表脚本这个目录结构看起来行数多实际上每个类的代码量都控制在百行以内适合做实验教学。比较值得学习的一点是 common 包单独存在协议常量放在这里客户端和服务端同时引用不会出现两端各有一套格式定义最后对不上的翻车现场。dao 层与 server 层解耦意味着如果某天要换连接池或换 ORM改动范围被限制在 dao 包内这是刻意这么设计的。model 包里的实体类只存字段和 getter/setter不掺杂任何业务逻辑保持干净。2.3 通信协议一行文本搞定指令分发没有用 RMI也没有用序列化传输对象这套系统用的是最直接的文本协议。客户端发送一行请求文本服务端按固定分隔符拆解。协议格式如下操作类型|参数1|参数2|参数3举几个实际报文例子LOGIN|admin|123456 ADD|2024001|张三|1985-06-12|研发部|高级工程师 QUERY|姓名|张三操作类型有 LOGIN、ADD、DELETE、UPDATE、QUERY、GET_ALL、LOGOUT。参数之间用竖线分隔整个报文以换行符结尾。这样设计的理由是调试成本极低在控制台里就能模拟客户端直接用telnet 127.0.0.1 8888发文本测试不用每次启动整个客户端。Protocol.java 里保存了这些常量public final class Protocol { public static final String LOGIN LOGIN; public static final String ADD ADD; public static final String DELETE DELETE; public static final String UPDATE UPDATE; public static final String QUERY QUERY; public static final String GET_ALL GET_ALL; public static final String LOGOUT LOGOUT; public static final String SEPARATOR \\|; public static final String OK OK; public static final String FAIL FAIL; public static final int SERVER_PORT 8888; }这里有三个参数值得记一下SEPARATOR 是竖线在正则里的转义写法初学者经常忘了写双反斜杠导致 split 失效这个坑后面章节专门说SERVER_PORT 是服务端监听端口OK 和 FAIL 是响应状态前缀客户端收到响应后根据前缀决定往控制台打成功还是失败信息。响应报文也走同一行文本格式比如OK|2024001|张三|1985-06-12|研发部|高级工程师。这个设计让协议层保持对称没有为请求和响应写两套解析代码客户端把响应也按竖线拆开就行维护起来少一半工作量。3. 数据库设计与 JDBC 层档案数据落地的第一步3.1 建库建表脚本与字段选择数据库是整套系统的底座。init.sql 里先建库再建表脚本开头假设已经装好 MySQL 并配置了可登录的账号。建表语句如下CREATE DATABASE IF NOT EXISTS archive_db DEFAULT CHARACTER SET utf8mb4; USE archive_db; CREATE TABLE IF NOT EXISTS t_user ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE IF NOT EXISTS t_archive ( id INT AUTO_INCREMENT PRIMARY KEY, archive_no VARCHAR(32) NOT NULL UNIQUE COMMENT 档案编号, name VARCHAR(64) NOT NULL COMMENT 姓名, birth_date DATE COMMENT 出生日期, dept VARCHAR(64) COMMENT 所属部门, position VARCHAR(64) COMMENT 职位, status TINYINT DEFAULT 1 COMMENT 1在职 0离职, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段选择的逻辑是这样的archive_no 作为档案的业务主键加 UNIQUE 约束防止同一编号被录入两次status 字段故意取 TINYINT 而不是字符串只表示在职/离职两种状态用数字表达在 SQL 过滤时更快。birth_date 用 DATE 类型客户端传字符串JDBC 层面做类型转换避免存成字符串导致后续无法按日期段筛选。注意两个细节一是所有表都显式指定 utf8mb4否则中文姓名和部门名称很容易出现乱码二是密码字段用 VARCHAR(64)这个长度是留作 MD5 或 SHA-256 摘要用的后期做加密改造时不需要改表结构。3.2 JDBC 连接管理与配置分离DAO 层的第一步是拿到数据库连接。db.properties 里的配置长这样db.drivercom.mysql.cj.jdbc.Driver db.urljdbc:mysql://localhost:3306/archive_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai db.usernameroot db.password123456DBConnection 类负责读取配置并创建连接。这是实验项目不引入连接池依赖保持简单public class DBConnection { private static String url; private static String user; private static String password; static { try (InputStream in DBConnection.class .getClassLoader().getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(in); url props.getProperty(db.url); user props.getProperty(db.username); password props.getProperty(db.password); Class.forName(props.getProperty(db.driver)); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } }static 块在类加载时执行一次读取配置、注册驱动后续所有请求都复用同一组配置。URL 里serverTimezoneAsia/Shanghai一定不能省新版 MySQL 驱动不写时区直接报错。useUnicodetruecharacterEncodingutf8是处理中文乱码的关键参数从连接侧解决编码问题比在代码里到处转码靠谱得多。每次操作都新建连接、用完关闭虽然比连接池低效但实验场景完全够用也让连接的生命周期一目了然。3.3 ArchiveDao 的 CRUD 实现从一行报文到一行记录档案的增删改查集中在 ArchiveDao 里。以新增档案为例核心代码如下public boolean add(Archive archive) { String sql INSERT INTO t_archive (archive_no, name, birth_date, dept, position, status) VALUES (?, ?, ?, ?, ?, ?); try (Connection conn DBConnection.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, archive.getArchiveNo()); ps.setString(2, archive.getName()); ps.setDate(3, archive.getBirthDate() null ? null : new java.sql.Date(archive.getBirthDate().getTime())); ps.setString(4, archive.getDept()); ps.setString(5, archive.getPosition()); ps.setInt(6, archive.getStatus()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }这里必须用 PreparedStatement 而不是字符串拼接 SQL原因有两个一是防 SQL 注入用户通过客户端输入的姓名或者部门名称里如果带单引号直接拼接会语法错误甚至被注入二是 PreparedStatement 有预编译机制同一条 SQL 反复执行时性能更好。setDate 这里有个易错点实体里如果用 java.util.Date传给 JDBC 时必须转成 java.sql.Date否则编译报错这是 Java 两套日期类型长期共存带来的历史包袱。查询方法返回 ListArchive结果集按列名取值后放进实体。模糊查询用的是name LIKE ?配合%通配符拼接同样走占位符。查询结果不对时最有效的排查方式是打印 SQL 和参数不要猜。4. Socket 通信与多线程一次请求从点击到入库的完整链路4.1 服务端主线程监听工作线程处理ServerMain 的启动逻辑是典型的一线程监听、多线程处理模式public class ServerMain { public static void main(String[] args) throws IOException { try (ServerSocket server new ServerSocket(Protocol.SERVER_PORT)) { System.out.println(服务器已启动端口 Protocol.SERVER_PORT); ExecutorService pool Executors.newFixedThreadPool(8); while (true) { Socket socket server.accept(); pool.execute(new ClientHandler(socket)); } } } }main 线程只做一件事accept 等待新连接。每来一个连接丢给固定线程池去跑 ClientHandler主线程立刻回到 accept 继续监听。线程数设为 8 是按实验场景估的档案管理系统的并发量不会很高8 个线程足够应付几十台客户端同时访问如果调成无界缓存线程池高并发下会创建大量线程把 CPU 打满这个尺度要克制。try-with-resources 保证了 ServerSocket 随程序退出自动关闭避免端口残留。调试时反复重启服务端最常遇到的Address already in use就是因为上一次进程没退干净第 5 章会细说。4.2 ClientHandler读取、解析、路由、回写ClientHandler 是单个连接的生命周期管理类。做的事情按顺序是读取客户端那一行文本、解析出操作类型、交给 RequestRouter 分发、把执行结果拼成响应文本写回public 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(), UTF-8)); PrintWriter out new PrintWriter( new OutputStreamWriter(socket.getOutputStream(), UTF-8), true)) { String line; while ((line in.readLine()) ! null) { String[] parts line.split(Protocol.SEPARATOR); String action parts[0]; String result RequestRouter.route(action, parts); out.println(result); } } catch (IOException e) { e.printStackTrace(); } } }两个关键参数说明BufferedReader 的 readLine 按换行符截断所以客户端发送报文时末尾必须带换行PrintWriter 的第二个参数 autoFlush 设为 true表示每次 println 自动冲刷缓冲区否则响应会卡在缓冲区里出不来表现为客户端一直等不到结果。编码方面输入输出流全部显式指定 UTF-8否则在 Windows 中文环境下默认 GBK 会和服务端的 UTF-8 互相乱码。异常处理故意只打日志不断开连接因为一条报文解析错了不应该把整个连接炸掉。循环里每次读一行就处理一行客户端关闭后 readLine 返回 null循环自然退出坏连接的资源被自动释放。4.3 RequestRouter把动作映射到 DAO路由层的设计目标是避免 if-else 地狱虽然实验项目规模小但用 switch 已经比长串 if 清晰很多public class RequestRouter { public static String route(String action, String[] parts) { switch (action) { case Protocol.LOGIN: return UserDao.login(parts[1], parts[2]) ? Protocol.OK |登录成功 : Protocol.FAIL |用户名或密码错误; case Protocol.ADD: Archive archive new Archive(); archive.setArchiveNo(parts[1]); archive.setName(parts[2]); archive.setBirthDate(parseDate(parts[3])); archive.setDept(parts[4]); archive.setPosition(parts[5]); archive.setStatus(1); return ArchiveDao.add(archive) ? Protocol.OK |添加成功 : Protocol.FAIL |添加失败; case Protocol.QUERY: ListArchive list ArchiveDao.query(parts[2], parts[3]); return buildListResult(list); case Protocol.DELETE: return ArchiveDao.deleteByNo(parts[1]) ? Protocol.OK |删除成功 : Protocol.FAIL |档案不存在; default: return Protocol.FAIL |未知指令; } } }路由层不碰数据库只做参数提取和结果包装。这个分层很重要DAO 层不出现 Socket 相关类服务端层不出现 JDBC 代码。改动要么改路由要么改 DAO互不牵连。parts 下标映射要对照协议文档比如 ADD 协议里 parts[1] 是档案编号、parts[2] 是姓名协议升级时这里和客户端要同步改。common 包里放常量而不是散落字符串就是给大家一个相对唯一的改点。4.4 客户端一个简单的控制台界面封装客户端是纯交互层。RequestSender 封装了 Socket 收发细节ConsoleUI 只负责打印菜单和收集输入public class RequestSender { private Socket socket; private BufferedReader in; private PrintWriter out; public RequestSender(String host, int port) throws IOException { socket new Socket(host, port); in new BufferedReader(new InputStreamReader( socket.getInputStream(), UTF-8)); out new PrintWriter(new OutputStreamWriter( socket.getOutputStream(), UTF-8), true); } public String send(String message) throws IOException { out.println(message); return in.readLine(); } }send 方法两行完成一次完整请求发出去一行等回来一行。这是同步阻塞模型的直观体现简单但可靠。如果要改成异步就要引入多线程和回调实验范围就不太合适了。ConsoleUI 里按数字菜单选择操作然后拼报文调 RequestSender.send把返回结果原样打印。整个客户端不依赖任何数据库类正好验证职责分离客户端是哑终端所有业务逻辑都在服务端。5. 运行步骤与避坑指南照着跑通以及那些容易翻车的细节5.1 环境准备与三步启动运行这个项目需要本机有 JDK 8 以上、MySQL 5.7 或 8.0以及一个能跑 Maven 或直接把 src 丢进 IDE 的 Java 环境。具体步骤如下第一步初始化数据库。用命令行或数据库管理工具执行 sql 目录下的 init.sql建库建表。完成后确认 archive_db 里能看到 t_user 和 t_archive 两张表。第二步改配置。把 src/main/resources/db.properties 里的用户名密码改成你本机的 MySQL 账号url 里如果是远程数据库就换掉 localhost。这里最容易漏的是密码里有特殊字符时直接在 properties 里写会解析失败需要转义或用 IDE 的 properties 编辑器处理。第三步启动服务端再启动客户端。先运行 ServerMain控制台打印“服务器已启动”后再用客户端工具或 telnet 测试连通性。telnet 测试命令是telnet 127.0.0.1 8888连接上后手动输入GET_ALL|如果返回 OK 开头的一行记录说明整条链路通了。5.2 常见问题排查现象、原因、解法第 1 条登录信息正常但档案列表里的中文显示成问号。现象是所有中文内容变成乱码英文正常。原因一半是 db.properties 的 url 缺少 characterEncoding 参数另一半是客户端发送时没有指定 UTF-8Windows 中文环境默认 GBK服务端用 UTF-8 去读自然错位。解决确认 url 带characterEncodingutf8确认 ClientHandler 和 RequestSender 的 InputStreamReader 都显式传了UTF-8不要依赖运行环境默认编码。第 2 条反复重启服务端时报Address already in use。现象是第二次启动 ServerMain 直接抛 BindException 退出。原因很直接上一次进程没有退出端口仍被占用或者 IDE 的停止按钮只是断开了控制台而不是杀了进程。解决命令行执行netstat -ano | findstr 8888查 PID在任务管理器结束对应进程。血泪经验是改代码前先确认端口真的释放了不然改半天重启还是同样报错。第 3 条执行 QUERY 时报 Unknown column 或列名不存在的 SQLException。现象是编译无错一运行到数据库操作就抛异常。原因大多是表和实体字段没有一一对应或者 init.sql 执行不干净表结构是旧版本残留。解决先检查 t_archive 的表结构再对照 Archive 实体和 ArchiveDao 里 SQL 的列名亲手建表最保险。不要用之前实验留下的旧库把 archive_db drop 掉重新执行 init.sql能省掉大半玄学。第 4 条客户端发送报文后在服务端 split 越界 ArrayIndexOutOfBoundsException。现象是某条指令一执行就崩其他指令正常。原因是手动拼报文时少传了参数分隔符个数不够比如 QUERY 协议要求QUERY|字段|值三段但只给了两段。解决在 RequestRouter 入口加参数个数校验不合法就返回 FAIL 并提示格式而不是继续执行。我当时在这上面翻过车后来在 Protocol 里加了格式错误常量专门表示报文格式不对客户端处理起来更明确。第 5 条客户端连接超时 Connection refused。现象是客户端启动后一直在等最后抛连接被拒。原因大概率是服务端没起或者端口写错。解决先确认 ServerMain 控制台出现启动提示再确认客户端代码里的端口和 Protocol.SERVER_PORT 一致。这属于低级失误但遇到时要优先怀疑端口而不是代码逻辑。6. 二次开发方向让这个实验项目变成拿得出手的作品6.1 四个低成本高回报的改造点这套系统跑通以后再往上走的路很清晰我建议按以下顺序做改造。第一个改造点是给密码加盐哈希。现在 UserDao 登录把明文密码直接比较这在实验里没毛病但如果想写进简历第一步就是把明文换成加盐的 SHA-256。做法是在 t_user 表加一个 salt 字段密码存入时存储 salt 和saltpassword的哈希值登录时先按用户名查 salt再重新计算哈希比对。工作量小却能体现安全意识是最推荐先做的。第二个改造点是引入日志组件替换掉满屏的 printStackTrace。用 java.util.logging 或 log4j2 都可以把服务端每次收到的报文、路由结果、SQL 异常记录到文件。你会发现排查效率翻倍尤其是多客户端同时访问时的并发问题。第三个改造点是给 DAO 层做批量操作。现在每来一条 ADD 就 insert 一次档案数量大时可以先在客户端收集 N 条一次性提交DAO 层用 addBatch 和 executeBatch 提升性能。注意点不在代码而在协议设计批量协议需要增加一个条数参数让服务端知道读几组数据。第四个改造点是界面升级。控制台换成 JavaFX 或 Swing只是把报文拼接方式换个壳协议和 DAO 层一行都不用动这正好检验当初分层的合理性。如果换界面要改服务端代码说明分层没做好。6.2 改造过程中的验证方法每次改完一个点先执行一次冒烟测试建库、改配置、起服务端、起客户端或用 telnet跑一遍登录和增删改查的完整流程再用GET_ALL|拉一条数据起来看是否正常。如果改造破坏了协议最常见的现象是返回 FAIL 前缀这时候去 Protocol.java 和 RequestRouter.java 对照报文字段。不要追着控制台输出猜把前后端两条报文打印出来对齐通常一眼就能看出哪段断裂。还有一个个人习惯保存一份原始可运行的备份。任何一个改造步骤失败第一反应不是翻代码而是用备份先跑通再回头对比 diff。从那以后我每次改这种实验项目都强制走一遍这个流程先备份再动刀改完跑通至少一次完整链路才算完尤其是涉及协议和数据库字段的操作牵一发动全身。希望这个习惯和思路能帮到你让这个小型档案管理系统成为你 Java 学习路径上一个真正有分量的节点。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询