拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java+MySQL图书馆管理系统:数据库设计、JDBC与事务实战解析

Java+MySQL图书馆管理系统:数据库设计、JDBC与事务实战解析

简介:面向Java期末大作业和课程设计场景,这套图书馆信息管理系统基于Java和MySQL实现,支持书名、作者、日期、出版社等多条件检索,并包含登录、借阅、归还等核心流程,界面完整、代码清晰,适合需要快速跑通项目并完成期末文档的在校学生。压缩包共86个文件,整体约2.22MB,涵盖11个Java源码、47个class编译文件、1份SQL数据库脚本、1份docx作业文档,以及多张png/jpg界面截图和工程配置说明文件;项目目录区分src源码、bin编译输出和资源文件,SQL脚本包含建库建表语句及测试数据,便于对照源码理解模块设计,也可直接导入运行验证效果。配套演示视频与安装教程视频从Java环境安装讲到MySQL数据库配置,步骤详细,新手按视频操作即可在本地成功跑起来。目前已有10173人学习下载,是图书馆信息管理系统期末项目、课程设计或Java+MySQL入门练手的实用参考。

1. 图书馆信息管理系统作为 Java+MySQL 期末大作业:它到底在考什么

期末大作业选「图书馆信息管理系统」这个题目的学生,远比选「学生管理系统」「超市收银系统」的人多,原因很实在:业务模型足够典型,书、读者、借阅、归还、逾期,每一块都能对应到数据库表关系和 Java 面向对象设计,老师一眼能看出你有没有真做;同时工作量又卡得刚好,一个人两周能写完,不会像电商系统那样收不住尾。这套题实际检验的是三件事:MySQL 表结构设计是否合理、JDBC 操作是否熟练、Swing 或 JSP 前端和数据库之间能不能顺畅对接。标题里的「完整版:项目+代码+文档+演示视频+安装教程视频」,对应的是你交付时拿得出手的五个东西,而不是只交一个能跑起来的压缩包。

本文会按我自己做这类项目的顺序拆:先定数据模型,再写 JDBC 和数据访问层,接着把项目在本机跑起来,然后把最容易翻车的地方单独拎出来讲,最后说演示视频和答辩文档怎么准备。每一段你都能直接照抄代码或配置,我会把参数含义和踩过的坑都标出来。

2. 把图书馆业务拆成 5 张表:数据模型设计是拿分的关键

2.1 为什么是这 5 张表:从借阅流程反推表结构

很多人的第一个错误是一上来就写代码,表结构边写边改,最后项目能跑,但一问三张表之间是什么关系就答不上来。正确做法是先走一遍业务:读者来借书,系统要记录谁借了哪本书、什么时候借的、什么时候该还;管理员要看到书库里有哪些书、库存剩多少。这背后最少需要 5 张表:图书表(book)、读者表(reader)、借阅记录表(borrow)、分类表(category)、管理员表(admin)。

借阅记录表是整个系统的核心,它通过 book_id 和 reader_id 关联另外两张表。图书表里单独放一个 category_id,是为了避免在 book 表里直接存分类名字符串——存名字会导致数据冗余,而且改分类名时要 UPDATE 多条记录,这是数据库设计基础题里最常考的点。管理员表独立出来,是因为管理员登录和读者登录权限不同,混在一张表里会让权限判断变麻烦。

我一般会在设计文档里画三句话解释这三张核心表的关系:「一本书属于一个分类」「一个读者可以借多本书」「一条借阅记录同时关联一本书和一个读者」。这不只是写文档用,答辩时老师第一个问题往往就是「为什么 borrow 表要单独建」。

2.2 建表 SQL:字段类型、默认值、字符集一次到位

直接给一份能用的建表 SQL,我按企业项目的习惯加了注释。注意字符集统一用 utf8mb4,不要用 utf8,因为 utf8 在 MySQL 里存不了生僻字和部分特殊符号(比如借阅备注里的 emoji),utf8mb4 才是真正的四字节 UTF-8。

-- 分类表 CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '分类ID', name VARCHAR(50) NOT NULL UNIQUE COMMENT '分类名称' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 图书表 CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '图书ID', book_name VARCHAR(100) NOT NULL COMMENT '书名', author VARCHAR(50) DEFAULT '' COMMENT '作者', publisher VARCHAR(100) DEFAULT '' COMMENT '出版社', category_id INT NOT NULL COMMENT '分类ID,逻辑外键', stock INT NOT NULL DEFAULT 1 COMMENT '库存数量', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '入库时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 读者表 CREATE TABLE reader ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '读者ID', reader_no VARCHAR(20) NOT NULL UNIQUE COMMENT '借书证号', name VARCHAR(50) NOT NULL COMMENT '姓名', phone VARCHAR(20) DEFAULT '' COMMENT '手机号', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 借阅记录表 CREATE TABLE borrow ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '记录ID', book_id INT NOT NULL COMMENT '图书ID', reader_id INT NOT NULL COMMENT '读者ID', borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '借书时间', due_time DATETIME COMMENT '应还时间', return_time DATETIME DEFAULT NULL COMMENT '实际归还时间', status TINYINT NOT NULL DEFAULT 0 COMMENT '0借出中 1已归还 2逾期未还', KEY idx_book_id (book_id), KEY idx_reader_id (reader_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里有几个关键点:图书表的 category_id 我写的是逻辑外键而不是物理外键,也就是说建表时不加 FOREIGN KEY 约束。期末项目里加物理外键会导致插入数据必须严格按顺序来,删除分类时还要处理约束冲突,很多学生在答辩演示时当场报错就是这个原因。逻辑外键靠程序保证一致性,对课程设计来说完全够用,我会在文档里主动说明这个选择——这反而是加分项,说明你想过外键的利弊。

借阅记录的 status 字段用 TINYINT 而不是 VARCHAR,存 0/1/2 三个数字,查询和统计都更快。due_time(应还时间)和 return_time(实际归还时间)必须分开,不能只存一个,否则「逾期判断」和「归还登记」会打架。逾期判断的 SQL 在答辩时很常被问到,我后面写给你。

2.3 索引和引擎:为什么 InnoDB 是唯一选择

上面每张表我都指定了 ENGINE=InnoDB,这不是随手写的。期末项目里最容易出现的两个问题:一是有人用默认的 MyISAM 引擎,二是建表时不加任何索引。MyISAM 不支持事务,如果你的项目里做了「借书时要同时更新库存和插入借阅记录」这种两步操作,中间任何一步失败,数据就脏了。InnoDB 支持事务,配合后面的 JDBC 代码可以把两步操作放在同一个事务里,要么都成功要么都失败。

索引方面,borrow 表上我建了 idx_book_id 和 idx_reader_id 两个普通索引。原因是查询「某个读者借了哪些书」和「某本书被谁借走了」是最常见的两个查询,如果不在外键列上建索引,MySQL 每次都要全表扫描。这个细节你写在文档的「设计说明」里,比在代码里堆功能更能体现水平。

另外提醒一个默认值问题:reader 表的 create_time 用了 DEFAULT CURRENT_TIMESTAMP,但 borrow 表的 return_time 故意没设默认值,而是用 DEFAULT NULL。原因是借出时 return_time 本来就还不存在,让它为空是业务语义的正确表达。如果你给所有时间字段都设 CURRENT_TIMESTAMP,归还时间会被自动填成借书时间,这是一个非常隐蔽的逻辑错误。

3. 用 JDBC 写数据访问层:连接池、预处理语句和事务三个必会点

3.1 最稳妥的技术选型:Swing + JDBC + MySQL,不要碰框架

期末大作业的技术栈选择有个现实原则:能用框架,但别用你没把握在答辩时讲清楚的框架。JSP+Servlet 是很多学校的标准要求,Swing 是单机版课程设计最常见的桌面方案。我不建议在期末项目里引入 MyBatis 或 Spring Boot,不是这些技术不好,而是你答辩时被问「MyBatis 的一级缓存和二级缓存有什么区别」的概率,远大于被问「JDBC 的 PreparedStatement 和 Statement 有什么区别」的概率,后者你能稳答,前者容易翻车。

这套项目的最佳结构是三层的简化版:界面层(Swing 或 JSP)→ 数据访问层(DAO)→ 数据库(MySQL)。界面层不写 SQL,DAO 里不写按钮事件,各层职责清楚,代码量不大但结构规整。我见过很多学生把 SQL 直接写在按钮的监听器里,虽然能跑,但老师看一眼就说这是「面向过程的 Java」,分数直接掉一档。

3.2 第一个代码块:使用连接池而不是 DriverManager

学校教材里教的是 Class.forName + DriverManager.getConnection,但真实的 Java 项目没人这么写。原因很简单:每次连接都经历 TCP 握手、认证、断开,高并发下性能极差。用连接池可以让程序启动时预创建一批连接,用完归还而不是关闭。期末项目规模小,引入 Druid 连接池并不会增加多少代码量,却是一个能在文档里写一整页的亮点。

以下是基于 Druid 的连接池工具类,Druid 是阿里巴巴开源的,配置简单,你不需要深入它的源码:

package util; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class DBUtil { private static DataSource dataSource; // 静态代码块:类加载时执行一次,初始化连接池 static { try { Properties props = new Properties(); // 配置文件放在 src 目录下,名为 db.properties InputStream in = DBUtil.class.getClassLoader().getResourceAsStream("db.properties"); props.load(in); dataSource = DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { e.printStackTrace(); } } // 从连接池取连接 public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }

对应的 db.properties 配置文件放在 src 根目录:

driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/library?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8 username=root password=你的数据库密码 initialSize=5 maxActive=10 maxWait=3000

注意三个参数。driverClassName 在 MySQL 8.x 下必须是 com.mysql.cj.jdbc.Driver,旧版的 com.mysql.jdbc.Driver 虽然也能用但会在日志里打警告,属于「能跑但不体面」。url 里的 serverTimezone=Asia/Shanghai 必须加,MySQL 8.x 不指定时区会报 CST 时区识别错误的异常;characterEncoding=utf8 配合 utf8mb4 字符集,是为了让中文不乱码。initialSize 和 maxActive 分别是初始连接数和最大连接数,期末项目 5 和 10 就够,别调大,连接数太大会让 MySQL 吃内存。

3.3 第二个代码块:PreparedStatement 为什么必须用

数据访问层写 SQL 时只能用 PreparedStatement,不用 Statement。核心原因是 SQL 注入:如果你用字符串拼接把读者输入直接拼进 SQL,比如「DELETE FROM reader WHERE id = 」加上输入框内容,对方输入「1 OR 1=1」就能删掉全部读者。PreparedStatement 用 ? 占位符传参,参数和 SQL 语句结构分离,数据库驱动会自动处理转义。

下面是图书管理的 DAO 方法,演示查询和插入两种操作:

package dao; import util.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class BookDao { // 查询所有图书 public List<Book> findAll() { List<Book> list = new ArrayList<>(); String sql = "SELECT id, book_name, author, publisher, category_id, stock FROM book"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Book book = new Book(); book.setId(rs.getInt("id")); book.setBookName(rs.getString("book_name")); book.setAuthor(rs.getString("author")); book.setPublisher(rs.getString("publisher")); book.setCategoryId(rs.getInt("category_id")); book.setStock(rs.getInt("stock")); list.add(book); } } catch (SQLException e) { e.printStackTrace(); } return list; } // 新增图书,返回受影响行数 public int insert(Book book) { String sql = "INSERT INTO book(book_name, author, publisher, category_id, stock) VALUES(?, ?, ?, ?, ?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, book.getBookName()); ps.setString(2, book.getAuthor()); ps.setString(3, book.getPublisher()); ps.setInt(4, book.getCategoryId()); ps.setInt(5, book.getStock()); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } } }

这段代码里用了 Java 7 的 try-with-resources 语法,Connection、PreparedStatement、ResultSet 会自动关闭。注意 ResultSet 也要手动关,很多学生只关 Connection,导致数据库连接被连接池回收后,关联的结果集没有释放,运行久了连接池的连接全被占满。

setString 和 setInt 是按序号对应 ? 的位置,第一个 ? 的序号是 1,不是 0,这是新手最容易写错的地方。如果你发现插入的数据错位或报「Parameter index out of range」,先数一遍问号个数和 set 方法的序号。

3.4 事务处理:借书操作必须写成原子操作

借书这个动作在数据库层面包含两步:更新图书库存减一,插入一条借阅记录。两步必须在一个事务里,否则可能出现库存减了但借阅记录没插上(或者反过来)的情况。下面这段代码是事务的标准写法,期末项目的加分点就在这里。

package service; import util.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class BorrowService { public boolean borrowBook(int bookId, int readerId) { String updateStock = "UPDATE book SET stock = stock - 1 WHERE id = ? AND stock > 0"; String insertBorrow = "INSERT INTO borrow(book_id, reader_id, due_time, status) VALUES(?, ?, DATE_ADD(NOW(), INTERVAL 30 DAY), 0)"; try (Connection conn = DBUtil.getConnection()) { // 关键:关闭自动提交,改为手动控制事务 conn.setAutoCommit(false); try (PreparedStatement ps1 = conn.prepareStatement(updateStock)) { ps1.setInt(1, bookId); int rows = ps1.executeUpdate(); if (rows == 0) { // 库存不足,回滚 conn.rollback(); return false; } } try (PreparedStatement ps2 = conn.prepareStatement(insertBorrow)) { ps2.setInt(1, bookId); ps2.setInt(2, readerId); ps2.executeUpdate(); } // 两步都成功才提交 conn.commit(); return true; } catch (SQLException e) { e.printStackTrace(); // 异常时回滚 if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } return false; } } }

这段代码有两个细节值得在文档里写。第一,UPDATE 语句里带了「AND stock > 0」条件,如果库存已经是 0,执行影响行数为 0,说明借不了,直接回滚,这是用乐观锁思路避免超借。第二,DATE_ADD(NOW(), INTERVAL 30 DAY) 把应还时间设为借书后 30 天,SQL 函数写在语句里而不是在 Java 代码里算好再传参,能让时间计算依托数据库服务器时间,避免客户端和服务器时间不一致。

事务里最容易忽略的问题是把 setAutoCommit(false) 写在 try-with-resources 外面还是里面:必须写在拿到 Connection 之后、执行第一条 SQL 之前。如果你在连接池获取连接后没有关自动提交,那么 executeUpdate 每执行一次就自动持久化,事务就失去意义了。

4. 在本机跑通这个项目:从 JDK 到 MySQL 到演示数据的全流程

4.1 环境准备:JDK、MySQL、连接工具三件套的版本匹配

跑通这个项目首先要解决环境问题,我见过太多项目本身没问题、卡在环境装不上。先说版本匹配:JDK 建议装 8 或 11,对应 javax 开头的 Swing 包,太新的 JDK 比如 17 或 21 在部分 Windows 老机器上会有显示缩放问题;MySQL 装 5.7 或 8.0 都行,但 8.0 的驱动类名和认证方式跟 5.7 不同,代码里按 8.0 写就统一用 8.0。

MySQL 安装时有两个关键选项:字符集选 utf8mb4,认证插件选「Use Legacy Authentication」或者安装后在命令行执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '密码';。为什么强调这个?因为 MySQL 8.0 默认的 caching_sha2_password 插件,在某些旧版 JDBC 驱动(mysql-connector-java 5.x)下会报「Public Key Retrieval is not allowed」错误,很多人卡在这一步。如果你用的是 mysql-connector-j 8.x 驱动,可以直接在 JDBC url 里加 allowPublicKeyRetrieval=true 解决。

连接工具方面,Navicat 和 MySQL Workbench 二选一。MySQL Workbench 免费且不存在激活问题,适合学生项目;Navicat 在热搜词里常被搜到但它是收费软件,不要为此冒险,Workbench 完全够用。我一般会用命令行客户端做验证,因为答辩时老师可能没有图形界面工具,命令行操作反而更能说明你真的懂。

4.2 导入项目和数据库:手把手操作步骤

拿到项目压缩包后按以下步骤执行。先建数据库,再建表,再导入测试数据,顺序不能反。如果是 Windows 系统,注意 SQL 脚本里如果有中文注释,要确认文件编码是 UTF-8,否则用命令行 source 导入时注释会乱码。

# 第一步:登录 MySQL,密码替换成你自己的 mysql -u root -p # 第二步:创建数据库并指定字符集 CREATE DATABASE library DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 第三步:切换数据库,然后用 source 执行建表脚本(假设脚本在 D 盘根目录) USE library; source D:/library_db.sql; # 第四步:查看表是否创建成功 SHOW TABLES;

执行完 SHOW TABLES 应该看到 category、book、reader、borrow、admin 五张表。如果少了表,检查脚本里有没有 DROP TABLE IF EXISTS 语句,或者有没有因为前面建表出错导致后续语句没执行。source 命令在执行时如果某条 SQL 报错,会把错误显示在终端里,但后面的 SQL 还会继续跑,所以不要只看最后有没有报错就完事,要回头翻日志。

导入测试数据我建议用 SQL 脚本而不是在界面里一条条录,既省时间又能保证数据格式正确。写几条有代表性的数据:图书至少 10 本,覆盖 3 个分类;读者 5 个;借阅记录里要有「已归还」「借出中」「逾期未还」三种状态。这三种状态能让你的查询功能演示变得非常自然,特别是逾期列表,一打开就有数据可以展示。

4.3 修改项目里的数据库连接配置并启动

项目代码里 DBUtil 从 db.properties 读连接信息,改成你自己的 MySQL 端口、用户名、密码即可。端口默认 3306,如果你安装时改过端口,这里要同步改。下面是我建议的启动顺序,能帮你区分是配置问题还是代码问题。

# 1. 先用命令行确认 MySQL 服务正在运行(Windows 下) net start | findstr mysql # 2. 用命令行确认能连上数据库(这一步能排除 Navicat 连接正常但代码连不上的情况) mysql -u root -p -h 127.0.0.1 -P 3306 # 3. 在 IDEA 里刷新 Maven,确认 mysql-connector-j 和 druid 两个依赖已下载 # 4. 运行 Main 或 LoginFrame 类

这里要特别注意:db.properties 里的 url 中 host 建议写 127.0.0.1,不要写 localhost。原因是某些 MySQL 安装会默认把 localhost 解析为 socket 连接(Unix 系统)或 IPv6,而 JDBC 的 TCP 连接如果主机名解析异常,会报「Can't connect to local MySQL server through socket」错误。写 127.0.0.1 强制走 TCP,可以绕开这个坑。

启动后如果界面出来了但操作报中文乱码,就在 IDE 的启动参数里加-Dfile.encoding=UTF-8(IDEA 里是 VM options)。这个坑特别普遍:数据库里的数据是正常中文,Java 端读出来变成问号,十有八九是 IDE 默认用了 GBK 编码启动 JVM。

5. 常见问题排查:从 2002 socket 错误到中文乱码的 4 个必查项

5.1 现象:连接报 error 2002 (HY000): Can't connect to local MySQL server through socket

这个问题在热搜词里出现频率很高,期末项目里遇到的概率也不小。现象是双击运行项目时控制台抛出这个异常,但用 Navicat 或命令行却连不上,或者只有 Java 代码连不上。

原因是 MySQL 客户端默认走 Unix socket 文件(Linux/macOS)或命名管道(Windows),而你的 Java 程序通过 JDBC 驱动发起的是 TCP/IP 连接。两个连接方式路径不同,未必都开放。解决方法是分三步走:先确认 MySQL 服务确实在跑(Windows 服务管理器或net start能看到 mysql 服务);再用命令行测试 TCP 连接是否可用,命令是mysql -u root -p -h 127.0.0.1 -P 3306,如果这条能连上而 Java 连不上,说明 Java 的 url 里 host 或 port 写错;如果命令行也报 2002,说明 MySQL 的 skip-networking 选项被启用了,去 my.ini 里把 skip-networking 注释掉重启服务。

5.2 现象:中文数据写入数据库后变成问号或乱码

这个坑几乎是每个期末项目都会遇到的。现象分两种:从界面输入中文点保存,数据库里变成 ??;或者数据库里正常的中文读到界面变成 ??。前一种是写入时编码不匹配,后一种是读取时编码不匹配。

原因涉及三层编码必须一致:MySQL 数据库字符集、JDBC url 的 characterEncoding 参数、Java 工程文件本身的编码。解决方法是三层全部对齐到 UTF-8。具体操作:建库时用CREATE DATABASE library DEFAULT CHARACTER SET utf8mb4(上面已经做了);JDBC url 末尾加characterEncoding=utf8(上面也已经做了);IDEA 里在 Settings → Editor → File Encodings 把 Global Encoding、Project Encoding、Properties Files 都设为 UTF-8。如果项目已经写入了乱码数据,用命令行DELETE掉重新插入,因为已存在的 ?? 数据不会被自动修复。

一个容易漏掉的点:如果 db.properties 文件本身是在 Windows 记事本里编辑保存的,可能默认用了 ANSI 编码,里面如果有中文注释或中文密码,也会引发编码错乱。建议用 IDEA 或 VS Code 打开属性文件编辑并保存为 UTF-8 格式。

5.3 现象:连接池初始化失败,报 ClassNotFoundException: com.mysql.cj.jdbc.Driver

报这个错说明 mysql-connector-j 的 jar 包没有进入运行时 classpath。期末项目用 Maven 的话,检查 pom.xml 依赖是否正确声明,然后执行 mvn clean 再重新运行。最常见的原因:依赖的作用域写错了。以下是标准依赖声明,scope 写 runtime 而不是 compile,因为 JDBC 驱动只需要运行时加载,编译期不需要引用它。

<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.20</version> </dependency>

这里有个版本坑:mysql-connector-j 的 groupId 在 8.x 版本是 com.mysql,artifact 是 mysql-connector-j;而在 5.x 是老坐标 mysql:mysql-connector-java。如果你在网络上复制粘贴了旧版依赖到新项目,会下载失败或下载到一个不兼容的旧包。优先用 8.0.33 这个稳定版本,它在中央仓库可以正常拉取。

如果不用 Maven、直接用 IDEA 的 Project Structure 导入 jar 包,检查是否同时导入了 mysql 驱动和 druid 两个包,漏掉 druid 会报 DruidDataSourceFactory 找不到。导入后要在 Artifacts 里确认 Output Layout 的 lib 目录下出现了这两个 jar,否则编译通过但运行时照样 ClassNotFoundException。

5.4 现象:数据库连接成功但 SQL 执行报「Unknown column 字段名」

这个报错背后的原因多半是 Java 实体类字段名和数据库列名不一致,或者 SQL 里查的列在表里不存在。我之前见过一个学生把 book 表的 book_name 字段命名为 bookName,查询时写 SELECT bookName FROM book,MySQL 报错找不着列。数据库命名习惯和 Java 驼峰命名本身就是两套体系,这个差异要刻意处理。

解决方式两种:第一种是 SQL 里用别名对齐,比如SELECT book_name AS bookName FROM book;第二种是用 Map 封装结果而不建实体类。期末项目我推荐第一种,因为实体类更接近「面向对象」的要求,答辩时好讲。排查步骤是:把报错信息里完整的 SQL 语句拷贝出来,到 Navicat 或命令行里手动执行一次,看是否报同样的错。如果手动执行正常而 Java 执行报错,就检查是不是用了不同的数据库(比如代码连接的是 library_test 表结构是旧的),忽略这个步骤会让你浪费很多时间。

5.5 现象:时间字段显示「2026-08-24」而不是本地时间

出现这个现象的原因通常是 JDBC url 里没有指定 serverTimezone,或指定了错误的时区。MySQL 的 CST 时区是一个有歧义的缩写,在中国代表北京时间,在数据库驱动解析时却可能被当作美国中部时间,导致时间偏移 13 个小时。解决方法是把 serverTimezone 显式设为 Asia/Shanghai,或者使用serverTimezone=GMT%2B8也行。

如果项目里用了返回时间戳的 SQL 函数(如 NOW()),数据库服务器时间和应用服务器时间可能不一致,最稳妥是数据库和 Java 都统一用系统默认时区,然后 JDBC url 里显式指定。另外,Swing 的 JTable 显示时间时,如果你直接把 java.sql.Timestamp set 进表格模型,显示格式会丑而且可能带毫秒;建议在实体类里把时间字段格式化为yyyy-MM-dd HH:mm:ss字符串再放到表格里展示,保存时再转回 Timestamp。这个格式转换看起来多余,实际上是让界面专业感提升最明显的小改动。

6. 从「能跑」到「像样」:演示视频录制、文档组织与答辩留一手

项目做到能跑只完成了一半,期末大作业的评分很大程度取决于你交付的「完整版」里那三样东西:文档、演示视频、安装教程视频。先说文档,别写成长篇大论的功能罗列,我按答辩被提问的顺序组织成五段:需求分析(你解决了什么问题)、数据库设计(ER 图和表结构说明)、系统架构(Swing/JSP+DAO+MySQL 三层图)、核心代码说明(连接池、事务、PreparedStatement 各贴一段关键代码并解释)、测试与运行说明(环境版本、启动步骤、测试数据)。这五段每段控制在两页以内,整份文档 15 页左右。

演示视频的录制有一个实用技巧:不要一上来就操作界面,先把数据库表结构 SHOW 一遍再开始演示。具体顺序建议:用命令行SHOW TABLES和DESC book;展示表结构,说明这是你设计的核心;启动项目登录;新增一本图书;模拟借书流程(选书 → 库存减一 → 生成一条借阅记录);模拟还书流程;查询逾期列表并把一条逾期记录的状态展示出来;最后进数据库用 SELECT 验证库存和借阅记录确实变了。录到「数据库数据也跟着变」这一步,评分老师就能看出这不是写死的前端假数据。

安装教程视频比演示视频更重要,因为评分老师很可能看完演示后想自己跑一遍。录制时从「安装 MySQL」讲到「用 source 导入 SQL 脚本」再到「IDEA 打开项目改 db.properties」最后「运行」,录之前先把所有步骤走通,防止录到一半发现某步报错尴尬。录制工具用 OBS 就行,免费而且能同时录屏幕和麦克风,分辨率设为 1920x1080,保证代码清晰可读。视频时长控制在 12 分钟以内,超过的部分八成是无意义停顿和重复操作。

最后一个答辩经验:在代码里故意留一个「能讲清楚但不算 bug」的设计点。比如我在事务代码里用「UPDATE book SET stock = stock - 1 WHERE id = ? AND stock > 0」这种带库存判断的写法,老师问「为什么这里多一个 AND 条件」,你回答「为了防止库存被减成负数,这也是乐观锁的一种实现」。这种细节比你能背出八个设计模式更能证明代码是你自己写的,因为它像一个真实的工程决策而不是教材练习。

我把这个习惯沿用了很多年:任何期末项目或课程设计,交付前一定把「删除关键代码会不会立刻报错」当作测试标准。演示时老师偶尔会让你改一行代码或加一个功能,如果系统被你自己的改动弄挂,不是能力的证明,是交付流程的漏洞。这个项目能给你的不只是学分,更是「从零搭起一个完整 CRUD 系统的熟悉感」——那是面试前最有价值的一次实战排练,希望帮到你。

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

返回列表