ARTICLE DETAIL

资讯详情

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

JavaWeb图书管理系统课程设计:环境搭建、数据库设计与核心功能实现

JavaWeb图书管理系统课程设计:环境搭建、数据库设计与核心功能实现 简介这是一套面向高校计算机相关专业学生的JavaWeb课程设计完整方案以图书管理系统为主题适合正在准备课程设计、毕业设计或需要JavaWeb实战练手项目的学习者。资源包含完整源码与数据库脚本代码结构清晰、功能模块齐全下载后可直接运行省去从零搭建的繁琐过程。压缩包共352个文件约64.65MB涵盖37个java源文件、91个class编译文件、14个html页面、13个css与12个js前端资源以及5个sql数据库脚本、11个xml配置和41个xls测试数据另附jar依赖包与properties配置文件覆盖图书信息管理、用户信息维护、Excel数据导入导出等核心业务模块。目前已有448人学习下载可作为课程设计参考模板帮助读者快速理解JavaWeb分层开发思路、数据库表结构设计与前后端交互流程并在此基础上进行功能扩展与二次开发。1. 图书管理系统课程设计从能跑起来到敢交上去中间隔着什么每年学期末总有一批人对着“基于javaweb的图书管理系统源码数据库”这个压缩包发愁。解压之后一堆文件夹不知道从哪下手用 IDEA 打开Tomcat 一启动就报 404数据库导进去了登录页面却提示密码错误。这不是能力问题是这类课程设计项目天然存在一个断层——它默认你知道 JavaWeb 的整套运行链路但课堂上讲 MVC 的时候你多半在打瞌睡。这篇东西要解决的就是这个断层。我会按一个真实的图书管理系统该有的样子把环境搭建、数据库设计、核心功能实现、常见报错排查这几件事拆开讲清楚。适合两类人一类是拿到源码但跑不起来的另一类是想自己从零写一个能拿 95 分以上的课程设计的。核心词就三个javaweb、图书管理系统、数据库。读完你至少能做到——IDEA 里配好 Tomcat 跑通登录知道借阅记录表该怎么设计遇到 500 错误知道先看哪里。2. 环境与项目结构IDEA 跑 JavaWeb 项目最容易翻车的三个配置2.1 JDK、Tomcat、MySQL 的版本匹配不是玄学课程设计项目最常见的翻车场景源码是几年前写的用的 JDK 8 Tomcat 8 MySQL 5.7你本地装的是 JDK 17 Tomcat 10 MySQL 8然后启动就炸。这不是代码写得烂是版本兼容性问题。先看一张版本对应关系表这是我在多个课程设计项目里验证过的组合组件推荐版本兼容范围不兼容表现JDK8 或 118-11JDK 17 下 javax.servlet 包直接消失Tomcat8.5 或 98.5-9Tomcat 10 把 javax.* 改成 jakarta.*所有 Servlet 报错MySQL5.7 或 8.05.7-8.08.0 需要改连接串时区参数MySQL 驱动5.1.x 或 8.0.x跟随 MySQL驱动版本和数据库版本不匹配报 Communications link failureTomcat 10 那个坑值得单独说。从 Tomcat 10 开始Servlet API 的包名从javax.servlet变成了jakarta.servlet。你拿到的课程设计源码里import 语句全是javax.servlet.*放到 Tomcat 10 里一个都找不到。解决办法要么换 Tomcat 9要么全局替换包名——但替换之后如果用了其他依赖 javax 的库又是一连串问题。所以最省事的做法就是 Tomcat 9。JDK 版本同理。JDK 8 到 JDK 11 之间JavaWeb 相关的 API 基本没动。但 JDK 17 移除了 Java EE 模块javax.servlet直接没了。你可以在 IDEA 的 Project Structure 里把 SDK 切到 8 或 11不需要卸载本地的 JDK 17。MySQL 8.0 的时区问题也经常遇到。连接串里不加serverTimezoneAsia/Shanghai启动就报The server time zone value ?D1ú±ê×?ê±?? is unrecognized。这不是乱码是 MySQL 8 默认时区名和 JDBC 驱动解析不一致。2.2 用 IDEA 导入项目并配置 Tomcat 的完整步骤拿到一个压缩包解压后目录结构通常是这样的library-system/ ├── src/ │ └── com/library/ │ ├── dao/ │ ├── entity/ │ ├── servlet/ │ └── util/ ├── web/ │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ │ ├── css/ │ ├── js/ │ └── login.jsp └── library.sql在 IDEA 里操作按这个顺序走第一步File → New → Project from Existing Sources选项目根目录IDEA 会自动识别为 Java 项目。如果没识别出来手动在 Project Structure 里把src标记为 Sources Root把web标记为 Web Resource Directory。第二步配置 Artifacts。File → Project Structure → Artifacts → → Web Application: Exploded → From Modules。这一步决定了 Tomcat 启动时加载哪些文件。如果 Artifacts 没配Tomcat 启动后访问任何页面都是 404。第三步配置 Tomcat Server。Run → Edit Configurations → → Tomcat Server → Local。在 Server 标签页指定 Tomcat 安装目录在 Deployment 标签页点 → Artifact选刚才配好的 exploded 包。Application context 建议设成/library这样访问地址就是http://localhost:8080/library/login.jsp。第四步把 MySQL 驱动 jar 包放进web/WEB-INF/lib/目录。注意不是放在项目根目录的 lib 里是 WEB-INF 下面的 lib。IDEA 有时候不会自动把这个目录加入 classpath需要在 Project Structure 的 Libraries 里手动添加。第五步改数据库连接配置。找到src/com/library/util/下面的 DBUtil 或 JDBCUtils 类把 url、username、password 改成你本地的。连接串模板// MySQL 8.0 连接串注意时区和 SSL 参数 private static final String URL jdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse; private static final String USER root; private static final String PASSWORD 你的密码;useUnicodetruecharacterEncodingutf8解决中文乱码serverTimezoneAsia/Shanghai解决时区报错useSSLfalse去掉 SSL 警告。这三个参数在 MySQL 8 环境下基本是必加的。2.3 数据库导入与连接测试拿到library.sql文件后不要直接双击运行。先建库再导表。-- 先创建数据库字符集用 utf8mb4 CREATE DATABASE library DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 切换到该数据库 USE library; -- 然后执行 sql 文件里的建表语句 -- 如果 sql 文件里没有 CREATE DATABASE 和 USE 语句需要手动加上导入方式有两种。命令行方式mysql -u root -p library library.sql图形化工具方式用 Navicat 或 DBeaver 连上 MySQL右键数据库选“运行 SQL 文件”选中 library.sql 执行。注意如果 sql 文件里已经包含了CREATE DATABASE语句而你本地已经有同名库会报错。可以先DROP DATABASE library;再导入。导入完成后验证三件事表数量对不对通常 4-6 张表用户表、图书表、借阅记录表、分类表等管理员账号能不能查到SELECT * FROM admin;中文有没有乱码看图书标题字段。连接测试可以在 IDEA 里写一个 main 方法public class TestConnection { public static void main(String[] args) { try (Connection conn DriverManager.getConnection(URL, USER, PASSWORD)) { System.out.println(数据库连接成功 conn.getMetaData().getDatabaseProductVersion()); } catch (SQLException e) { System.out.println(连接失败 e.getMessage()); } } }如果报ClassNotFoundException: com.mysql.cj.jdbc.Driver说明驱动 jar 没加进 classpath。如果报Access denied for user说明用户名密码不对。如果报Unknown database library说明库没建。3. 数据库设计图书管理系统四张核心表怎么建才不返工3.1 表结构设计从借阅记录表看关联关系图书管理系统的数据库设计核心就四张表用户表、图书表、借阅记录表、图书分类表。其中借阅记录表是关联的中心设计不好后面写查询会非常痛苦。先看用户表。课程设计里通常分管理员和普通用户两种角色可以用一张表加角色字段也可以分两张表。我一般用一张表CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT 密码, real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, role tinyint(1) DEFAULT 0 COMMENT 0-普通用户 1-管理员, status tinyint(1) DEFAULT 1 COMMENT 1-正常 0-禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;role字段用 tinyint 而不是 varchar查询效率更高代码里判断也简单。status字段用于软删除用户被禁用后数据还在只是不能登录。图书表的关键字段CREATE TABLE book ( id int(11) NOT NULL AUTO_INCREMENT, isbn varchar(20) DEFAULT NULL, title varchar(200) NOT NULL, author varchar(100) DEFAULT NULL, publisher varchar(100) DEFAULT NULL, category_id int(11) DEFAULT NULL, total_count int(11) DEFAULT 1 COMMENT 总库存, available_count int(11) DEFAULT 1 COMMENT 可借数量, location varchar(50) DEFAULT NULL COMMENT 馆藏位置, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id), KEY idx_title (title) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;total_count和available_count分开存不要用计算字段。借书时available_count - 1还书时 1。这样查询“当前可借图书”只需要WHERE available_count 0不用联表算。借阅记录表是最容易设计出问题的CREATE TABLE borrow_record ( id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL, book_id int(11) NOT NULL, borrow_date datetime DEFAULT CURRENT_TIMESTAMP, due_date datetime DEFAULT NULL COMMENT 应还日期, return_date datetime DEFAULT NULL COMMENT 实际归还日期, status tinyint(1) DEFAULT 0 COMMENT 0-借出 1-已还 2-逾期, PRIMARY KEY (id), KEY idx_user (user_id), KEY idx_book (book_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;due_date在借书时就算好一般是借出日期加 30 天。return_date默认为 NULL还书时才写入。status字段让查询“当前借出的书”变成WHERE status 0不用比较日期。3.2 增删改查 SQL 与索引优化课程设计里最常写的查询是“查某用户当前借了哪些书”SQL 长这样SELECT b.title, b.author, br.borrow_date, br.due_date FROM borrow_record br JOIN book b ON br.book_id b.id WHERE br.user_id ? AND br.status 0 ORDER BY br.borrow_date DESC;这条 SQL 在数据量小的时候没问题但如果你在borrow_record表的user_id和status上没建索引数据量到几千条就会明显变慢。上面建表语句里已经加了idx_user和idx_status但更好的做法是建联合索引ALTER TABLE borrow_record ADD INDEX idx_user_status (user_id, status);联合索引的顺序很重要。WHERE user_id ? AND status 0这种查询(user_id, status)的索引能直接定位而单独的两个索引 MySQL 只会用一个。另一个常见查询是“查某本书的借阅历史”SELECT u.real_name, br.borrow_date, br.return_date FROM borrow_record br JOIN user u ON br.user_id u.id WHERE br.book_id ? ORDER BY br.borrow_date DESC;这个查询走idx_book索引。如果同时要查借阅中的和已归还的可以加AND br.status IN (0, 1)。分页查询在图书列表页会用到SELECT * FROM book WHERE title LIKE CONCAT(%, ?, %) LIMIT ?, ?;LIMIT的偏移量在数据量大时性能会下降但课程设计的数据量通常几百条不用考虑这个问题。如果非要优化可以用WHERE id ? LIMIT ?的方式但没必要。3.3 事务处理借书还书为什么必须加事务借书操作涉及两张表的更新borrow_record插入一条记录book表的available_count减一。这两步必须在一个事务里否则可能出现“记录插入了但库存没减”或者反过来。public boolean borrowBook(int userId, int bookId) { Connection conn null; PreparedStatement ps1 null, ps2 null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 第一步检查库存 String checkSql SELECT available_count FROM book WHERE id ? FOR UPDATE; ps1 conn.prepareStatement(checkSql); ps1.setInt(1, bookId); ResultSet rs ps1.executeQuery(); if (!rs.next() || rs.getInt(available_count) 0) { conn.rollback(); return false; } // 第二步插入借阅记录 String insertSql INSERT INTO borrow_record (user_id, book_id, due_date, status) VALUES (?, ?, DATE_ADD(NOW(), INTERVAL 30 DAY), 0); ps2 conn.prepareStatement(insertSql); ps2.setInt(1, userId); ps2.setInt(2, bookId); ps2.executeUpdate(); // 第三步扣减库存 String updateSql UPDATE book SET available_count available_count - 1 WHERE id ? AND available_count 0; ps2 conn.prepareStatement(updateSql); ps2.setInt(1, bookId); int affected ps2.executeUpdate(); if (affected 0) { conn.rollback(); return false; } conn.commit(); return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { // 关闭资源恢复 autoCommit } }SELECT ... FOR UPDATE在事务里锁住那行记录防止并发借同一本书时超借。UPDATE ... WHERE available_count 0是第二道保险即使锁没生效库存也不会变成负数。还书操作同理更新borrow_record的return_date和status同时book表的available_count加一。两个操作必须在一个事务里。4. 核心功能实现登录、借阅、权限过滤的代码骨架4.1 登录功能从 JSP 表单到 Servlet 验证登录流程是 JavaWeb 最基础的链路JSP 页面提交表单 → Servlet 接收参数 → 调 DAO 查数据库 → 返回结果到 JSP。login.jsp 的表单部分form action${pageContext.request.contextPath}/LoginServlet methodpost input typetext nameusername placeholder用户名 required input typepassword namepassword placeholder密码 required button typesubmit登录/button /form${pageContext.request.contextPath}获取项目上下文路径避免硬编码/library。如果部署时改了 context path这里会自动适配。LoginServlet 的 doPost 方法protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); UserDao userDao new UserDao(); User user userDao.findByUsernameAndPassword(username, password); if (user ! null) { if (user.getStatus() 0) { request.setAttribute(msg, 账号已被禁用); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } HttpSession session request.getSession(); session.setAttribute(currentUser, user); if (user.getRole() 1) { response.sendRedirect(request.getContextPath() /admin/index.jsp); } else { response.sendRedirect(request.getContextPath() /user/index.jsp); } } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } }几个关键点密码在数据库里存的是明文还是 MD5课程设计里明文居多但如果你想拿高分加一个 MD5 工具类存库前加密查询时也加密再比对。session.setAttribute(currentUser, user)把用户信息存进 session后续页面通过 session 判断是否登录。UserDao 的查询方法public User findByUsernameAndPassword(String username, String password) { String sql SELECT * FROM user WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); ResultSet rs ps.executeQuery(); if (rs.next()) { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setRealName(rs.getString(real_name)); user.setRole(rs.getInt(role)); user.setStatus(rs.getInt(status)); return user; } } catch (SQLException e) { e.printStackTrace(); } return null; }用 try-with-resources 自动关闭连接比手动 finally 里 close 更简洁。PreparedStatement 防 SQL 注入这是基本要求。4.2 借阅与归还Servlet 里的业务逻辑借书 Servlet 的核心逻辑protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session request.getSession(); User user (User) session.getAttribute(currentUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } int bookId Integer.parseInt(request.getParameter(bookId)); BorrowService borrowService new BorrowService(); boolean success borrowService.borrowBook(user.getId(), bookId); if (success) { response.sendRedirect(request.getContextPath() /user/borrowList.jsp); } else { request.setAttribute(msg, 借阅失败库存不足或已达借阅上限); request.getRequestDispatcher(/user/bookList.jsp).forward(request, response); } }借阅上限的判断可以放在 Service 层查该用户当前借出未还的记录数超过 5 本就不让借。这个规则课程设计里通常有要求不加会扣分。还书 Servlet 类似传入recordId调 Service 的returnBook方法更新记录状态和库存。4.3 权限过滤器一个 Filter 挡住未登录访问没有过滤器的话用户直接在地址栏输/admin/index.jsp就能进管理后台。加一个 FilterWebFilter(urlPatterns {/admin/*, /user/*}) public class AuthFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(); User user (User) session.getAttribute(currentUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } String uri request.getRequestURI(); if (uri.contains(/admin/) user.getRole() ! 1) { response.sendRedirect(request.getContextPath() /user/index.jsp); return; } chain.doFilter(req, resp); } }WebFilter注解需要web.xml里metadata-completefalse或者不写这个属性。如果 web.xml 里写了metadata-completetrue注解不生效需要在 web.xml 里手动配置 filter。5. 避坑与排查启动报错、乱码、连接池的五个血泪经验5.1 404Tomcat 启动了但访问不到页面现象IDEA 控制台显示 Tomcat started浏览器访问http://localhost:8080/library/login.jsp返回 404。原因通常有三个。第一Artifact 没配或配错了。检查 Project Structure → Artifacts看 Output Layout 里有没有你的 web 目录内容。第二Application context 设错了。在 Run Configuration 的 Deployment 标签页看 Application context 是什么如果是/那访问地址就是http://localhost:8080/login.jsp。第三web.xml 里 welcome-file 配的首页文件不存在。解决先看 Tomcat 启动日志里有没有Deploying web application archive或Deploying web application directory确认项目被部署了。然后检查浏览器地址栏的路径和 context path 是否一致。5.2 500空指针和数据库连接失败现象页面报 HTTP 500控制台一堆异常堆栈。原因最常见的是DBUtil.getConnection()返回 null然后调用方法时空指针。DBUtil 返回 null 的原因又通常是驱动类没加载或连接参数不对。解决在 DBUtil 的静态代码块里加Class.forName(com.mysql.cj.jdbc.Driver)并确认 WEB-INF/lib 下有 mysql-connector-java 的 jar 包。如果用的是 MySQL 5.x驱动类名是com.mysql.jdbc.Driver8.x 是com.mysql.cj.jdbc.Driver写错了就报 ClassNotFoundException。5.3 中文乱码从请求到响应到数据库的三层排查现象表单提交的中文在数据库里变成问号或者页面显示乱码。原因分三层。请求层POST 请求默认用 ISO-8859-1 解码需要在 Servlet 里request.setCharacterEncoding(UTF-8)。响应层response.setContentType(text/html;charsetUTF-8)。数据库层连接串里加characterEncodingutf8建库时用utf8mb4。解决在 Filter 里统一设置请求和响应编码比在每个 Servlet 里写更省事。WebFilter(/*) public class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); chain.doFilter(req, resp); } }注意这个 Filter 要放在 AuthFilter 之前或者用WebFilter的 order 属性控制顺序。5.4 连接池为什么你的项目跑一会儿就卡死现象项目刚启动正常操作几次之后页面一直转圈最后报Timeout waiting for idle object。原因每次数据库操作都新建连接用完没关闭连接数耗尽。或者用了连接池但配置的最大连接数太小。解决课程设计里用 Druid 或 C3P0 连接池不要每次DriverManager.getConnection。Druid 的配置driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/library?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse usernameroot password你的密码 initialSize5 maxActive20 maxWait3000maxActive20表示最多 20 个连接maxWait3000表示获取连接超时 3 秒。如果并发不高这两个值够用了。关键是每次用完连接要close()连接池的 close 是归还连接而不是真正关闭。5.5 JSP 页面报 javax.servlet 不存在现象IDEA 里 JSP 文件满屏红色提示cannot resolve symbol javax.servlet。原因项目没有引入 Servlet API 和 JSP API 的依赖。Tomcat 的 lib 目录下有这些 jar但 IDEA 默认不会自动加。解决Project Structure → Libraries → → Java → 选 Tomcat 安装目录下的lib/servlet-api.jar和lib/jsp-api.jar。或者用 Maven 的话加javax.servlet:javax.servlet-api:4.0.1依赖scope 设为 provided。6. 从能跑到能拿高分三个让课程设计脱颖而出的技巧6.1 给借阅记录加一个逾期自动计算大部分课程设计的借阅状态是手动改的还书时把 status 改成 1。但“逾期”这个状态如果也要手动判断答辩时老师一问就露馅。更好的做法是在查询时动态计算SELECT *, CASE WHEN status 0 AND due_date NOW() THEN 已逾期 WHEN status 0 THEN 借阅中 WHEN status 1 THEN 已归还 END AS status_text FROM borrow_record WHERE user_id ?这样不需要定时任务每次查询自动算出当前状态。如果要在页面上标红逾期记录在 JSP 里判断status_text 已逾期加个 CSS 类就行。6.2 用 AJAX 做无刷新借阅答辩时是个加分项传统表单提交借书会刷新整个页面体验一般。用 jQuery 的 AJAX 改成无刷新function borrowBook(bookId) { $.ajax({ url: contextPath /BorrowServlet, type: GET, data: { bookId: bookId }, success: function(res) { if (res.code 200) { alert(借阅成功); location.reload(); } else { alert(res.msg || 借阅失败); } }, error: function() { alert(网络错误请稍后重试); } }); }Servlet 返回 JSON 而不是重定向response.setContentType(application/json;charsetUTF-8); PrintWriter out response.getWriter(); if (success) { out.print({\code\:200,\msg\:\借阅成功\}); } else { out.print({\code\:500,\msg\:\库存不足\}); } out.flush();这个改动不大但答辩时演示效果明显好很多。老师看到 AJAX 交互印象分直接上去。6.3 数据库备份与恢复答辩前一定要做的事答辩现场最怕什么演示到一半数据库连不上或者数据被误删了。提前做好备份# 备份结构和数据 mysqldump -u root -p library library_backup.sql # 只备份结构 mysqldump -u root -p --no-data library library_structure.sql # 恢复 mysql -u root -p library library_backup.sql把library_backup.sql放在 U 盘里答辩前如果数据库出问题几分钟就能恢复。另外在项目里加一个“一键初始化”的 Servlet调用mysql命令重新导入 sql 文件演示前点一下就行。我自己的习惯是每次改完数据库结构立刻导出一份带时间戳的备份比如library_20250101.sql。这样即使改错了也能回退到上一个版本。课程设计虽然不大但这个习惯养成了以后做真实项目能省很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表