☰
Java实验设计:基于Socket与SQL的档案管理系统实战
2026/10/17 12:40:57 网站建设 项目流程

简介:这份资源是一套面向高校Java课程实验设计的完整源码方案,围绕C/S架构的小型档案管理系统展开,适合正在完成面向对象与多线程综合实验的学生及需要Socket通信实战案例的开发者参考。系统涵盖客户端与服务器端两个子系统,涉及用户登录与权限分级、档案属性入库、文件上传下载、条件查询以及用户信息增删改查等核心功能,服务器端采用多线程响应多客户端请求,并配合MySQL完成数据持久化。压缩包共47个文件,约1.37MB,以14个java源文件与15个class编译文件为主体,另含sql建库脚本、jar数据库驱动、readme说明及实验指导文档,目录按client与server分模块组织,结构清晰。目前已有875人学习下载。读者可据此获得一套可直接运行的实验参考实现,理解Socket通信、多线程处理与数据库操作的整合思路,并对照实验要求快速搭建与调试自己的档案管理系统。

1. 从一份“能跑起来”的 Java 实验设计说起

很多同学做 Java 实验设计时,最怕的不是写代码,而是“写完了跑不起来”。尤其是档案管理这类题目,表面看只是增删改查,真动手才发现:数据库怎么建、Socket 怎么通信、客户端和服务端怎么分工、文件上传怎么存,每一步都能卡住半天。这份 Java 实验设计实现的小型档案管理系统,就是冲着“能跑通、能演示、能交作业”去的。它用 Java 写核心业务,用 SQL 做数据持久化,用 Socket 做客户端与服务端通信,把档案录入、查询、修改、删除、借阅记录这些功能串成了一条完整链路。适合正在做课程设计、实验报告,或者想找一个 Java 网络编程加数据库练手项目的开发者。下面我按实际拆项目的顺序,把结构、跑法和改法讲清楚。

2. 项目定位与技术选型:为什么是 Java + SQL + Socket

2.1 这个系统到底解决什么问题

档案管理系统的核心诉求很朴素:把纸质档案的信息电子化,让管理员能快速录入、检索、修改和统计。这份实验设计没有一上来就堆 Spring Boot 或前端框架,而是用最原始的 Java SE 加 Socket 通信,把“网络编程”和“数据库操作”两个实验重点都覆盖了。服务端负责监听端口、处理客户端请求、操作数据库;客户端负责界面展示和发送指令。两边通过自定义协议传输数据,比如用特定分隔符拼接字段,或者直接传序列化对象。

这种设计的好处是依赖少、结构清晰,适合实验环境。你不需要配 Tomcat,也不需要懂前端构建,只要 JDK 和 MySQL 就能跑。常见做法是服务端启动后先初始化数据库连接池,然后循环 accept 客户端连接,每个连接开一个线程处理。客户端启动后连上服务端,通过控制台或 Swing 界面输入操作。对于实验设计来说,这种“看得见摸得着”的通信过程,比直接调 REST API 更能体现网络编程的理解。

2.2 技术栈拆解与版本建议

项目涉及的技术点可以拆成四块:Java 基础语法与面向对象、JDBC 数据库操作、Socket 网络通信、多线程并发处理。数据库用 MySQL 5.7 或 8.0 都行,驱动包用 mysql-connector-java。JDK 建议 1.8 以上,因为要用到 Lambda 表达式和 Stream 简化集合操作。开发工具用 IDEA 或 Eclipse 都可以,实验报告里通常要求贴关键代码和运行截图。

技术模块具体选型在项目中的角色
编程语言Java SE 8+业务逻辑、网络通信、界面
数据库MySQL 5.7/8.0存储档案、用户、借阅记录
通信方式Socket TCP客户端与服务端数据交换
并发处理多线程每个客户端连接独立线程
界面Swing 或控制台用户交互入口
构建工具无或 Maven依赖管理(可选)

注意:如果实验环境不能装 MySQL,可以换成 SQLite,只需改 JDBC URL 和驱动类名,SQL 语法基本不用动。

2.3 目录结构长什么样

一份典型的实验设计源码包,目录不会太复杂,但该有的分层都有。下面是我拆过的类似项目里最常见的结构:

ArchiveSystem/ ├── src/ │ ├── com/archive/server/ │ │ ├── ServerMain.java # 服务端启动入口 │ │ ├── ClientHandler.java # 每个客户端连接的处理线程 │ │ └── ArchiveService.java # 业务逻辑层 │ ├── com/archive/client/ │ │ ├── ClientMain.java # 客户端启动入口 │ │ └── ArchiveView.java # Swing 界面或控制台菜单 │ ├── com/archive/dao/ │ │ ├── ArchiveDao.java # 档案表 CRUD │ │ ├── UserDao.java # 用户表操作 │ │ └── DBUtil.java # 数据库连接工具 │ ├── com/archive/model/ │ │ ├── Archive.java # 档案实体类 │ │ ├── User.java # 用户实体类 │ │ └── BorrowRecord.java # 借阅记录实体类 │ └── com/archive/common/ │ └── Protocol.java # 通信协议常量与工具方法 ├── sql/ │ └── init.sql # 建库建表脚本 ├── lib/ │ └── mysql-connector-java.jar # 数据库驱动 └── README.md

这个结构把服务端、客户端、数据访问、实体模型、公共协议分开了。改代码时定位很快:要加字段就改 model 和 dao,要改通信格式就动 Protocol 和 ClientHandler。实验报告里画架构图,直接按这个分层画就行。

3. 核心模块拆解:从数据库到 Socket 的完整链路

3.1 数据库设计与建表脚本

档案管理系统的数据库一般至少三张表:档案表、用户表、借阅记录表。档案表存档案编号、名称、类别、存放位置、状态等;用户表存管理员账号密码;借阅记录表存谁在什么时候借了哪个档案、什么时候还。建表时注意主键、外键和索引,档案编号通常加唯一索引,避免重复录入。

-- 档案表 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(64) COMMENT '档案类别', location VARCHAR(128) COMMENT '存放位置', status TINYINT DEFAULT 1 COMMENT '1在库 0借出', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, role VARCHAR(16) DEFAULT 'admin' ); -- 借阅记录表 CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, archive_id INT NOT NULL, user_id INT NOT NULL, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, return_time DATETIME, FOREIGN KEY (archive_id) REFERENCES archive(id), FOREIGN KEY (user_id) REFERENCES user(id) );

建表脚本放在 sql/init.sql 里,服务端启动时可以选择自动执行,也可以手动在 MySQL 客户端跑一遍。字段类型和长度按实际需求调,比如档案名称如果可能很长,就把 VARCHAR(128) 加大。status 字段用 TINYINT 表示在库和借出两种状态,查询时直接 where status = 1 就能筛出可借档案。

3.2 数据库连接工具类 DBUtil

JDBC 连接如果每次操作都 DriverManager.getConnection,代码重复且性能差。常见做法是写一个 DBUtil,用静态代码块加载驱动,提供获取连接和关闭资源的方法。实验设计里不一定要上连接池,但至少把 URL、用户名、密码抽成常量,方便换环境。

public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/archive_db?useSSL=false&serverTimezone=UTC"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new RuntimeException("数据库驱动加载失败", 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 带了 useSSL=false 和 serverTimezone=UTC,是 MySQL 8.0 驱动常见的坑,不加可能报时区错误。用户名密码按自己环境改。close 方法把三个资源一起关,调用时不用写一堆 try-catch。如果实验要求用 PreparedStatement,把 Statement 换成 PreparedStatement 即可,关闭逻辑一样。

3.3 档案 DAO 层:增删改查的具体实现

DAO 层是业务逻辑和数据库之间的桥梁。以档案查询为例,支持按编号、名称、类别模糊查。下面这段代码展示了插入和查询两个核心方法。

public class ArchiveDao { public boolean insert(Archive archive) { String sql = "INSERT INTO archive(archive_no, name, category, location, status) VALUES(?,?,?,?,?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, archive.getArchiveNo()); ps.setString(2, archive.getName()); ps.setString(3, archive.getCategory()); ps.setString(4, archive.getLocation()); ps.setInt(5, archive.getStatus()); return ps.executeUpdate() > 0; } catch (SQLException e) { e.printStackTrace(); return false; } } public List<Archive> search(String keyword) { List<Archive> list = new ArrayList<>(); String sql = "SELECT * FROM archive WHERE archive_no LIKE ? OR name LIKE ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { String like = "%" + keyword + "%"; ps.setString(1, like); ps.setString(2, like); ResultSet rs = ps.executeQuery(); while (rs.next()) { Archive a = new Archive(); a.setId(rs.getInt("id")); a.setArchiveNo(rs.getString("archive_no")); a.setName(rs.getString("name")); a.setCategory(rs.getString("category")); a.setLocation(rs.getString("location")); a.setStatus(rs.getInt("status")); list.add(a); } } catch (SQLException e) { e.printStackTrace(); } return list; } }

insert 方法用 PreparedStatement 防止 SQL 注入,参数按占位符顺序设置。search 方法用 LIKE 做模糊匹配,keyword 前后加 %。try-with-resources 自动关闭 Connection 和 PreparedStatement,比手动 close 更安全。如果查询结果为空,返回空列表而不是 null,调用方不用做额外判空。

3.4 Socket 通信协议设计

客户端和服务端之间怎么传数据,是实验设计里最能体现设计能力的地方。简单做法是定义一套文本协议,比如每条消息以换行结束,字段之间用竖线分隔。第一段是命令字,后面跟参数。服务端解析命令字后分发到对应处理方法。

public class Protocol { public static final String CMD_LOGIN = "LOGIN"; public static final String CMD_ADD = "ADD"; public static final String CMD_SEARCH = "SEARCH"; public static final String CMD_DELETE = "DELETE"; public static final String SEPARATOR = "\\|"; // 拼接消息:CMD|参数1|参数2 public static String build(String cmd, String... params) { StringBuilder sb = new StringBuilder(cmd); for (String p : params) { sb.append("|").append(p); } return sb.toString(); } // 解析消息 public static String[] parse(String line) { return line.split(SEPARATOR); } }

客户端发送ADD|A001|人事档案|人事类|A区3排|1,服务端收到后按竖线切分,第一段是 ADD,后面依次是档案编号、名称、类别、位置、状态。这种协议简单直观,调试时直接打印字符串就能看懂。缺点是字段里不能出现竖线,如果档案名称可能包含特殊字符,可以换成 JSON 或 Base64 编码。实验设计里用竖线分隔足够,报告里也好写。

3.5 服务端多线程处理客户端连接

服务端主线程只负责 accept,每来一个客户端就开一个新线程处理。这样多个客户端可以同时操作,不会互相阻塞。下面是一个简化的服务端入口和处理器。

public class ServerMain { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务端启动,监听 8888 端口"); while (true) { Socket socket = serverSocket.accept(); new Thread(new ClientHandler(socket)).start(); } } } public class ClientHandler implements Runnable { private Socket socket; private ArchiveService service = new ArchiveService(); 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[] parts = Protocol.parse(line); String result = service.handle(parts); out.println(result); } } catch (IOException e) { e.printStackTrace(); } } }

ServerSocket 监听 8888 端口,accept 返回后立刻交给新线程。ClientHandler 里用 BufferedReader 按行读,PrintWriter 按行写,自动 flush。service.handle 根据命令字调用不同 DAO 方法,返回结果字符串。客户端收到结果后展示。这里要注意 socket 关闭和线程退出,客户端断开后 readLine 返回 null,循环结束,线程自然终止。

4. 跑起来:环境准备、启动顺序与常见报错

4.1 环境准备清单

跑这个项目之前,先把环境对齐。JDK 装 1.8 或以上,命令行 java -version 能输出就行。MySQL 装好并启动,用 Navicat 或命令行建一个 archive_db 数据库。把 mysql-connector-java 的 jar 包放到 lib 目录,IDEA 里右键 Add as Library。如果项目用 Maven,pom.xml 里加对应依赖即可。

检查项验证方式常见问题
JDKjava -version版本低于 1.8 编译报错
MySQLmysql -u root -p 能登录服务未启动或密码错误
驱动包lib 目录下有 jar忘记 Add as Library
端口8888 未被占用被其他程序占用导致启动失败
数据库archive_db 存在未执行 init.sql

4.2 启动顺序与验证

先执行 sql/init.sql 建表,再启动服务端 ServerMain,控制台输出“服务端启动,监听 8888 端口”。然后启动客户端 ClientMain,输入服务端 IP(本机就填 127.0.0.1)和端口 8888。登录后尝试新增一条档案,再查询,看是否能查到。如果服务端控制台打印了收到的命令和返回结果,说明通信链路通了。

# 编译(如果不用 IDE) javac -d out -cp "lib/mysql-connector-java.jar" src/com/archive/**/*.java # 启动服务端 java -cp "out:lib/mysql-connector-java.jar" com.archive.server.ServerMain # 启动客户端 java -cp "out:lib/mysql-connector-java.jar" com.archive.client.ClientMain

Windows 下 classpath 分隔符用分号,Linux 和 Mac 用冒号。如果报 ClassNotFoundException: com.mysql.cj.jdbc.Driver,检查驱动包是否在 classpath 里。如果报 Access denied for user,检查 DBUtil 里的用户名密码。

4.3 常见报错与排查

现象一:服务端启动报端口被占用。原因可能是 8888 端口已被其他程序监听。解决方法是换端口,比如改成 9999,同时改客户端连接端口。用 netstat -ano | findstr 8888 可以查占用进程。

现象二:客户端连不上服务端。先确认服务端是否已启动,再确认 IP 和端口是否填对。如果服务端和客户端不在同一台机器,要确保防火墙放行对应端口。实验环境通常本机跑,填 127.0.0.1 最稳。

现象三:中文乱码。Socket 传输时如果两端编码不一致,中文会变问号。常见做法是在 BufferedReader 和 PrintWriter 构造时指定 UTF-8,或者用 OutputStreamWriter 包一层并指定字符集。数据库连接 URL 里也可以加 characterEncoding=utf8。

现象四:查询结果为空但数据库里明明有数据。检查 SQL 里的 LIKE 拼接是否正确,以及是否用了事务未提交。如果插入后没 commit,其他连接查不到。JDBC 默认自动提交,但如果你手动开了事务,记得 commit。

现象五:多客户端同时操作时数据错乱。比如两个客户端同时借同一本档案。原因是业务层没有加锁或状态检查。解决方式是在借阅方法里先查状态,再用 update ... where status = 1 的原子操作更新,根据受影响行数判断是否借阅成功。

5. 二次开发与改造:从实验设计到能拿得出手的项目

5.1 把控制台换成 Swing 界面

实验设计如果只交控制台,演示效果一般。改成 Swing 界面工作量不大,但观感提升明显。核心思路是把客户端原来的 Scanner 输入换成文本框和按钮,把服务端返回的结果展示到 JTable 或 JTextArea。通信层不用动,只改客户端展示层。

// 简化示例:点击查询按钮后发送命令并展示结果 searchBtn.addActionListener(e -> { String keyword = keywordField.getText().trim(); String cmd = Protocol.build(Protocol.CMD_SEARCH, keyword); out.println(cmd); // 发送到服务端 String response = in.readLine(); // 读取返回 // 假设返回格式:编号,名称,类别;编号,名称,类别 String[] rows = response.split(";"); DefaultTableModel model = (DefaultTableModel) table.getModel(); model.setRowCount(0); for (String row : rows) { model.addRow(row.split(",")); } });

这里的关键是客户端和服务端的协议要保持一致。如果服务端返回的是竖线分隔,客户端就按竖线切;如果改成 JSON,两边都要改。建议在 Protocol 里加一个 formatResult 方法,统一返回格式,避免两边各写各的。

5.2 加一个简单的权限控制

现在的用户表只有用户名和密码,可以加一个 role 字段区分管理员和普通用户。登录成功后,服务端把角色返回给客户端,客户端根据角色决定是否显示删除按钮。服务端在处理删除命令时也要再校验一次角色,防止客户端绕过界面直接发命令。

// 服务端处理删除时校验角色 if (Protocol.CMD_DELETE.equals(cmd)) { if (!"admin".equals(currentUser.getRole())) { return "ERROR|无权限删除"; } // 执行删除 }

这种“界面隐藏 + 服务端校验”的双重控制,是权限设计的基本习惯。实验报告里可以作为一个扩展点写。

5.3 用 Maven 管理依赖和打包

如果想让项目更容易分发,可以改成 Maven 结构,把 mysql-connector-java 写进 pom.xml,用 mvn package 打成可执行 jar。这样别人拿到 jar 包,配好数据库就能跑,不用手动配 classpath。

<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>

打包时用 maven-shade-plugin 或 maven-assembly-plugin 把依赖一起打进去,生成一个 fat jar。启动命令变成 java -jar archive-system.jar,对实验验收来说更省事。

5.4 一个我踩过的坑:别在循环里开连接

早期写 DAO 时,我在 for 循环里每次调用 DBUtil.getConnection(),查一百条数据就开一百次连接,跑得慢还容易把数据库连接数占满。后来改成一次连接、批量查询,或者用连接池,问题才解决。从那以后我每次写数据库操作都强制走一遍“连接是否复用”的检查,宁可多写几行代码,也不在循环里反复开连接。希望这个习惯能帮到你。

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

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

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

立即咨询