ARTICLE DETAIL

资讯详情

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

基于JSP+SqlServer的高校教材征订管理系统:从设计到避坑

基于JSP+SqlServer的高校教材征订管理系统:从设计到避坑 简介这份资源是面向高校计算机专业学生与JavaWeb初学者的一套完整课程设计/毕业设计参考项目主题为基于JavaEE的高校网上教材征订管理系统采用JSP网站设计与Sql Server数据库实现可帮助读者理解教材征订流程的自动化与信息化落地方式。压缩包共164个文件约6.49MB以46个jsp页面、55个gif素材、8个png与6个jpg图片、6个js脚本、3个css样式表为主另含5个java源码、5个class编译文件、5个jar依赖及mdf、ldf数据库文件覆盖视图、控制、数据持久化与静态资源等模块。目前已有140人学习下载。项目按MVC模式组织Servlet承担控制器职责JSP负责视图展示JDBC完成与Sql Server的数据交互数据库层面包含用户、教材、订单等表及外键关联设计读者可据此掌握JSP动态页面、请求处理、SQL查询优化与权限安全等核心技能适合作为课程设计参考或企业级Web应用入门练手素材。1. 从一份 .rar 说起高校教材征订系统到底在解决谁的痛每到学期末教务处、教材科和各个院系的教务员都会被同一件事折磨几百个班级的教材需求靠 Excel 和 QQ 群来回传版本对不上、数量算错、漏订重订是常态。基于 JavaEE 的高校网上教材征订管理系统本质就是把这条「院系报需求 → 教材科汇总 → 供应商发货 → 学生领书结算」的链路搬到 Web 上用 JSP 做页面、用 SqlServer 存数据让每个角色只看到自己该看的那部分。它适合两类人一类是课程设计/毕设需要一套完整可跑的管理系统另一类是真的想给学校做个小工具、又不想上重型框架的开发者。这套东西技术栈不新但胜在门槛低、部署简单一台装了 Tomcat 和 SqlServer 的机器就能跑起来对预算和运维都紧张的校园场景反而合适。下面我按「先跑通、再讲清、最后避坑」的顺序把这条链路拆开讲。2. 技术选型与运行环境为什么是 JSP SqlServer 而不是别的2.1 这套组合在校园场景里为什么还站得住很多人一听 JSP 就皱眉觉得是上个时代的东西。但在高校教材征订这个具体场景里选型逻辑和互联网大厂完全不同。教材科通常只有一两台老服务器运维由信息中心的兼职老师负责他们熟悉的是 Windows Server IIS/Tomcat SqlServer 这一套而不是 Docker 和 K8s。JSP 的优势在于「页面即逻辑」一个.jsp文件里写 Java 代码片段就能查库、渲染表格不需要额外学模板引擎和前后端分离的构建链。SqlServer 的优势在于图形化工具成熟教材科的人自己就能用 SSMS 改数据、导报表出了小问题不用等开发。从数据模型看教材征订的核心是几张关系表教材信息表、班级表、征订单主表、征订单明细表、用户表。这种强关系、事务性不复杂的场景SqlServer 完全够用而且它的IDENTITY自增、TOP分页、字符串函数对新手友好。真正需要警惕的是并发——征订高峰期可能几十个教务员同时提交JSP 里如果用Statement拼 SQL很容易出问题后面避坑章会细讲。提示如果你的环境是 LinuxSqlServer 也有 Linux 版但校园里绝大多数还是 Windows本文按 Windows Tomcat SqlServer 走。2.2 环境搭建从 JDK 到 SqlServer 的最小可用清单先把环境跑起来别急着写业务代码。下面是我一般会用的版本组合不追新只求稳。组件建议版本说明JDK8 或 11JSP 老项目对高版本 JDK 兼容性差8 最稳Tomcat8.5 或 99 对 Servlet 4.0 支持好8.5 更轻SqlServer2016 或 20192012 也能用但 2016 之后的分页语法更顺手IDEEclipse/IntelliJ IDEAIDEA 对 JSP 支持更好但 Eclipse 更贴近教材驱动mssql-jdbc注意版本要和 JDK 匹配JDK8 用 7.x 或 8.x安装 SqlServer 时最常见的翻车点是「无法找到数据库引擎启动句柄」这通常是因为之前装过又卸载不干净注册表里残留了实例信息。解决办法是彻底卸载后清理HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server下的残留项再重装。另一个坑是实例名默认实例和命名实例的连接字符串不一样localhost和localhost\SQLEXPRESS是两回事配 JDBC 时一定要确认。配置 Tomcat 时把项目打成 war 包丢进webapps是最省事的做法。但 JSP 项目经常需要改数据库连接我习惯把连接参数抽到一个db.properties里而不是硬编码在 JSP 或 Servlet 中。这样换环境只改一个文件。# 检查 Tomcat 是否正常启动 # 访问 http://localhost:8080看到 Tomcat 首页即成功 # 部署 war 包后访问 http://localhost:8080/你的项目名/上面这段不是代码是验证步骤。Tomcat 启动后如果 8080 端口被占用改conf/server.xml里的Connector port。SqlServer 默认端口 1433如果连不上先看 SqlServer 配置管理器里 TCP/IP 协议是否启用IP 地址里有没有把127.0.0.1的 TCP 端口设成 1433。2.3 数据库连接JDBC 在 JSP 项目里的正确打开方式JSP 项目里连 SqlServer最原始的方式是在 JSP 页面顶部% page importjava.sql.* %然后写DriverManager.getConnection。这种方式能跑但每个页面都建连接高峰期直接把数据库连接数打满。我一般会写一个简单的工具类用静态方法拿连接至少做到「用完就关」。// DBUtil.java —— 放在 src 下的工具类 import java.sql.*; public class DBUtil { private static final String URL jdbc:sqlserver://localhost:1433;databaseNameTextbookDB;encryptfalse; private static final String USER sa; private static final String PWD 你的密码; static { try { Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConn() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } public static void close(Connection c, Statement s, ResultSet r) { try { if (r ! null) r.close(); } catch (Exception e) {} try { if (s ! null) s.close(); } catch (Exception e) {} try { if (c ! null) c.close(); } catch (Exception e) {} } }逻辑说明静态块只加载一次驱动避免重复注册。getConn每次返回新连接close按 ResultSet → Statement → Connection 的顺序关闭这是 JDBC 的标准关闭顺序反了可能报错。参数说明encryptfalse在 SqlServer 2016 之后如果不加可能因为强制加密而连接失败databaseName必须和实际库名一致大小写不敏感但别写错。这个工具类不解决连接池问题但作为课程设计够用真要上生产换成 HikariCP 或 Druid 只是把getConn的实现改掉调用方不用动。3. 核心功能落地教材征订的增删改查怎么写成 JSP3.1 数据表设计五张表撑起整个征订流程先把表建好后面写页面才有依据。教材征订系统的数据模型不复杂但字段类型和约束要想清楚否则后面改表很痛苦。-- 教材信息表 CREATE TABLE Textbook ( tid INT IDENTITY(1,1) PRIMARY KEY, tname NVARCHAR(100) NOT NULL, author NVARCHAR(50), publisher NVARCHAR(100), price DECIMAL(10,2), stock INT DEFAULT 0 ); -- 班级表 CREATE TABLE ClassInfo ( cid INT IDENTITY(1,1) PRIMARY KEY, cname NVARCHAR(50) NOT NULL, grade NVARCHAR(20), major NVARCHAR(50) ); -- 征订单主表 CREATE TABLE OrderMain ( oid INT IDENTITY(1,1) PRIMARY KEY, cid INT FOREIGN KEY REFERENCES ClassInfo(cid), orderDate DATETIME DEFAULT GETDATE(), status NVARCHAR(20) DEFAULT 待审核 ); -- 征订单明细 CREATE TABLE OrderDetail ( did INT IDENTITY(1,1) PRIMARY KEY, oid INT FOREIGN KEY REFERENCES OrderMain(oid), tid INT FOREIGN KEY REFERENCES Textbook(tid), quantity INT NOT NULL ); -- 用户表 CREATE TABLE SysUser ( uid INT IDENTITY(1,1) PRIMARY KEY, username NVARCHAR(50) UNIQUE NOT NULL, password NVARCHAR(50) NOT NULL, role NVARCHAR(20) DEFAULT 教务员 );逻辑说明IDENTITY(1,1)是 SqlServer 的自增写法从 1 开始每次加 1。NVARCHAR而不是VARCHAR因为教材名和出版社可能有中文NVARCHAR按 Unicode 存储不会乱码。DECIMAL(10,2)存价格别用FLOAT浮点误差在金额上会出问题。外键约束保证明细不会指向不存在的订单或教材。参数说明status默认「待审核」后续可以扩展成「已审核」「已发货」「已完成」。stock库存字段在征订场景里可选但如果要做「库存不足提醒」就得留着。3.2 教材列表页JSP 里查库、分页和中文乱码处理教材列表是使用频率最高的页面教务员要在这里浏览、搜索、选择教材加入征订单。JSP 里写查询核心是「取连接 → 执行 SQL → 遍历 ResultSet → 渲染表格 → 关闭资源」。% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8% % page importjava.sql.*, util.DBUtil % % request.setCharacterEncoding(UTF-8); int pageNo 1; String p request.getParameter(page); if (p ! null) pageNo Integer.parseInt(p); int pageSize 10; int start (pageNo - 1) * pageSize; Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConn(); // SqlServer 分页先 ORDER BY 再 OFFSET FETCH String sql SELECT tid,tname,author,publisher,price,stock FROM Textbook ORDER BY tid OFFSET ? ROWS FETCH NEXT ? ROWS ONLY; ps conn.prepareStatement(sql); ps.setInt(1, start); ps.setInt(2, pageSize); rs ps.executeQuery(); % table border1 trth编号/thth教材名/thth作者/thth出版社/thth价格/thth库存/th/tr % while (rs.next()) { % tr td% rs.getInt(tid) %/td td% rs.getString(tname) %/td td% rs.getString(author) %/td td% rs.getString(publisher) %/td td% rs.getBigDecimal(price) %/td td% rs.getInt(stock) %/td /tr % } } catch (Exception e) { out.println(查询出错 e.getMessage()); } finally { DBUtil.close(conn, ps, rs); } % /table逻辑说明request.setCharacterEncoding(UTF-8)必须在取任何参数之前调用否则 POST 中文会乱码。分页用 SqlServer 2012 之后支持的OFFSET ... FETCH NEXT比老的ROW_NUMBER()写法简洁。PreparedStatement用?占位避免 SQL 注入这是必须养成的习惯。参数说明pageSize设 10 是经验值太大页面加载慢太小翻页烦。start的计算是(pageNo-1)*pageSize第一页 start0。注意OFFSET后面不能直接跟TOP这是两套分页语法混用会报错。注意JSP 页面里out.println输出异常信息只适合调试上线前要改成跳转到统一错误页否则会把数据库结构暴露给用户。3.3 征订单提交事务保证主表和明细表一起成功征订单提交是整个系统里唯一需要事务的地方主表插一条明细表插多条任何一条失败都必须回滚否则会出现「有订单没明细」的脏数据。// OrderService.java 里的提交方法 public boolean submitOrder(int cid, MapInteger,Integer items) { Connection conn null; PreparedStatement psMain null; PreparedStatement psDetail null; ResultSet rs null; try { conn DBUtil.getConn(); conn.setAutoCommit(false); // 开启事务 String sqlMain INSERT INTO OrderMain(cid,status) VALUES(?,?); psMain conn.prepareStatement(sqlMain, Statement.RETURN_GENERATED_KEYS); psMain.setInt(1, cid); psMain.setString(2, 待审核); psMain.executeUpdate(); rs psMain.getGeneratedKeys(); int oid 0; if (rs.next()) oid rs.getInt(1); String sqlDetail INSERT INTO OrderDetail(oid,tid,quantity) VALUES(?,?,?); psDetail conn.prepareStatement(sqlDetail); for (Map.EntryInteger,Integer e : items.entrySet()) { psDetail.setInt(1, oid); psDetail.setInt(2, e.getKey()); psDetail.setInt(3, e.getValue()); psDetail.addBatch(); } psDetail.executeBatch(); conn.commit(); return true; } catch (Exception e) { try { if (conn ! null) conn.rollback(); } catch (Exception ex) {} e.printStackTrace(); return false; } finally { try { if (conn ! null) conn.setAutoCommit(true); } catch (Exception e) {} DBUtil.close(conn, psDetail, null); DBUtil.close(null, psMain, rs); } }逻辑说明setAutoCommit(false)关闭自动提交之后所有操作在同一个事务里。RETURN_GENERATED_KEYS让 SqlServer 返回刚插入的主键用来关联明细。addBatchexecuteBatch批量插入明细比循环单条插入快很多。任何异常都走rollback保证要么全成功要么全失败。参数说明items是Map教材ID, 数量从页面表单解析而来。finally里恢复autoCommit是防止连接被放回池子后状态不对。这个写法在 JSP 里通常放在 Servlet 或 JavaBean 中不要直接写在 JSP 脚本片段里否则页面会很难维护。4. 避坑与排查JSP SqlServer 最容易翻车的五个地方4.1 中文乱码从页面到数据库的完整链路现象教材名在列表页显示正常但插入数据库后变成问号或者从数据库读出来是乱码。原因乱码可能出现在三个环节——JSP 页面编码、请求参数编码、数据库字段编码。JSP 的pageEncoding管的是文件本身编码contentType里的charset管的是响应编码request.setCharacterEncoding管的是请求体编码。SqlServer 如果字段用了VARCHAR而不是NVARCHAR中文会丢。解决JSP 顶部三件套写全contentTypetext/html; charsetUTF-8 pageEncodingUTF-8取参数前调request.setCharacterEncoding(UTF-8)数据库字段一律用NVARCHAR。JDBC URL 里可以加characterEncodingUTF-8但 SqlServer 的 JDBC 驱动对这个参数支持有限关键还是字段类型。4.2 分页查询返回空OFFSET 和 TOP 不能混用现象第一页正常翻到第二页数据为空或者直接报语法错误。原因SqlServer 的分页有两套语法老的是ROW_NUMBER() OVER新的是OFFSET ... FETCH NEXT。OFFSET必须配合ORDER BY而且不能和TOP出现在同一个查询里。有人从 MySQL 的LIMIT转过来写成SELECT TOP 10 ... OFFSET 10直接报错。解决统一用ORDER BY 字段 OFFSET n ROWS FETCH NEXT m ROWS ONLY确保ORDER BY的字段有索引否则深分页会越来越慢。如果 SqlServer 版本低于 2012只能用ROW_NUMBER()子查询。4.3 连接泄漏Tomcat 跑一天就卡死现象系统刚启动正常运行几小时后越来越慢最后报「无法获取连接」。原因JSP 里拿了Connection但没在finally里关闭或者ResultSet没关导致Statement也关不掉。Tomcat 默认连接池不大泄漏几十个连接就耗尽。解决所有数据库操作必须写在try...finally里finally中按顺序关闭。更彻底的做法是引入连接池把DBUtil.getConn改成从DataSource取连接close改成归还连接。排查时可以在 SqlServer 里执行sp_who2看当前连接数或者用 SSMS 的活动监视器。4.4 字符串转数字SqlServer 的隐式转换陷阱现象查询条件里传10和10结果不一样或者WHERE price 100报转换错误。原因SqlServer 在比较NVARCHAR和INT时会尝试隐式转换但转换规则依赖优先级。如果字段是字符串类型100和99按字典序比较100 99成立这显然不是想要的。解决参数化查询时用setInt、setBigDecimal明确类型不要用setString传数字。如果字段本身设计成了字符串用CAST或CONVERT显式转换比如WHERE CAST(price AS DECIMAL(10,2)) 100。设计表时就把数字字段定成数字类型别偷懒。4.5 部署后 404war 包名和访问路径对不上现象Tomcat 启动正常但访问http://localhost:8080/项目名/报 404。原因war 包名决定了上下文路径。如果 war 叫textbook.war访问路径就是/textbook不是/textbook/WebContent或别的。另外web.xml里welcome-file配置的首页文件必须真实存在大小写敏感。解决确认webapps下解压后的目录名访问路径和它一致。检查WEB-INF/web.xml的welcome-file-list确保index.jsp或login.jsp在项目根目录下。如果改了server.xml的Context path以那个为准。5. 进阶技巧用存储过程做征订统计和库存联动5.1 为什么把统计逻辑放进存储过程教材科最常要的报表是「每个班级征订了多少种、多少册、总金额多少」。这个查询要关联四张表如果在 JSP 里拼 SQL每次都要写一长串 JOIN改起来容易漏。放进存储过程的好处是逻辑集中在数据库端JSP 只负责调用和展示SqlServer 可以对存储过程做执行计划缓存重复调用更快权限也好控制只给教务员EXECUTE权限不给直接查表的权限。CREATE PROCEDURE sp_OrderStat cid INT AS BEGIN SET NOCOUNT ON; SELECT c.cname AS 班级, COUNT(D.did) AS 教材种类, SUM(D.quantity) AS 总册数, SUM(D.quantity * T.price) AS 总金额 FROM OrderMain O JOIN ClassInfo C ON O.cid C.cid JOIN OrderDetail D ON O.oid D.oid JOIN Textbook T ON D.tid T.tid WHERE O.cid cid AND O.status 已取消 GROUP BY C.cname; END逻辑说明SET NOCOUNT ON关闭行数提示减少网络传输。WHERE里过滤掉已取消的订单保证统计准确。SUM(D.quantity * T.price)直接算出金额不用在 Java 里再循环。参数说明cid是班级 ID调用时传具体值。如果要不传参数查所有班级可以再写一个不带WHERE的版本或者用cid IS NULL OR O.cid cid的技巧。在 JSP 里调用存储过程CallableStatement cs conn.prepareCall({call sp_OrderStat(?)}); cs.setInt(1, cid); ResultSet rs cs.executeQuery(); // 遍历 rs 渲染表格和普通查询一样CallableStatement是 JDBC 调存储过程的标准方式{call 过程名(?)}是转义语法SqlServer 驱动会翻译成EXEC。5.2 库存联动征订审核通过后自动扣减如果教材信息表里有stock字段审核通过时应该扣库存库存不足要拦截。这个逻辑放在存储过程里用事务包住「更新订单状态 扣库存 检查库存」三步。CREATE PROCEDURE sp_ApproveOrder oid INT AS BEGIN SET NOCOUNT ON; BEGIN TRANSACTION; BEGIN TRY -- 检查库存是否充足 IF EXISTS ( SELECT 1 FROM OrderDetail D JOIN Textbook T ON D.tid T.tid WHERE D.oid oid AND T.stock D.quantity ) BEGIN ROLLBACK; RETURN -1; -- 库存不足 END -- 扣库存 UPDATE T SET T.stock T.stock - D.quantity FROM Textbook T JOIN OrderDetail D ON T.tid D.tid WHERE D.oid oid; -- 改状态 UPDATE OrderMain SET status 已审核 WHERE oid oid; COMMIT; RETURN 0; END TRY BEGIN CATCH ROLLBACK; RETURN -2; -- 异常 END CATCH END逻辑说明BEGIN TRANSACTION开启事务TRY...CATCH是 SqlServer 的错误处理结构。先检查库存不足就回滚并返回 -1充足则扣减并改状态提交后返回 0。参数说明返回值由 JSP 端用CallableStatement的registerOutParameter接收根据返回值给用户不同提示。这个存储过程把「检查 更新」放在一个事务里避免了并发下的超卖。5.3 验证方法怎么确认统计和扣减是对的写完存储过程别急着接页面先在 SSMS 里手动验证。造几条测试数据两个班级、三种教材、若干订单然后EXEC sp_OrderStat 1看结果是否符合手算。扣库存的验证更关键把某教材库存设成 5提交一个数量 10 的订单审核时应该返回 -1 且库存不变改成数量 3审核后库存应该变成 2订单状态变成「已审核」。我自己的习惯是每加一个存储过程就在 SSMS 里写一段测试脚本连同预期结果一起存成.sql文件。下次改逻辑时先跑测试脚本确认没破坏原有行为再部署。这个习惯帮我省过很多次「改一个功能崩三个页面」的后悔药。JSP 项目没有单元测试框架这种手工回归就是最实在的保障。希望帮到你。本文还有配套的精品资源点击获取
返回列表