简介这份资源是面向高校计算机专业学生与JavaWeb初学者的一套完整课程设计/毕业设计参考项目主题为基于JavaEE的高校网上教材征订管理系统采用JSP网站设计与Sql Server数据库实现可帮助读者理解教材征订流程的自动化与信息化落地方式。压缩包共164个文件约6.49MB以46个jsp页面、55个gif素材、8个png与6个jpg图片、6个js脚本、5个java源码及配套class、jar文件为主另含css样式、db与mdf/ldf数据库文件、xml配置及说明文档覆盖视图、控制、数据持久化与静态资源等模块。项目围绕JSP、Servlet、JDBC与Sql Server展开涉及MVC分层、用户与教材、订单等数据表设计、外键关联及SQL查询优化等知识点适合作为课程设计模板或企业级Web入门练手素材。目前已有140人学习下载可据此快速梳理系统结构、复用页面与数据库脚本并对照源码理解请求处理与数据交互的完整链路。1. 教材征订管理系统从 JavaEE 课程设计到能跑通的 JSPSqlServer 落地每年学期末高校教材科最头疼的就是统计各班级的征订数量。纸质表格收上来Excel 版本五花八门汇总时对不上号是常态。基于 JavaEE 的高校网上教材征订管理系统本质就是把这套流程搬到浏览器里教师或班委在线选教材、填数量教材科后台汇总、导出订单。技术栈是经典的 JSP Servlet SqlServer属于 JavaEE 课程设计里最常被选中的题目之一。它适合两类人一是正在做课程设计、需要一套能跑通且能讲清楚原理的学生二是刚接手老系统维护、需要快速理解 JSP 直连数据库这套写法的初级开发者。这套方案不追求前后端分离胜在结构直白、部署门槛低一台装了 Tomcat 和 SqlServer 的机器就能完整复现。2. 环境搭建JDK、Tomcat 与 SqlServer 的版本咬合2.1 为什么这套组合对版本特别敏感JSP 页面最终由 Tomcat 编译成 Servlet 执行而 Tomcat 的版本又决定了它支持的 Servlet 规范和 JDK 版本。SqlServer 这边JDBC 驱动的版本必须和数据库实例版本匹配否则连接阶段就会抛异常。很多人卡在第一步不是代码写错而是版本没对齐。常见做法是JDK 8 配 Tomcat 8.5 或 9SqlServer 用 2012 到 2019 都可以驱动选 mssql-jdbc 的 7.x 或 8.x 版本。这个组合在课程设计场景里最稳网上能查到的资料也最多。需要留意的是SqlServer 安装本身有个高频翻车点安装程序报「无法找到数据库引擎启动句柄」。这通常是因为之前装过 SqlServer 没卸干净注册表里残留了实例信息。解决方式是先用官方卸载工具清理再手动删掉安装目录和注册表里HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server下的残留项重启后再装。如果机器上已经装了宝塔面板SqlServer 配置管理器可能识别不到实例这时候优先用命令行sqlservr.exe -m进单用户模式排查而不是反复重装。2.2 从零把环境跑起来的具体步骤先确认 JDK 装好命令行能输出版本号java -version javac -version两个命令的版本号要一致不一致说明 PATH 里混了多个 JDK。接着配 Tomcat解压后设置CATALINA_HOME环境变量启动startup.bat浏览器访问http://localhost:8080能看到 Tomcat 默认页就算通了。SqlServer 这边安装时实例名建议用默认实例认证模式选「混合模式」并设置 sa 密码。装完后打开 SqlServer 配置管理器确认 SQL Server 服务和 SQL Server Browser 服务都在运行TCP/IP 协议已启用端口是 1433。然后用 SSMS 或图形化工具连上去执行建库语句CREATE DATABASE TextbookOrder ON PRIMARY ( NAME TextbookOrder_data, FILENAME D:\sqlserver\TextbookOrder_data.mdf, SIZE 10MB, FILEGROWTH 5MB ) LOG ON ( NAME TextbookOrder_log, FILENAME D:\sqlserver\TextbookOrder_log.ldf, SIZE 5MB, FILEGROWTH 5MB );这段语句里FILENAME的路径要换成你机器上真实存在的目录否则建库直接失败。FILEGROWTH设成 5MB 是防止日志文件一次涨太多占满磁盘。建完库之后建一张教材表验证连接USE TextbookOrder; CREATE TABLE Textbook ( BookId INT IDENTITY(1,1) PRIMARY KEY, BookName NVARCHAR(100) NOT NULL, ISBN VARCHAR(20) NOT NULL, Publisher NVARCHAR(50), Price DECIMAL(8,2), Stock INT DEFAULT 0 );IDENTITY(1,1)让 BookId 自增省去手动维护主键的麻烦。NVARCHAR用于书名和出版社因为中文内容用VARCHAR在某些排序规则下会乱码。建完表插一条测试数据确认增删改查都正常再进入下一步。2.3 JDBC 驱动引入与连接测试在项目里新建lib目录把 mssql-jdbc 的 jar 包放进去并在 IDE 的项目结构里把它加到 classpath。写一个最简单的连接测试类import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class DbTest { public static void main(String[] args) throws Exception { // 加载驱动SqlServer 2008 之后统一用这个类名 Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver); String url jdbc:sqlserver://localhost:1433;databaseNameTextbookOrder;encryptfalse; Connection conn DriverManager.getConnection(url, sa, 你的密码); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT COUNT(*) FROM Textbook); if (rs.next()) { System.out.println(教材表记录数 rs.getInt(1)); } rs.close(); stmt.close(); conn.close(); } }encryptfalse是必须加的新版驱动默认开启加密本地没配证书会直接连不上。databaseName指定要连的库不写的话默认连 master。如果这里报「连接被拒绝」先检查 SqlServer 的 TCP/IP 是否启用、1433 端口是否被防火墙拦了。报「登录失败」则检查 sa 账户是否启用、密码是否正确。这一步通了后面 JSP 页面里的数据库操作才有基础。3. 数据库设计教材征订业务落到 SqlServer 表结构3.1 四张核心表撑起整个征订流程教材征订的业务链条其实不复杂用户登录、浏览教材、提交征订、后台汇总。对应到数据库至少需要用户表、教材表、征订订单表、订单明细表。用户表存账号密码和角色教材表存书目信息订单表记录一次征订行为明细表记录这次征订里每本教材的数量。四张表的关系是一个用户可以有多个订单一个订单可以包含多本教材。建表时有个容易忽略的点订单表和明细表之间要用外键关联并且明细表里要冗余存一份教材单价。因为教材价格会变如果明细表只存 BookId事后查历史订单时价格就对不上了。这是血泪经验课程设计答辩时老师很可能追问这一点。CREATE TABLE SysUser ( UserId INT IDENTITY(1,1) PRIMARY KEY, UserName NVARCHAR(30) NOT NULL UNIQUE, Password NVARCHAR(64) NOT NULL, Role NVARCHAR(10) DEFAULT teacher, Dept NVARCHAR(50) ); CREATE TABLE OrderMain ( OrderId INT IDENTITY(1,1) PRIMARY KEY, UserId INT NOT NULL, OrderDate DATETIME DEFAULT GETDATE(), Status NVARCHAR(10) DEFAULT pending, FOREIGN KEY (UserId) REFERENCES SysUser(UserId) ); CREATE TABLE OrderDetail ( DetailId INT IDENTITY(1,1) PRIMARY KEY, OrderId INT NOT NULL, BookId INT NOT NULL, Quantity INT NOT NULL, UnitPrice DECIMAL(8,2) NOT NULL, FOREIGN KEY (OrderId) REFERENCES OrderMain(OrderId), FOREIGN KEY (BookId) REFERENCES Textbook(BookId) );Status字段用pending、confirmed、cancelled三个值区分订单状态后台汇总时只统计confirmed的。UnitPrice在明细表里冗余存储下单时从教材表读当前价格写入后续教材调价不影响历史订单。3.2 汇总查询里 SqlServer 的字符串与数字转换坑后台需要按教材汇总征订总数写出来的 SQL 大概是这样SELECT b.BookName, b.ISBN, SUM(d.Quantity) AS TotalQty FROM OrderDetail d JOIN Textbook b ON d.BookId b.BookId JOIN OrderMain o ON d.OrderId o.OrderId WHERE o.Status confirmed GROUP BY b.BookName, b.ISBN ORDER BY TotalQty DESC;如果 ISBN 在表里定义成了VARCHAR而你想按 ISBN 排序或做数值比较就需要转换。SqlServer 里用CAST或CONVERTSELECT BookName, CAST(ISBN AS BIGINT) AS IsbnNum FROM Textbook WHERE ISNUMERIC(ISBN) 1 ORDER BY IsbnNum;ISNUMERIC先过滤掉非纯数字的 ISBN否则转换时遇到带字母的会直接报错。这是 SqlServer 字符串转数字时最常见的翻车场景。另外如果要做分页OFFSET ... FETCH NEXT是 SqlServer 2012 之后的标准写法SELECT BookName, Price FROM Textbook ORDER BY BookId OFFSET 20 ROWS FETCH NEXT 20 ROWS ONLY;注意OFFSET必须配合ORDER BY使用不写排序的话 SqlServer 直接报语法错误。查出来的结果是跳过前 20 条、取接下来 20 条也就是第 21 到 40 条。3.3 多行合并成一行征订明细的展示需求后台查看某个订单时需要把该订单下所有教材名称拼成一行显示。SqlServer 里用STUFF加FOR XML PATH实现SELECT o.OrderId, STUFF(( SELECT 、 b.BookName FROM OrderDetail d JOIN Textbook b ON d.BookId b.BookId WHERE d.OrderId o.OrderId FOR XML PATH() ), 1, 1, ) AS BookList FROM OrderMain o WHERE o.Status confirmed;FOR XML PATH()把多行拼成一个 XML 字符串STUFF去掉开头多余的分隔符。这个写法在 SqlServer 2017 之后也可以用STRING_AGG替代但课程设计环境里数据库版本不确定用STUFF兼容性更好。4. JSP 页面开发从登录到征订提交的完整链路4.1 登录页与 Session 管理登录页login.jsp负责收集用户名密码提交到LoginServlet处理。Servlet 里查库比对密码成功后把用户信息放进 Session// LoginServlet 核心逻辑 String userName request.getParameter(userName); String password request.getParameter(password); String sql SELECT UserId, Role, Dept FROM SysUser WHERE UserName? AND Password?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, userName); ps.setString(2, password); ResultSet rs ps.executeQuery(); if (rs.next()) { HttpSession session request.getSession(); session.setAttribute(userId, rs.getInt(UserId)); session.setAttribute(role, rs.getString(Role)); response.sendRedirect(bookList.jsp); } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); }用PreparedStatement而不是拼接 SQL是为了防 SQL 注入。密码这里直接明文比对课程设计够用但实际项目里必须加哈希。Session 里存userId和role后续页面用session.getAttribute(userId)判断是否登录用role区分教师和教材科管理员。4.2 教材列表页的分页与坐标定位图片教材列表页bookList.jsp从Textbook表查数据配合前面说的OFFSET ... FETCH NEXT做分页。页面上每本教材显示封面图、书名、价格和一个「征订」按钮。封面图如果要用坐标定位点击区域可以用 HTML 的map和area标签img srcimages/book_cover.jpg usemap#coverMap / map namecoverMap area shaperect coords10,10,120,40 hreforder.jsp?bookId1 alt征订 / /mapcoords里的四个数字分别是左上角 x、y 和右下角 x、y。这种写法适合封面图上固定位置有「立即征订」字样的场景。不过更常见的做法是直接用按钮坐标定位图片点击区域在 JSP 页面里属于偏门需求知道有这么回事就行。4.3 征订提交与订单写入征订页order.jsp显示教材详情和一个数量输入框提交到OrderServlet。Servlet 里先插OrderMain拿到自增的 OrderId再插OrderDetailconn.setAutoCommit(false); try { String sqlMain INSERT INTO OrderMain (UserId, Status) VALUES (?, pending); PreparedStatement psMain conn.prepareStatement(sqlMain, Statement.RETURN_GENERATED_KEYS); psMain.setInt(1, userId); psMain.executeUpdate(); ResultSet keys psMain.getGeneratedKeys(); keys.next(); int orderId keys.getInt(1); String sqlDetail INSERT INTO OrderDetail (OrderId, BookId, Quantity, UnitPrice) VALUES (?,?,?,?); PreparedStatement psDetail conn.prepareStatement(sqlDetail); psDetail.setInt(1, orderId); psDetail.setInt(2, bookId); psDetail.setInt(3, quantity); psDetail.setBigDecimal(4, unitPrice); psDetail.executeUpdate(); conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); }RETURN_GENERATED_KEYS让 JDBC 返回刚插入行的自增主键这是 SqlServer 里拿 OrderId 的标准做法。整个插入过程放在事务里任何一步失败都回滚避免出现有主订单没明细的脏数据。4.4 页面加载完后自动刷新一次的场景有些场景下 JSP 页面需要在加载完成后刷新一次比如从其他页面跳转过来时 Session 里的数据还没更新。用 JavaScript 的window.onload配合location.reload()可以实现但要加个标记防止无限刷新window.onload function() { if (!sessionStorage.getItem(refreshed)) { sessionStorage.setItem(refreshed, true); location.reload(); } else { sessionStorage.removeItem(refreshed); } };sessionStorage在页面刷新后仍然保留所以第一次加载时设标记并刷新第二次加载时读到标记就跳过避免死循环。这个技巧在 JSP 页面里偶尔会用到但多数情况下应该从数据流上解决而不是靠刷新页面。5. 避坑与排查这套技术栈最容易翻车的五个地方5.1 中文乱码现象是页面显示问号原因是编码链没统一JSP 页面顶部要写% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 里在取参数前设置request.setCharacterEncoding(UTF-8)数据库连接 URL 里加characterEncodingUTF-8。三处缺一处就可能乱码。SqlServer 的排序规则如果是Chinese_PRC_CI_ASNVARCHAR字段存中文没问题但VARCHAR字段就可能出问题。建库时排序规则选对能省掉后面很多麻烦。5.2 连接池耗尽现象是页面卡死原因是 Connection 没关JSP 里直接DriverManager.getConnection每次新建连接用完不关的话连接数很快到上限。解决方式是在finally块里关闭ResultSet、Statement、Connection或者用 Tomcat 的 JNDI 数据源配连接池。课程设计里至少要做到手动关闭顺序是先关 ResultSet 再关 Statement 最后关 Connection。5.3 SqlServer 端口不通现象是连接超时原因是 TCP/IP 没启用SqlServer 默认安装后 TCP/IP 协议是禁用的需要在配置管理器里手动启用并重启 SQL Server 服务。启用后确认监听端口是 1433如果改了端口JDBC URL 里也要对应改。防火墙入站规则里放行 1433 端口否则本机连得上、别的机器连不上。5.4 JSP 编译报错现象是 500 错误原因是 jar 包没进 WEB-INF/libJSP 里用到的类如果对应的 jar 包只加到了 IDE 的 classpath 而没复制到WEB-INF/lib目录Tomcat 运行时找不到类直接报ClassNotFoundException。解决方式是把 mssql-jdbc 的 jar 包复制一份到WEB-INF/lib下这是 Tomcat 加载第三方库的标准位置。5.5 自增主键拿不到现象是 OrderId 为 0原因是没加 RETURN_GENERATED_KEYSprepareStatement时不传第二个参数getGeneratedKeys返回空结果集。必须写成conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)并且用executeUpdate而不是executeQuery。这个坑在订单写入场景里特别常见拿不到 OrderId 后面明细就插不进去。6. 进阶技巧用视图和存储过程把汇总逻辑收进数据库后台汇总页面如果每次都用 Java 拼 SQL 查代码会越来越臃肿。更省事的做法是在 SqlServer 里建一个视图把征订汇总逻辑固化下来CREATE VIEW v_OrderSummary AS SELECT o.OrderId, u.UserName, u.Dept, b.BookName, b.ISBN, d.Quantity, d.UnitPrice, d.Quantity * d.UnitPrice AS SubTotal, o.OrderDate, o.Status FROM OrderMain o JOIN SysUser u ON o.UserId u.UserId JOIN OrderDetail d ON o.OrderId d.OrderId JOIN Textbook b ON d.BookId b.BookId;视图建好后JSP 页面里直接SELECT * FROM v_OrderSummary WHERE Statusconfirmed就行不用再写四表关联。如果汇总逻辑还要按院系分组统计可以再包一层存储过程CREATE PROCEDURE sp_DeptSummary Status NVARCHAR(10) AS BEGIN SELECT Dept, COUNT(DISTINCT OrderId) AS OrderCount, SUM(Quantity) AS TotalBooks, SUM(Quantity * UnitPrice) AS TotalAmount FROM v_OrderSummary WHERE Status Status GROUP BY Dept; END;Java 里用CallableStatement调用CallableStatement cs conn.prepareCall({call sp_DeptSummary(?)}); cs.setString(1, confirmed); ResultSet rs cs.executeQuery();把汇总逻辑放进数据库的好处是JSP 页面只负责展示业务规则变更时改存储过程就行不用重新编译 Java 代码。但要注意存储过程里的SUM在数据量大时可能变慢v_OrderSummary视图上如果没索引每次查询都是全表扫描。课程设计的数据量下没问题真实场景里需要在OrderMain.Status和OrderDetail.OrderId上建索引。验证汇总结果是否正确可以手动跑一条对照 SQLSELECT SUM(Quantity * UnitPrice) FROM OrderDetail d JOIN OrderMain o ON d.OrderId o.OrderId WHERE o.Status confirmed;把这个结果和存储过程返回的TotalAmount对一下数字一致说明逻辑没问题。我一般会在部署后先跑一遍对照确认无误再把页面交给别人用。这套 JSP SqlServer 的组合虽然老但把版本对齐、编码统一、连接关闭这三件事做到位跑起来其实很稳。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?