☰
Java图书管理系统课程设计:数据库建模、JDBC事务与分层架构实战
2026/10/7 2:31:17 网站建设 项目流程

“图书管理系统”大概是我见过最两极分化的课程设计题目了。一方面,它确实是Java课程设计里的常青树,几乎每个学校的题目库都有它;另一方面,每年答辩季我都会看到一批学生熬夜到凌晨三点,最后交上来一个“功能看似齐全、点开代码就崩”的半成品。很多人以为这个题简单——不就是图书的增删改查吗?但实际做下来才发现,登录逻辑、库存联动、借阅状态流转、超期计算,任何一个环节没设计清楚,后面全是补丁。这篇文章我打算从需求拆分讲到环境搭建、数据库建模、JDBC实操、核心功能实现、常见报错排查,最后再聊答辩时怎么讲才能让评委觉得你“真懂”,目标是让零基础的同学也能照着一步步做完,并且知道自己每一步在做什么。

1. 课程设计选题悖论:图书管理系统看似简单,为什么每年挂掉一半人

1.1 课程设计和真实项目的差别:评分点在“结构”不在“功能”

先说一个反直觉的现象:我见过的课程设计里,功能做得最全的那批人,分数往往不是最高的;反而是那些功能朴实、但代码层次清楚的人拿了优秀。

原因很简单。课程设计的评分老师一天要审几十份作业,他没法真的把你每个按钮都点一遍。他看的是:代码结构是否分层、异常是否处理、数据库设计是否合理、文档能不能说清楚你的思路。一个三层架构清楚、注释到位、会在借书时判断库存的项目,哪怕只有三个功能,老师也会觉得“这个学生是理解了的”。反之,你把所有逻辑全塞在“界面类”里,一个类三千行,老师翻两页就想关了。

所以你在动手之前,先摆正心态:这不是一个“把功能堆出来”的任务,这是一个“证明你理解Java核心特性”的任务。面向对象、封装、DAO模式、JDBC事务、SQL与集合的转换,这些才是评分表上真正扣分和加分的地方。

1.2 需求范围拆解:最小可用版本到底包含哪几块

很多同学一上来就想着“我要做一个完整的系统”,于是列了十几个功能,做到第八个已经开始烦躁,最后草草收尾。我建议你先做减法。

图书管理系统的核心业务流转其实只有一条线:管理员登录 → 管理图书 → 管理读者 → 处理借书还书 → 统计罚款。围绕这条线,我把功能分成三档:

  • 基础必做(及格线):管理员登录、图书信息的增删改查、读者信息的增删改查、借书功能、还书功能。
  • 建议做(加分项):模糊搜索、分页展示、借阅记录查询、超期罚款自动计算、密码MD5加密。
  • 有余力再碰(亮点):读者自助注册、排行榜、图表统计、Excel导出、批量导入。

我见过很多零基础的同学栽在一个地方:想一口气全做。结果每个功能都做成了“一个按钮弹个窗”,数据库表没设计好就硬写代码,后面越写越乱。正确的做法是先搭好骨架,把基础必做跑通,再一个一个加建议项。你每加一个功能,都要回头看看:“这个功能需不需要改表结构?需不需要动DAO层?”想清楚再动笔。

2. 技术选型与开发环境:控制台、Swing还是Spring Boot,先想清楚再说

2.1 不同技术栈的对比:从答辩效果出发做选择

每次课设答疑,问得最多的就是“老师,我到底用Swing还是用Spring Boot?”这个问题没有一个万能答案,主要看你手里有多少时间、你对哪个更熟。

我做了一张对比表,你可以直接对号入座:

技术栈上手难度工作量答辩观感适合哪类人
纯控制台低小差,老师会觉得太简陋只剩两三天、实在没时间
Java Swing + JDBC中中中,界面偏老但逻辑清楚大二、只学过Java基础
JavaFX + JDBC中高中中上,界面现代对Java根底有点信心的
Spring Boot + Thymeleaf中高较大高,接近真实项目大三大四、学过框架
Spring Boot + Vue前后端分离高大高有前后端基础、时间充裕

这里要泼一盆冷水:不要为了答辩炫技去选一个你一个月学不完的技术栈。我见过不少同学跟风选Spring Boot + Vue,最后后端接口写了一半,前端调不通,临近答辩才回头问“能不能改成Swing”。说实话,如果你们课程只教到Java SE,Swing或者JavaFX已经足够拿高分了。老师想看到的是“你会写类、会封装、会用JDBC”,不是“你会搭个花架子”。

如果让我推荐零基础的最优解:Java Swing + MySQL + JDBC。这四条线全部踩在课程大纲上,代码量适中,遇到问题网上资料也最好找。等你做完这个版本,如果你还有精力,再把某个模块用Spring Boot重写一遍,答辩时跟老师说一句“我对比过两种实现方式”,效果反而更好。

2.2 环境配置实操:JDK、MySQL、IDE、连接驱动版本匹配

选型定了,环境必须先折腾明白。这里给一套我实操下来最稳的版本组合,别乱升级:

  • JDK:8或11都行,不要装最新的17、21。不是不能用,而是很多教材和网上文章默认用的是8,你遇到报错去搜索时,8的答案最多。
  • MySQL:8.0版本。注意8.0的驱动类和5.x不同,连接参数也不一样,很多老教程讲的是5.1的写法,你照抄会报错。
  • IDE:Eclipse或IntelliJ IDEA社区版都行。IDEA对新手更友好,但内存占用大;Eclipse启动快,课程设计绰绰有余。
  • JDBC驱动:MySQL 8对应mysql-connector-j-8.0.x.jar,别用com.mysql.jdbc.Driver,要用com.mysql.cj.jdbc.Driver。

如果你用Maven管理依赖,pom.xml里这样写:

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

如果你是用Eclipse/IDEA手工加jar包,记得把mysql-connector-j-8.0.33.jar放进项目的lib目录,然后右键“Add as Library”。这一步漏掉的话,后面百分之百会报ClassNotFoundException。

连接串也有讲究。MySQL 8时代,最简单的写法是这样:

String url = "jdbc:mysql://localhost:3306/library?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8";

这里的serverTimezone=Asia/Shanghai是刚需。不加它,你会看到一个“The server time zone value”的英文报错;不加characterEncoding=utf8,你往数据库里写入中文时,十有八九会变成问号。

提示:如果你用的是IDEA,注意右下角或顶部消息栏偶尔会提示“Setup JDK”,不要忽略它,否则你编译时能看到代码却找不到运行按钮。

3. 数据库表结构设计:五张表如何支撑借阅核心流转

3.1 数据库设计的前置思考:先画状态,再定字段

很多人建表是“想到什么加什么”,图书表、读者表建完就开写,写到借书功能时发现“哎,我怎么知道这本书借出去了没”,于是又回去加字段,来回折腾。其实数据库设计有更省力的顺序:先把业务流转画出来,再定字段。

图书借阅的核心流转是:

读者去借书 → 管理员登记 → 图书库存减一 → 生成借阅记录 → 到期还书 → 库存加一 → 如果超期则生成罚款

所以系统至少要记录两个层面的东西:实体信息(图书、读者、管理员)和过程信息(借阅记录、罚款记录)。实体信息解决“你是谁、这是什么书”的问题,过程信息解决“这本书现在在谁手里、该不该还、罚了多少钱”的问题。你只要想明白这一点,表结构就不会跑偏。

另外还有一个特别容易踩的坑:要不要把“当前借了几本书”作为读者表的一个字段?我的建议是不要。借阅数量完全可以通过查询借阅记录表数出来,你单独存一个字段,还书、借书时还要手动改它,一改漏就数据不一致。记住:能算出来的数据,尽量不存。

3.2 五张核心表的字段定义与建表语句

我直接给一份可以直接用的建表SQL。字符集统一用utf8mb4,这是对中文最友好的编码。

CREATE DATABASE IF NOT EXISTS library DEFAULT CHARACTER SET utf8mb4; CREATE TABLE admin ( admin_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(32) ); CREATE TABLE book ( book_id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20), title VARCHAR(128) NOT NULL, author VARCHAR(64), publisher VARCHAR(64), price DECIMAL(10,2), category VARCHAR(32), stock INT DEFAULT 0, total INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE reader ( reader_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL, phone VARCHAR(20), email VARCHAR(64), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE borrow_record ( borrow_id INT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL, reader_id INT NOT NULL, borrow_date DATE NOT NULL, due_date DATE NOT NULL, return_date DATE, status TINYINT DEFAULT 0, FOREIGN KEY (book_id) REFERENCES book(book_id), FOREIGN KEY (reader_id) REFERENCES reader(reader_id) ); CREATE TABLE fine ( fine_id INT PRIMARY KEY AUTO_INCREMENT, borrow_id INT NOT NULL, amount DECIMAL(10,2) NOT NULL, paid TINYINT DEFAULT 0, FOREIGN KEY (borrow_id) REFERENCES borrow_record(borrow_id) );

几个字段设计的细节,讲一下为什么这么写:

  • borrow_record.status用数字:0表示借出、1表示已还、2表示逾期未还。用数字比用字符串省空间,更重要的是程序里判断方便。你写if(rs.getInt("status") == 1)比if("已还".equals(rs.getString("status")))要优雅得多。
  • book.total表示图书总数,stock表示当前可借数量。借书时stock-1,还书时stock+1,而total不变,这样你在统计“馆藏总量”和“当前可借”时都有数据来源。
  • fine表里的paid用来标记罚款是否已缴纳。这个设计看似多余,但没它你后期做“罚款统计”时会非常痛苦。
  • 所有外键关系,在课程设计级别的项目里尽量保留。答辩时老师看见你有外键约束会明显认可,因为很多学生的表是“各管各的”,没有任何关系。

4. JDBC连接MySQL的细节:驱动加载、时区、事务一个都不能少

4.1 数据库连接的正确姿势:一个DBUtil工具类走天下

课上老师讲JDBC的时候,示例代码通常是把连接逻辑写在main方法里,每查一次就写一遍DriverManager.getConnection。但课程设计里你要是这么写,代码会炸开。正确做法是写一个工具类,把“拿连接”和“关资源”封装起来,全项目复用。

我给你们一个我这些年用下来最顺手的模板:

import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/library?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new RuntimeException("MySQL驱动加载失败,请检查jar包是否导入"); } } 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 e) { e.printStackTrace(); } try { if (stmt != null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }

这里有三个细节,答辩时老师很喜欢问:

第一,为什么用Class.forName加载驱动?其实MySQL 8的JDBC驱动在Java 6以上可以自动注册,不写也能连。但很多课程设计要求里明确写了要会手动加载驱动,而且写上它可以让报错信息更直观——驱动jar没导入就直接抛“MySQL驱动加载失败”,容易排查。

第二,为什么资源关闭要倒着关?ResultSet依赖Statement,Statement依赖Connection,所以关闭顺序必须是先结果集、再语句、再连接。你要是先关了连接再关语句,严格来说会有资源管理器报错,虽然实际运行中不一定炸,但这是个规范问题。

第三,为什么要把static块放在类里而不是每次连接前调用?驱动只需要加载一次就够了,放在静态块里,类首次加载时执行,后面所有连接都复用这次加载。这是Java类加载机制的基本应用,答辩时主动提一句,能看出你真的理解了static关键字。

4.2 借书还书中的事务处理:一个坑每年都有人踩

我一直觉得课程设计阶段最该掌握的JDBC知识点就是事务。很多同学做借书功能时是这样写的:先执行一条INSERT INTO borrow_record,再执行一条UPDATE book SET stock = stock - 1。表面看没问题,但假如第一条SQL成功了,第二条SQL因为某种原因失败了——比如库存字段写错、连接断开——就会出现“借阅记录有了,库存没减”的脏数据。

解决办法很简单:把两步操作放进同一个事务里。JDBC里手动控制事务的代码如下:

Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交 // 第一步:插入借阅记录 String sqlBorrow = "INSERT INTO borrow_record(book_id, reader_id, borrow_date, due_date, status) VALUES(?,?,?,?,0)"; PreparedStatement ps1 = conn.prepareStatement(sqlBorrow); ps1.setInt(1, bookId); ps1.setInt(2, readerId); ps1.setDate(3, new java.sql.Date(System.currentTimeMillis())); // 设置应还日期,默认借期30天 ps1.setDate(4, new java.sql.Date(System.currentTimeMillis() + 30L * 24 * 3600 * 1000)); ps1.executeUpdate(); // 第二步:扣减库存 String sqlStock = "UPDATE book SET stock = stock - 1 WHERE book_id = ? AND stock > 0"; PreparedStatement ps2 = conn.prepareStatement(sqlStock); ps2.setInt(1, bookId); int rows = ps2.executeUpdate(); if (rows == 0) { throw new RuntimeException("库存不足,借书失败"); } conn.commit(); // 全部成功才提交 } catch (Exception e) { try { if (conn != null) conn.rollback(); // 任何一步失败就回滚 } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); } finally { // 归还连接,注意把事务状态复位 try { if (conn != null) { conn.setAutoCommit(true); DBUtil.close(conn, null, null); } } catch (SQLException e) { e.printStackTrace(); } }

注意这段代码里的两个巧思:UPDATE book SET stock = stock - 1 WHERE stock > 0这一步我特意加了stock > 0条件,配合rows == 0判断,可以避免“库存为负”。而conn.setAutoCommit(false)到finally里再改回true,是防止连接归还后被下一个使用者带着“非自动提交”的状态干活。这个细节很多人忽略,池化连接复用时容易出诡异问题。

答辩时你把这段讲明白,基本就赢了。因为大部分同学的借书功能就是两条不带事务的SQL,他们自己都没意识到会出什么问题。

5. 核心业务代码逐个拆:登录、增删改查、借书还书的实现逻辑

5.1 数据访问层:一个接口、一个实现类,避免DAO代码满天飞

很多零基础同学的代码是“Service里直接写JDBC”——这样写不是不能跑,但你在Service里把PreparedStatement、ResultSet这些低级操作全堆出来,代码会越来越难改。我做课程设计时习惯分两层:

  • DAO层:只关心“怎么从数据库拿数据”,对外提供findById、findAll、add、update、delete这类方法。
  • 业务层/Service层:只关心“这个操作需要怎么做”,比如借书业务是“先查库存再插入记录再扣库存”,它只管调DAO,不管SQL长什么样。

以图书查询为例,DAO层写一个接口:

public interface BookDAO { List<Book> findAll() throws SQLException; Book findById(int bookId) throws SQLException; List<Book> searchByTitle(String keyword) throws SQLException; boolean add(Book book) throws SQLException; boolean update(Book book) throws SQLException; boolean delete(int bookId) throws SQLException; }

实现类里最核心的是那句“ResultSet转换成对象”的代码。我习惯单独抽一个方法:

private Book mapRow(ResultSet rs) throws SQLException { Book book = new Book(); book.setBookId(rs.getInt("book_id")); book.setTitle(rs.getString("title")); book.setAuthor(rs.getString("author")); book.setPublisher(rs.getString("publisher")); book.setPrice(rs.getBigDecimal("price")); book.setCategory(rs.getString("category")); book.setStock(rs.getInt("stock")); book.setTotal(rs.getInt("total")); return book; }

有的同学会问:为什么不直接用rs.getString("title")绑定给界面?因为那样的话,界面类就依赖ResultSet,而ResultSet是JDBC特有的类型,你的UI层会变成“离不开数据库的植物人”。把它们转成普通的Book对象后,界面、业务、数据三层互不干扰,改SQL不会影响界面,这就是分层的价值。

5.2 借书还书完整流程:两张表联动、库存校验、状态更新的代码骨架

前面讲了事务代码,这里我把借书的“业务层方法”完整串一遍,你会发现所有操作都落在三个步骤上:查库存 → 写记录 → 更新库存。

public boolean borrowBook(int bookId, int readerId) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 查库存 String sqlCheck = "SELECT stock FROM book WHERE book_id = ? FOR UPDATE"; PreparedStatement psCheck = conn.prepareStatement(sqlCheck); psCheck.setInt(1, bookId); ResultSet rs = psCheck.executeQuery(); if (!rs.next() || rs.getInt("stock") <= 0) { return false; } // 2. 插入借阅记录 String sqlInsert = "INSERT INTO borrow_record(book_id, reader_id, borrow_date, due_date, status) VALUES(?,?,?,?,0)"; PreparedStatement psInsert = conn.prepareStatement(sqlInsert); psInsert.setInt(1, bookId); psInsert.setInt(2, readerId); long now = System.currentTimeMillis(); psInsert.setDate(3, new java.sql.Date(now)); psInsert.setDate(4, new java.sql.Date(now + 30L * 24 * 60 * 60 * 1000)); psInsert.executeUpdate(); // 3. 扣库存 String sqlUpdate = "UPDATE book SET stock = stock - 1 WHERE book_id = ? AND stock > 0"; PreparedStatement psUpdate = conn.prepareStatement(sqlUpdate); psUpdate.setInt(1, bookId); psUpdate.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { e.printStackTrace(); try { if (conn != null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } return false; } finally { try { if (conn != null) conn.setAutoCommit(true); DBUtil.close(conn, null, null); } catch (SQLException e) { e.printStackTrace(); } } }

那个SELECT ... FOR UPDATE我解释一下:这是“行级锁”,防止两个管理员同时给同一本书办借书,导致库存被减成负数。课程设计阶段,老师可能不指望你会这个,但你在答辩时提出来,绝对是加分项。

还书逻辑稍微反过来:先查这条借阅记录是否存在且状态为“借出”,然后更新return_date为今天,把status改成“已还”,再把库存加回去。超期罚款的计算很简单:用今天的日期减去due_date,超过一天就按某个金额累加:

long overdueDays = (today.getTime() - dueDate.getTime()) / (24 * 3600 * 1000); if (overdueDays > 0) { double fineAmount = overdueDays * 0.5; // 每天0.5元 // 插入fine表 }

做搜索功能时,DAO层的核心就是一条带LIKE的SQL:

SELECT * FROM book WHERE title LIKE ? OR author LIKE ? ORDER BY book_id LIMIT ?, ?

分页要注意LIMIT的偏移量是(page - 1) * pageSize,不是page。很多同学第一页没问题,第二页就少数据,基本都是这里算错了。

6. 从报错堆栈到问题定位:课程设计期间最常见的五类坑

6.1 ClassNotFoundException和SQLException的排查链路

写课程设计这几天,你大概率会碰上这么几个报错,我按出现频率排个序,直接把排查链路写给你们。以后遇到“红色异常”,不要慌,先看第一行英文。

java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver

这个报错90%的原因是jar包没导入,1%是版本过老写错类名。排查方式:进IDEA左侧的External Libraries或Eclipse的Referenced Libraries,搜一下有没有mysql-connector-j这个包。没有就重新导入;有的话再看你自己写的Class.forName里面的字符串是不是com.mysql.cj.jdbc.Driver而不是com.mysql.jdbc.Driver。

java.sql.SQLException: The server time zone value ...

这就是我前面说的serverTimezone参数漏了。在URL末尾加上&serverTimezone=Asia/Shanghai即可。如果加了还报错,检查你是不是在项目里同时导了旧版mysql-connector-java-5.x的jar包,把老jar删干净。

java.sql.SQLException: Access denied for user 'root'@'localhost'

密码错了,或者用户名写错。去DBUtil里检查你的USER和PASSWORD是不是对得上。还有一种情况是MySQL 8默认的认证插件是caching_sha2_password,但你的驱动版本太老,这也会报Access denied,解决办法是把驱动换成8.0以上版本。

Public Key Retrieval is not allowed

这是MySQL 8 + Connector/J 8.0的一个经典报错。原因是为了安全,驱动默认不自动获取服务器的公钥。最简单的解决办法是在URL上加上&allowPublicKeyRetrieval=true。

java.sql.SQLException: Column 'xxx' not found

检查SQL语句里的列名和表结构是否完全一致。注意MySQL的列名是不区分大小写的,所以问题一般出在拼写,不是大小写。

6.2 中文乱码、内存溢出、界面卡死等高频问题

比报错更让人崩溃的是“程序没报错,但结果不对”。中文乱码就是最典型的:数据库里中文正常,但Java程序读出来全是???。这通常涉及四个地方,你逐个检查:

  1. 连接URL有没有characterEncoding=utf8。
  2. 数据库和表的字符集是不是utf8mb4(建表语句里我写了,别改)。
  3. IDE的文件编码是否设置为UTF-8(IDEA设置里搜File Encodings,全改成UTF-8)。
  4. 如果你用控制台输出中文乱码,那就是终端编码问题,Windows下按Win+R输入cmd,执行chcp 65001切换UTF-8代码页再重启IDE,或者直接在IDE的Run Configuration里加-Dfile.encoding=UTF-8。

还有几个我实际帮人调试时看到过很多次的问题:

Swing界面点“查询”卡死几秒再弹结果——这是把耗时操作放在事件调度线程(EDT)里跑导致的。课程设计数据量小时感觉不明显,但查询慢的痕迹很丢分。解法是开个线程去跑数据库查询,查完通过SwingUtilities.invokeLater回到界面更新。你不需要深究并发理论,只要记住:按钮监听器里不要直接写数据库代码。

导出几千条数据时内存溢出报OutOfMemoryError——解法是分页查询再写文件,不要一次性SELECT *塞进List再写。Java里一个对象存几千条数据本来就吃内存,加上界面组件再占一份,很容易炸。

两个管理员同时给同一本书借书,库存变成-1——这属于并发问题,前面说的SELECT ... FOR UPDATE就是干这个的。课程设计答辩时,老师如果问“多个人同时借书怎么办”,你能说出这个方案,就已经超出大部分人水平了。

7. 答辩演示与评委提问:让评委觉得你真正懂这个系统的技巧

7.1 演示路径设计:先把“加分功能”放前面

答辩和讲课一样,开头三分钟决定了评委对你是“优秀”还是“及格”的初判。我见过太多学生上来就演示“登录”,而登录是所有系统都有的功能,等于你把最平庸的东西放在最前面。

我建议的演示顺序是:先把建议做和亮点功能亮出来。比如你做的是模糊搜索 + 分页 + 超期罚款计算,那就先输入一个不完整的关键词(比如搜“三”),瞬间显示分页结果,然后演示一本超期的书还书时自动弹罚款金额。这一套走完,评委心里已经默认你是“功能全面”的选手了。然后再回到“登录 → 图书管理 → 读者管理 → 借书还书”的主流程,把基础环节平顺过一遍,最后有时间再点一点代码里你觉得写得好的地方。

演示时还要注意数据准备。提前在数据库里造好一批像样的数据:十来本书、七八个读者、有一条已经超期的借阅记录。如果你的演示界面上全是空表,评委手动输数据等你演示,好感度会大打折扣。

7.2 高频提问与标准应答:从“怎么实现”到“为什么这样实现”

答辩题库其实很固定,我把出现频率最高的几个问题列一下,你按下面的思路准备,基本能覆盖80%的情况:

“你这个系统怎么防止SQL注入?”答:所有SQL操作都用PreparedStatement,参数通过setString、setInt绑定,不直接拼接字符串。PreparedStatement会由驱动程序对参数做转义,把用户输入当作数据而不是SQL语句解析。如果能再补一句“字符串查询用LIKE模糊匹配时,我会先把用户输入里的%和_转义掉”,评委眼睛会亮。

“图书表、读者表、借阅表之间什么关系?”答:图书和读者是多对多关系,通过borrow_record表充当中间表实现。每一条借阅记录关联一个读者和一本图书,同时记录了借出日期、应还日期、实还日期和状态。这里你站起来在黑板上画一下三张表,连线划清楚,这个题就稳过了。

“借书和还书如果同时发生,会不会出错?怎么解决?”答:会。我在借书时对图书行加了FOR UPDATE锁,同一本书的并发借书操作会被串行化。同时借书和还书两个操作放在一个事务里,任何一步失败都会回滚,不会出现记录和库存不一致。能答到这个深度,基本不用发愁了。

“你这个系统数据量变大了,比如到十万本书,会卡吗?”答:目前实现的查询都带条件过滤、分页,不会一次性加载全表。如果数据量进一步增长,可以考虑给title、author字段加索引,也可以用连接池管理连接,减少每次建立连接的开销。你不需要真去优化,但能说出思路就行。

“状态字段为什么用数字不用中文?”答:数字存储节省空间,比较时不需要做字符串匹配。更重要的是数据库里中文状态可读性差,一旦你改了业务规则就要刷数据,而用数字只要在Java的枚举或常量的映射里改对应关系即可。这里可以顺带提一句“修改一处,全局生效”,让老师感受到你理解了枚举常量的好处。

最后我还是想啰嗦一句,也是带过这么多届学生后最深的一个感受:课设拉开差距的时间点,从来不是最后三天,而是第一周你肯不肯静下心把表结构画出来、把每个按钮背后的方法列出来。你要是现在还没动手,别急着开IDE,先拿张白纸把“读者借书”这个动作从头到尾写一遍——从点哪个按钮,到调用哪个Service方法,再到改哪几张表——写清楚了你就会发现,代码反而没那么难写了。我见过太多“功能做得花里胡哨但数据库结构一塌糊涂”的项目,也见过“只有三个功能但分层清晰、异常处理到位”的作业拿了优秀,你要做哪一种,从打开IDE写下第一行CREATE TABLE的时候,就已经决定了。

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

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

立即咨询