ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Java图书馆系统:MVC+Swing+MySQL课设实战指南

Java图书馆系统:MVC+Swing+MySQL课设实战指南

简介:本资源是一份面向Java初学者与课程设计实践者的完整图书馆书库管理系统开发资料,聚焦于MVC架构下的Web应用开发全流程,解决图书录入、借阅管理、用户权限控制及数据统计等核心业务问题。压缩包共80个文件,含16个核心Java源码文件(涵盖Servlet、DAO、Bean等分层逻辑)、45个编译后class文件、MySQL数据库文件(.db/.mdb)及配套JSP页面资源(gif/jpg/ico等界面素材),整体951KB,结构清晰,便于导入IDE快速运行调试。已有387人学习下载,适合高校Java Web课程设计、毕业设计参考或Spring+MyBatis技术栈入门实践。读者可直接获取可运行的源代码、完整论文文档(含需求分析、数据库ER图、模块时序图及JDBC/Servlet/JSP关键技术实现详解),并结合源码深入理解MVC分层思想、借阅状态机设计、关键词检索逻辑与报表统计实现方法。

1. 一个能跑通、能改、能交差的图书馆系统:不是Demo,是带完整MVC分层+MySQL持久化+Swing界面的真实课设级工程

去年帮某高校计算机系A同学调试毕设时,他拿着一份“图书馆管理系统”的Java源码崩溃了——界面能弹出来,但点“添加图书”就报空指针,数据库连不上,连jdbc:mysql://localhost:3306/libdb这个URL里该填什么密码都找不到配置文件。后来翻出这份《JAVA图书馆书库管理系统设计(论文+源代码)》,从解压到跑通只用了22分钟:它不玩Spring Boot自动装配玄学,不塞Lombok编译插件黑匣子,就用最朴素的JDBC + Swing + MySQL 5.7原生组合,所有DAO层SQL手写、所有事件监听器明文注册、所有表结构在doc/数据库设计说明.txt里列得清清楚楚。适合两类人:一是大二大三刚学完Java基础和MySQL,需要一个真实可运行、可调试、可截图交作业的完整项目;二是想快速验证MVC分层思想、练手JDBC事务控制、或给Swing界面加新功能的实践者。它没用任何云服务、没调外部API、不依赖Docker,纯本地Java SE环境就能拉起来——这才是课设该有的样子。


2. 环境准备与项目结构解析:看清这12个核心文件怎么咬合在一起

2.1 JDK与MySQL版本实测兼容清单

本项目基于Java SE 8u202开发,经实测在JDK 8u181 ~ 8u291范围内全部稳定运行。MySQL要求5.6及以上,强烈建议用MySQL 5.7.32(这是A同学实验室机房统一镜像版本)。为什么不是MySQL 8?因为项目中com.mysql.jdbc.Driver驱动类在8.0+已被弃用,若强行升级需同步修改DBUtil.java里的驱动加载逻辑。JDK 11+也能跑,但需注意Swing组件在高DPI屏幕下的缩放问题(后文避坑章节会细说)。

提示:不要用MySQL 8.0默认的caching_sha2_password认证插件。安装时务必选择Legacy Authentication Method (mysql_native_password),否则DBUtil.getConnection()会抛Access denied for user异常——这是新手翻车第一高频点。

2.2 项目目录结构:12个关键文件定位指南

解压后你会看到4个一级目录:src/、lib/、doc/、sql/。下面这张表不是罗列,而是告诉你每个文件在运行时扮演什么角色、改哪里会影响什么:

路径文件名关键作用修改风险提示
src/Main.java程序入口,调用LoginFrame启动登录窗口改这里可能让整个程序起不来,慎动
src/dao/BookDao.java图书增删改查核心DAO,所有SQL语句在此新增字段必改此文件的insertBook()和updateBook()方法
src/util/DBUtil.java数据库连接池管理,含getConnection()和closeConnection()密码、端口、数据库名全在这里配,第一个要改的文件
src/entity/Book.java图书实体类,字段与book表严格对应添加ISBN校验?在此加@Override toString()前插入校验逻辑
src/view/MainFrame.java主界面(含菜单栏、工具栏、图书列表Table)想加“按出版社筛选”按钮?在此initComponents()里补JButton并绑定事件
lib/mysql-connector-java-5.1.47.jarJDBC驱动包,必须与MySQL 5.7匹配若换MySQL 8,需替换为mysql-connector-java-8.0.28.jar并改驱动类名
sql/libdb.sql完整建库建表SQL,含book、reader、borrow三张表执行前先用CREATE DATABASE libdb CHARACTER SET utf8mb4;建库
doc/数据库设计说明.txt字段含义、主外键关系、索引说明(比如book.isbn有唯一索引)borrow表的return_date允许NULL,代表未归还——业务逻辑依据

特别注意src/下没有package-info.java,所有类都在默认包(default package)下。这意味着你导入IDE时不能直接Import Project,必须Import Module并勾选“Create module from existing sources”,否则编译报package does not exist。

2.3 从零配置MySQL:三步走通数据链路

别急着写代码,先让数据库说话。打开sql/libdb.sql,你会看到类似这样的建表语句:

CREATE TABLE `book` ( `id` int(11) NOT NULL AUTO_INCREMENT, `isbn` varchar(20) NOT NULL, `title` varchar(100) NOT NULL, `author` varchar(50) DEFAULT NULL, `publisher` varchar(50) DEFAULT NULL, `pub_date` date DEFAULT NULL, `price` decimal(10,2) DEFAULT NULL, `stock` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `isbn` (`isbn`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

执行前请确认:

  1. 已创建名为libdb的数据库(字符集utf8mb4,排序规则utf8mb4_unicode_ci)
  2. 用户root密码为空或已知(项目默认配置是root/空密码)
  3. MySQL服务正在运行(Windows下检查服务名MySQL57是否启动)

执行libdb.sql后,用命令行验证数据是否就位:

mysql -uroot -p -D libdb -e "SELECT COUNT(*) FROM book;"

返回COUNT(*)为0是正常的——初始无数据,但表结构必须存在。如果报Unknown database 'libdb',说明建库失败;报Table 'libdb.book' doesn't exist,说明SQL没执行成功。


3. 核心模块拆解:BookDao与DBUtil如何协作完成一次借书操作

3.1 DBUtil:连接池虽小,但藏着三个关键设计点

src/util/DBUtil.java只有87行,却是整个数据访问的基石。它没用C3P0或Druid,而是用Vector<Connection>手写简易连接池。看这段获取连接的核心逻辑:

public static Connection getConnection() { Connection conn = null; try { // 1. 加载驱动(MySQL 5.x专用) Class.forName("com.mysql.jdbc.Driver"); // 2. 构建URL:注意useSSL=false和serverTimezone=GMT%2B8 String url = "jdbc:mysql://localhost:3306/libdb?useSSL=false&serverTimezone=GMT%2B8"; // 3. 获取连接,用户名密码硬编码——课设简化,生产环境必须抽离 conn = DriverManager.getConnection(url, "root", ""); } catch (Exception e) { e.printStackTrace(); } return conn; }

关键参数说明:

  • useSSL=false:MySQL 5.7默认要求SSL,不加此参数会报SSL connection error
  • serverTimezone=GMT%2B8:解决The server time zone value 'й׼ʱ' is unrecognized时区错误,%2B是+的URL编码
  • DriverManager.getConnection()返回的是物理连接,不是连接池中的复用连接——所以每次getConnection()都新建连接,closeConnection()才真正关闭。这种设计牺牲性能换可读性,正适合教学场景。

注意:closeConnection()方法里有conn.close(),但没做null判空。如果getConnection()返回null(比如数据库宕机),此处会NPE。实际部署时应加if (conn != null && !conn.isClosed()) conn.close();。

3.2 BookDao:一条SQL背后的三层封装

以“根据ISBN查询图书”为例,BookDao.java中这个方法值得逐行拆解:

public Book findBookByIsbn(String isbn) { Book book = null; Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); // 1. 从DBUtil拿连接 String sql = "SELECT * FROM book WHERE isbn = ?"; // 2. 预编译SQL,防SQL注入 pstmt = conn.prepareStatement(sql); // 3. 创建PreparedStatement pstmt.setString(1, isbn); // 4. 设置参数,索引从1开始! rs = pstmt.executeQuery(); // 5. 执行查询 if (rs.next()) { // 6. 移动游标到第一行(有结果才进) book = new Book(); // 7. 实例化实体 book.setId(rs.getInt("id")); // 8. 字段映射,注意类型匹配 book.setIsbn(rs.getString("isbn")); book.setTitle(rs.getString("title")); book.setAuthor(rs.getString("author")); book.setPublisher(rs.getString("publisher")); book.setPubDate(rs.getDate("pub_date")); // Date类型需用getDate() book.setPrice(rs.getBigDecimal("price")); // BigDecimal对应decimal book.setStock(rs.getInt("stock")); } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.closeConnection(conn, pstmt, rs); // 9. 统一关闭资源 } return book; // 10. 返回实体,null表示未找到 }

这个方法体现了JDBC标准流程:获取连接 → 预编译SQL → 设置参数 → 执行 → 处理结果 → 关闭资源。其中第4步pstmt.setString(1, isbn)的参数索引必须从1开始(不是0),第8步rs.getDate("pub_date")必须用getDate()而非getString(),否则日期格式错乱——这些细节在课设答辩时老师最爱问。

3.3 借书业务链路:从MainFrame点击到数据库落库的完整路径

用户在主界面点击“借阅”按钮后,发生了什么?我们顺着调用栈捋一遍:

  1. MainFrame.java中borrowBtn.addActionListener(...)触发
  2. 调用BorrowService.borrowBook(readerId, bookId)(src/service/BorrowService.java)
  3. BorrowService先查BookDao.findBookById(bookId)确认库存>0
  4. 再查ReaderDao.findReaderById(readerId)确认读者状态正常
  5. 开启事务:conn.setAutoCommit(false)
  6. 执行两条SQL:
    • INSERT INTO borrow(reader_id, book_id, borrow_date) VALUES (?, ?, ?)
    • UPDATE book SET stock = stock - 1 WHERE id = ?
  7. 若任一SQL失败,conn.rollback()回滚
  8. 成功则conn.commit(),刷新界面表格

这个流程把事务控制、业务校验、SQL执行全放在Service层,符合MVC中Model层职责分离。如果你想加“借阅数量限制”(如每人最多借5本),就在第4步后加int count = borrowDao.countByReaderId(readerId); if (count >= 5) throw new BusinessException("已达借阅上限");——改这里,不动DAO和View。


4. 运行与调试实战:从双击jar到定位NullPointerException的全流程

4.1 两种启动方式:IDE内运行 vs 双击jar包

方式一:IDE内运行(推荐调试)
在IntelliJ IDEA中:

  • File → Project Structure → Project → Project SDK选JDK 1.8
  • Modules → Sources → 将src/设为Sources root
  • Run → Edit Configurations → + → Application → Main class填Main
  • 点击Run,登录窗口弹出

此时断点打在LoginFrame.java的loginBtn.addActionListener里,能清晰看到用户名密码如何传入UserDao.login()。

方式二:双击jar包(模拟交付)
项目根目录下有libdb_system.jar,双击运行需满足:

  • Windows系统已安装JRE 1.8(非JDK)
  • lib/目录与jar包同级(jar包MANIFEST.MF中Class-Path: lib/mysql-connector-java-5.1.47.jar)
  • MySQL服务正在运行且libdb库已初始化

如果双击无反应,打开命令行执行:

java -jar libdb_system.jar

错误堆栈会直接打印在终端——这是定位NoClassDefFoundError(缺jar包)或SQLException(连不上库)的最快方式。

4.2 登录失败的三大原因与速查法

现象:输入admin/admin后提示“登录失败”,控制台无输出
原因与解法:

  • 原因1:MySQL服务未启动
    查法:netstat -ano | findstr :3306(Windows)或lsof -i :3306(Mac/Linux),无输出即未启动
    解法:启动MySQL服务(Windows服务管理器找MySQL57)

  • 原因2:DBUtil.java中密码错误
    查法:在DBUtil.getConnection()里加System.out.println("Connecting with pwd: [" + password + "]");
    解法:确认MySQL root密码,修改DBUtil.java第22行DriverManager.getConnection(..., "root", "your_password");

  • 原因3:libdb库不存在或表结构不匹配
    查法:mysql -uroot -p -e "USE libdb; SHOW TABLES;",若报Unknown database或返回空,则库/表缺失
    解法:重新执行sql/libdb.sql

提示:所有数据库操作异常都会被catch (SQLException e)捕获并e.printStackTrace(),但Swing界面不会显示。调试时务必开IDE控制台或命令行运行,否则等于盲人摸象。

4.3 Swing界面适配高DPI屏:字体模糊、按钮错位的救急方案

在4K屏或Windows缩放125%的笔记本上,你会发现:

  • 表格文字极小,像蚂蚁爬
  • “添加图书”按钮部分被截断
  • 弹窗位置偏右上角

这是因为Swing默认不启用DPI感知。救急方案(无需改代码):

  1. 右键libdb_system.jar→ 属性 → 兼容性 → 更改高DPI设置 → 勾选“替代高DPI缩放行为” → 选择“系统(增强)”
  2. 或在启动脚本中加JVM参数:
    java -Dsun.java2d.uiScale=1.25 -jar libdb_system.jar
    1.25对应125%缩放,1.5对应150%

若要代码级修复,在Main.java的main()方法开头加:

// 启用系统DPI缩放 System.setProperty("sun.java2d.uiScale", "1.25"); // 或强制使用Java内置缩放(更稳定) System.setProperty("sun.java2d.win.uiScaleX", "1.25"); System.setProperty("sun.java2d.win.uiScaleY", "1.25");

5. 常见问题排查:那些让课设答辩前夜崩溃的5个血泪坑

5.1 现象:点击“添加图书”报java.lang.NullPointerException,堆栈指向BookDao.insertBook()第45行

原因:DBUtil.getConnection()返回null,但BookDao没判空直接调用conn.prepareStatement()
解决:在BookDao.insertBook()开头加判空:

if (conn == null) { throw new RuntimeException("数据库连接获取失败,请检查MySQL服务及DBUtil配置"); }

同时确认DBUtil.java中Class.forName("com.mysql.jdbc.Driver")没被IDE自动优化删掉(有些IDE会标黄提示“unused”,千万别删!)

5.2 现象:中文插入MySQL后变成???,但数据库字符集已设为utf8mb4

原因:JDBC URL缺少characterEncoding=utf8mb4参数,且MySQL服务器my.cnf中[client]段未配default-character-set = utf8mb4
解决:

  1. 修改DBUtil.java的URL:
    String url = "jdbc:mysql://localhost:3306/libdb?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8mb4";
  2. 检查MySQL配置文件my.cnf(Windows在C:\ProgramData\MySQL\MySQL Server 5.7\my.ini),确保有:
    [client] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci

5.3 现象:MainFrame中图书列表Table显示空白,但数据库有数据

原因:BookTableModel.java(src/model/下)的getValueAt(int row, int column)方法中,row索引超出了bookList.size()
解决:检查BookTableModel构造方法中是否正确调用this.bookList = bookDao.findAllBooks(),并在getValueAt()开头加保护:

if (row < 0 || row >= bookList.size()) { return null; // 防止越界 }

5.4 现象:打包成jar后,lib/mysql-connector-java-5.1.47.jar不生效,报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver

原因:IDE打包时未将lib/下jar包打入classpath,或MANIFEST.MF中Class-Path路径错误
解决:

  • IntelliJ IDEA:File → Project Structure → Artifacts → + → JAR → From modules with dependencies → 选Main Class → Output Layout中确认lib/目录被包含
  • 手动检查生成的jar包内META-INF/MANIFEST.MF,必须有:
    Class-Path: lib/mysql-connector-java-5.1.47.jar

5.5 现象:修改Book.java增加isbnCheck字段后,BookDao.findBookByIsbn()返回对象该字段始终为null

原因:BookDao中ResultSet映射只处理了原始字段,新增字段未在while(rs.next())循环内赋值
解决:在BookDao.findBookByIsbn()的if (rs.next())块内,添加:

book.setIsbnCheck(rs.getString("isbn_check")); // 假设数据库表加了isbn_check字段

并同步在Book.java中添加private String isbnCheck;及getter/setter。


6. 二次开发实战:给系统加“图书到期提醒”功能的完整改造路径

6.1 需求分析:从用户场景反推技术动作

用户说:“老师,能不能加个功能,显示哪些书快到期了?”
这不是简单加个按钮。我们得拆解:

  • 数据源:borrow表中有return_date(计划归还日),但当前为空(代表未归还);borrow_date(借出日)是必填的
  • 计算逻辑:到期日 =borrow_date+ 30天(假设借期30天),需对比当前日期
  • 展示位置:在MainFrame的菜单栏加“提醒”→“即将到期图书”,点击弹出JTable显示book.title、reader.name、borrow_date、due_date
  • 技术边界:不改现有表结构(避免影响原有功能),用视图或计算字段实现

6.2 四步改造:代码级落地指南

步骤1:扩展数据库查询 —— 在BookDao中加新方法

在src/dao/BookDao.java末尾添加:

/** * 查询即将到期的图书(借出30天内到期) * SQL原理:计算borrow_date + 30天,再与当前日期比较 * 注意:MySQL 5.7用DATE_ADD,不支持INTERVAL 30 DAY简写 */ public List<BorrowInfo> findDueBooks() { List<BorrowInfo> list = new ArrayList<>(); Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); // 关键SQL:关联borrow、book、reader三表,计算到期日 String sql = "SELECT b.title, r.name, br.borrow_date, " + "DATE_ADD(br.borrow_date, INTERVAL 30 DAY) AS due_date " + "FROM borrow br " + "JOIN book b ON br.book_id = b.id " + "JOIN reader r ON br.reader_id = r.id " + "WHERE DATE_ADD(br.borrow_date, INTERVAL 30 DAY) BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY)"; pstmt = conn.prepareStatement(sql); rs = pstmt.executeQuery(); while (rs.next()) { BorrowInfo info = new BorrowInfo(); info.setTitle(rs.getString("title")); info.setReaderName(rs.getString("name")); info.setBorrowDate(rs.getDate("borrow_date")); info.setDueDate(rs.getDate("due_date")); list.add(info); } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.closeConnection(conn, pstmt, rs); } return list; }

注意:BorrowInfo.java需新建在src/entity/下,包含title、readerName、borrowDate、dueDate四个字段及getter/setter。这里不复用Book类,因返回的是借阅关系视图,非单纯图书信息。

步骤2:新增菜单项与事件处理器 —— 改造MainFrame

在src/view/MainFrame.java的initMenuBar()方法中(约第120行),找到JMenu systemMenu,在其后插入:

// 新增“提醒”菜单 JMenu remindMenu = new JMenu("提醒"); JMenuItem dueItem = new JMenuItem("即将到期图书"); dueItem.addActionListener(e -> showDueBooksDialog()); menuBar.add(remindMenu); remindMenu.add(dueItem);

然后在MainFrame类末尾添加事件处理方法:

private void showDueBooksDialog() { List<BorrowInfo> dueList = new BookDao().findDueBooks(); if (dueList.isEmpty()) { JOptionPane.showMessageDialog(this, "暂无即将到期的图书", "提醒", JOptionPane.INFORMATION_MESSAGE); return; } // 构建表格模型 String[] columns = {"图书标题", "读者姓名", "借出日期", "到期日期"}; DefaultTableModel model = new DefaultTableModel(columns, 0); for (BorrowInfo info : dueList) { Object[] row = { info.getTitle(), info.getReaderName(), info.getBorrowDate(), info.getDueDate() }; model.addRow(row); } JTable table = new JTable(model); table.setRowHeight(25); JScrollPane scrollPane = new JScrollPane(table); scrollPane.setPreferredSize(new Dimension(600, 400)); JOptionPane.showMessageDialog(this, scrollPane, "即将到期图书", JOptionPane.PLAIN_MESSAGE); }
步骤3:验证与测试 —— 三组数据覆盖边界

在MySQL中手动插入测试数据:

-- 测试1:今天借的,30天后到期(应显示) INSERT INTO borrow(reader_id, book_id, borrow_date) VALUES (1, 1, CURDATE()); -- 测试2:25天前借的,5天后到期(应显示) INSERT INTO borrow(reader_id, book_id, borrow_date) VALUES (2, 2, DATE_SUB(CURDATE(), INTERVAL 25 DAY)); -- 测试3:40天前借的,已过期(不应显示) INSERT INTO borrow(reader_id, book_id, borrow_date) VALUES (3, 3, DATE_SUB(CURDATE(), INTERVAL 40 DAY));

重启程序,点击“提醒→即将到期图书”,应看到前两行数据,第三行不出现。

步骤4:健壮性加固 —— 防御式编程补漏
  • 在showDueBooksDialog()开头加SwingUtilities.invokeLater(() -> { ... });,确保Swing线程安全
  • findDueBooks()中SQL的BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY)可改为参数化,方便后期改成“未来14天”:
    String sql = "SELECT ... WHERE DATE_ADD(br.borrow_date, INTERVAL 30 DAY) BETWEEN ? AND ?"; pstmt.setDate(1, new java.sql.Date(System.currentTimeMillis())); pstmt.setDate(2, new java.sql.Date(System.currentTimeMillis() + 7L * 24 * 60 * 60 * 1000));
  • 为BorrowInfo类重写toString(),方便调试时System.out.println(dueList)可读

从那以后我每次加新功能,都强制走一遍“需求→SQL→DAO→Service→View→测试数据→边界验证”闭环,哪怕只是加个按钮。因为课设系统最怕的不是功能少,而是改一处崩三处——而这个闭环就是我的后悔药。希望帮到你。

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

返回列表