ARTICLE DETAIL

资讯详情

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

Java+JSP+MySQL学校教材管理系统实战:从建库到事务扣库存

Java+JSP+MySQL学校教材管理系统实战:从建库到事务扣库存 简介这份资源是面向计算机专业学生与Java Web初学者的一套完整学校教材管理系统源码基于Java、JSP与MySQL技术栈构建可直接用于课程设计、毕业设计或Web开发练手。压缩包共81个文件约3.91MB包含26个JSP页面、22个class编译文件、11个Java源文件以及CSS样式、XML配置、JAR依赖和一份SQL建表脚本覆盖前端展示、后端逻辑与数据库脚本的完整链路。系统围绕管理员登录与教材管理两大模块展开实现教材的增删改查、库存查询与权限校验并采用MVC思路分离模型、视图与控制器便于理解请求处理与数据访问流程。目前已有1229人学习下载读者可借此掌握JSP动态页面开发、MySQL数据操作及异常处理、输入校验等实用技巧快速搭建可运行的教材管理平台。1. 从零搭一套学校教材管理系统JavaJSPMySQL 到底能解决什么每到学期初教务处最头疼的不是排课而是教材。班级数、学生人数、教材版本、库存余量、领用记录全靠几张 Excel 来回传改一处就全乱。用 JavaJSPMySQL 做一套 Web 学校教材管理系统本质就是把这套线下流程搬到浏览器里管理员维护教材信息和库存教师按班级提交领用申请系统自动扣减库存并留下可追溯的记录。它适合两类人一是正在找 JavaWeb 完整案例练手的在校生二是想给中小学校做一套轻量内部工具的一线开发者。技术栈不新但胜在依赖少、部署简单、逻辑闭环清晰一台普通服务器甚至一台办公电脑就能跑起来。下面我按真实落地顺序把选型、建库、编码、排错和进阶一条条讲透。2. 技术选型与数据库设计为什么这套老组合反而更适合教材管理2.1 选型理由JSP 不是过时是场景匹配很多人一听 JSP 就皱眉觉得 Spring Boot 才是正道。但教材管理系统的真实使用场景是用户量小几百人、并发低、功能固定、部署环境往往是学校内网的老服务器。这种场景下JSP 的优势反而明显——它把 Java 代码和 HTML 写在一个文件里页面跳转直观不需要前后端分离那一套构建工具链一个 Tomcat 加一个 MySQL 就能跑。对于要交课程设计或者快速交付内部工具的团队省下来的环境配置时间就是实打实的收益。当然这不意味着可以乱写。JSP 里塞业务逻辑是新手最容易翻车的地方正确做法是 JSP 只负责展示业务逻辑全部放到 Servlet 或 JavaBean 里数据库操作统一走 DAO 层。这个分层习惯养成了后面想迁移到 Spring Boot 也不难。环境版本上我一般会选 JDK 8 或 JDK 11、Tomcat 8.5 或 9、MySQL 5.7 或 8.0。MySQL 8.0 的安装配置在 Windows 10 上已经比较顺注意选对认证方式即可后面避坑章节会细说。2.2 数据库表设计五张表撑起整个系统教材管理的核心实体其实就几个用户、教材、库存、领用记录、班级。表不用多但字段要想清楚。下面是我常用的建表脚本直接可以在 MySQL 里执行。-- 用户表区分管理员和教师 CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 存储MD5后的密码, real_name VARCHAR(50) COMMENT 真实姓名, role TINYINT DEFAULT 2 COMMENT 1管理员 2教师, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 教材表教材基础信息 CREATE TABLE textbook ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL UNIQUE COMMENT 书号唯一, name VARCHAR(100) NOT NULL COMMENT 教材名称, publisher VARCHAR(100) COMMENT 出版社, price DECIMAL(8,2) DEFAULT 0.00 COMMENT 单价, stock INT DEFAULT 0 COMMENT 当前库存, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 领用记录表谁在什么时候领了什么 CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT 领用人, textbook_id INT NOT NULL COMMENT 教材ID, class_name VARCHAR(50) COMMENT 使用班级, quantity INT NOT NULL DEFAULT 1 COMMENT 领用数量, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 1 COMMENT 1已领用 2已归还, INDEX idx_user (user_id), INDEX idx_textbook (textbook_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有两个设计要点值得说。第一stock字段直接放在教材表里而不是单独做库存表因为教材管理的库存粒度就是「某本书还剩几本」没必要过度设计。第二borrow_record上建了两个索引因为查询「某教师领了哪些书」和「某本书被谁领了」是两个高频操作没索引数据一多就明显变慢。提示字符集统一用 utf8mb4教材名称里出现生僻字或特殊符号时不会乱码这个坑我踩过不止一次。2.3 连接配置一个 DBUtil 类解决所有数据库访问JSP 项目里最容易写乱的就是数据库连接。我的习惯是写一个工具类统一管理连接、关闭资源避免每个 Servlet 里都重复写 DriverManager。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/textbook_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 你的密码; static { try { Class.forName(com.mysql.cj.jdbc.Driver); // MySQL 8.0 驱动 } 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, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException ignored) {} try { if (ps ! null) ps.close(); } catch (SQLException ignored) {} try { if (conn ! null) conn.close(); } catch (SQLException ignored) {} } }参数说明serverTimezoneAsia/Shanghai是 MySQL 8.0 必须加的否则时间字段会差 8 小时useSSLfalse在本地和内网环境可以关掉省去证书配置的麻烦。驱动类名从 MySQL 5.x 的com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver写错会直接报找不到驱动。3. 核心功能实现登录、教材增删改查与领用扣库存3.1 登录与权限拦截一个 Filter 挡住未登录请求登录本身不难难的是别让用户绕过登录直接访问后台页面。做法是写一个 Filter拦截所有后台路径检查 session 里有没有用户信息。public class AuthFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); // 登录页和静态资源放行 if (uri.endsWith(login.jsp) || uri.contains(/css/) || uri.contains(/js/)) { chain.doFilter(req, resp); return; } Object user request.getSession().getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }逻辑说明先判断请求路径登录页和静态资源直接放行否则检查 session。这里有个细节getContextPath()不能省否则项目部署名不是 ROOT 时跳转会 404。web.xml 里配置这个 Filter 时url-pattern用/*拦截全部再在代码里做白名单比配多个 pattern 更省心。3.2 教材列表与分页别一次性把整张表查出来教材数量少的时候SELECT *没问题但只要有几百条页面就会明显卡。分页是必须的MySQL 用LIMIT实现最简单。public ListTextbook queryByPage(int page, int pageSize, String keyword) { ListTextbook list new ArrayList(); String sql SELECT * FROM textbook WHERE name LIKE ? ORDER BY id DESC LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % (keyword null ? : keyword) %); ps.setInt(2, (page - 1) * pageSize); // 起始偏移量 ps.setInt(3, pageSize); ResultSet rs ps.executeQuery(); while (rs.next()) { Textbook t new Textbook(); t.setId(rs.getInt(id)); t.setName(rs.getString(name)); t.setStock(rs.getInt(stock)); list.add(t); } } catch (SQLException e) { e.printStackTrace(); } return list; }参数说明page从 1 开始(page-1)*pageSize就是 SQL 的偏移量这是分页最容易算错的地方写成page*pageSize会漏掉第一页数据。keyword用LIKE模糊匹配注意%要拼在 Java 里而不是 SQL 里用PreparedStatement能防注入。3.3 领用扣库存事务是这里唯一的后悔药领用教材必须同时做两件事插入一条领用记录扣减教材库存。这两步要么都成功要么都失败否则就会出现「记录有了但库存没扣」的脏数据。必须用事务。public boolean borrow(int userId, int textbookId, String className, int qty) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 先查库存带行锁防止并发超领 String checkSql SELECT stock FROM textbook WHERE id ? FOR UPDATE; PreparedStatement check conn.prepareStatement(checkSql); check.setInt(1, textbookId); ResultSet rs check.executeQuery(); if (!rs.next() || rs.getInt(stock) qty) { conn.rollback(); return false; // 库存不足 } // 扣库存 PreparedStatement update conn.prepareStatement( UPDATE textbook SET stock stock - ? WHERE id ?); update.setInt(1, qty); update.setInt(2, textbookId); update.executeUpdate(); // 插记录 PreparedStatement insert conn.prepareStatement( INSERT INTO borrow_record(user_id, textbook_id, class_name, quantity) VALUES(?,?,?,?)); insert.setInt(1, userId); insert.setInt(2, textbookId); insert.setString(3, className); insert.setInt(4, qty); insert.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ignored) {} e.printStackTrace(); return false; } finally { try { if (conn ! null) conn.setAutoCommit(true); conn.close(); } catch (SQLException ignored) {} } }逻辑说明FOR UPDATE是关键它给这一行加排他锁防止两个教师同时领同一本书时都读到「库存够」然后各扣一次导致库存变负。这个并发问题在测试时很难复现但上线后一定会遇到属于典型的血泪经验。事务提交后记得把autoCommit恢复否则连接池复用时会出问题。4. 避坑与排查教材管理系统上线前必须过的五道坎4.1 中文乱码现象是页面显示问号原因是编码链没统一现象教材名称在数据库里正常但页面上显示成???或者乱码方块。原因通常出在三处JSP 页面没声明pageEncoding、请求参数没设setCharacterEncoding、数据库连接串没带characterEncodingutf8。解决方法是三处一起改JSP 顶部加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 里第一行写request.setCharacterEncoding(UTF-8)连接串加上characterEncodingutf8。三处缺一处都可能乱码别只改一个就以为好了。4.2 MySQL 8.0 连接报时区错误现象是启动就抛异常现象The server time zone value ?D1ú±ê×?ê±?? is unrecognized。原因是 MySQL 8.0 驱动要求明确指定时区而系统默认时区名带中文。解决方法是在 JDBC URL 里加serverTimezoneAsia/Shanghai或者干脆用serverTimezoneGMT%2B8。这个错误在 MySQL 5.7 上不会出现所以从 5.7 升级到 8.0 的项目特别容易中招。4.3 库存扣成负数现象是并发领用后库存异常现象两个教师同时领同一本只剩 1 本的教材结果库存变成 -1。原因是查询和更新之间没有锁两个请求都读到了「库存 1」。解决方法就是 3.3 里说的SELECT ... FOR UPDATE加事务。如果不想用行锁也可以在 UPDATE 语句里加条件WHERE id ? AND stock ?根据executeUpdate()返回的影响行数判断是否成功这是更轻量的做法。4.4 JSP 里写业务逻辑导致页面报 500现象是改一处崩一片现象JSP 页面里直接写数据库查询某次改字段后多个页面同时 500。原因是业务逻辑散落在各个 JSP 里没有统一入口。解决方法是把数据库操作全部抽到 DAO 类JSP 只负责遍历 Servlet 传过来的 list。这个重构越早做越好等到十几个页面都写了 SQL 再改工作量翻倍。4.5 Tomcat 部署后 404现象是本地能跑部署就找不到页面现象IDE 里运行正常打成 war 包丢到 Tomcat 的 webapps 下访问就 404。原因通常是访问路径没带项目名比如实际路径是/textbook/login.jsp却直接访问/login.jsp。解决方法是访问时带上 war 包名或者把 war 包改名为ROOT.war部署成根应用。另外检查web.xml的welcome-file配置别把默认首页写错。5. 进阶技巧用导出和校验把系统从「能用」做到「敢用」系统能跑起来只是第一步真正让教务老师愿意天天用的往往是几个细节功能。这里分享两个我每次都会加的进阶点。第一个是教材领用记录的 Excel 导出。教务每到期末都要对账让他们一条条复制粘贴不现实。用 Apache POI 生成 xls 文件一个 Servlet 就能搞定。核心代码不长// 在 Servlet 中response 设置好 content-type 和 header 后 HSSFWorkbook workbook new HSSFWorkbook(); HSSFSheet sheet workbook.createSheet(领用记录); HSSFRow header sheet.createRow(0); header.createCell(0).setCellValue(教材名称); header.createCell(1).setCellValue(领用班级); header.createCell(2).setCellValue(数量); header.createCell(3).setCellValue(领用时间); ListBorrowRecord records recordDao.queryAll(); int rowIdx 1; for (BorrowRecord r : records) { HSSFRow row sheet.createRow(rowIdx); row.createCell(0).setCellValue(r.getTextbookName()); row.createCell(1).setCellValue(r.getClassName()); row.createCell(2).setCellValue(r.getQuantity()); row.createCell(3).setCellValue(r.getBorrowTime()); } workbook.write(response.getOutputStream()); workbook.close();参数说明HSSFWorkbook对应 xls 格式数据量大时换成XSSFWorkbook生成 xlsx。注意response的 header 要设成application/vnd.ms-excel并指定文件名否则浏览器会直接打开而不是下载。文件名带中文时要用URLEncoder.encode处理不然会乱码。第二个是库存预警。在教材列表页对stock 10的教材标红并在首页显示一个待补货清单。实现上就是查询时加一个WHERE stock 10的统计前端用 CSS 类控制颜色。这个功能不复杂但能让管理员提前发现库存不足避免开学时才发现书不够。最后一个习惯每次改完 DAO 层的 SQL我都会手动在 MySQL 客户端里把这条 SQL 单独跑一遍确认结果集正确再放回代码。因为 Java 里 SQL 报错往往被 try-catch 吞掉只打印堆栈不主动验证很容易带着错误上线。这套系统不大但每个字段、每个事务都值得较真毕竟教务数据出错补起来比写代码累多了。希望帮到你。本文还有配套的精品资源点击获取
返回列表