简介:基于JSP+SQL Server的网上书店系统实现源码,附带项目说明文档,面向Java Web学习者、课程设计及毕业设计参考人群,可快速了解在线图书销售管理系统的完整业务流程。包内共有731个文件,以JSP页面、Java源码、CSS样式、JS脚本为主,另含SQL Server数据库文件(mdf/ldf)及文档说明,整体压缩包大小约9.26MB,目录结构清晰,便于定位关键模块。已有94人学习下载。资源覆盖用户登录身份验证、购物车与订单处理、管理员对图书和订单及友情链接的管理等核心功能;选择页面结合用户信息表,展示按用户名称和密码校验合法用户、通过权限编号控制功能、读取用户编号注入Session等典型实现手法。对于想要掌握JSP+SQL Server项目搭建、用户权限控制与购物流程编码的读者,是一份可直接运行的参考资料。
1. JSP + SQL Server网上书店系统到底能给你什么:一个比 Demo 更值得跑通的全栈样本
很多人在某个资源站或师兄的 U 盘里拿到过这种“网上书店系统实现源码”:压缩包里是 src、WebRoot、数据库脚本和一份说明文档,标题写着 java-jsp-SQLserver。第一反应通常是“太老了”,但真去部署时才发现,一套看似简单的 JSP + SQL Server 项目,反而比网上那些纯 CRUD Demo 更能暴露问题:TCP/IP 协议没开、JDBC 驱动没进 lib、数据库排序规则不对导致中文乱码、页面能打开但数据死活加载不出来。这个技术组合放在今天看,前端确实不算新潮,但它把 Java Web 开发里最核心的三层结构、会话跟踪、JDBC 操作、结果集分页全都串了起来,对做课程设计、毕业设计或者想补 Java Web 项目经历的人来说,仍然是一条值得完整复现、然后有东西可写进简历的路径。这篇文章就是按这个目标来拆:怎么把它跑起来、参数怎么配、坑在哪里、以及怎么改出一个能让面试官多说两句的版本。
2. 搭建可复现的开发环境:JDK、Tomcat、SQL Server 与 JDBC 驱动一个都不能少
2.1 为什么这个组合今天还值得装一次
刚接触 Java Web 的时候,不少人把精力放在学 Spring Boot 上,反而把 JSP/Servlet 这条老链路看得太简单。等到真去运行一套“JSP + SQL Server”源码时,才会发现自己根本没弄明白:Tomcat 是怎么把 JSP 编译成 Servlet 的,SQL Server 的登录方式跟 MySQL 有什么不同,Java 程序连接 SQL Server 时驱动从哪来。这些恰恰是很多 Java 面试题里爱往深处问的点,比如“三层架构里表现层怎么调业务层”“Session 和 Cookie 怎么维持登录态”。所以,这套网上书店系统与其说是一个过气项目,不如说是一个训练环境。
常见的源码包结构一般是 src 放 Java 类、WebRoot 或 webapp 放 JSP 页面、一个 .sql 文件放建表和测试数据、一份说明文档描述部署步骤。说明文档通常默认你已经装好了数据库和服务器,会把最关键的环境要求写在最前面。可现实里大多数翻车,不是代码问题,而是环境问题:JDK 版本太新、Tomcat 版本太新、SQL Server 装完没配置、JDBC 驱动拷贝错位置。我一般会建议在动手改代码之前,先把环境按照一套保守但稳定的版本组合搭好,再谈跑通。
2.2 安装与版本匹配:一张清单讲清楚谁和谁能配
版本选择上用国产经验来说就是“别追新”。很多老源码基于 javax.* 命名空间,而 Tomcat 10 起把包名切成了 jakarta.*,旧源码不改 import 直接编译报错。与其花整晚改包名,不如退到 Tomcat 9。JDK 建议用 8 或 11,SQL Server 用 Developer 版或 Express 版足够本地学习。下面是我常用的匹配参考:
| 组件 | 推荐版本 | 理由 |
|---|---|---|
| JDK | 8 或 11 | 兼容 Tomcat 9 和大量 JSP 老项目的语法 |
| Tomcat | 9.0.x 解压版 | 仍是 javax 命名空间,省去改包名的麻烦 |
| SQL Server | 2016 / 2019 Developer 或 Express | 学生和单机学习足够用,OFFSET FETCH 分页可用 |
| JDBC 驱动 | mssql-jdbc-x.x.jar(8.4 或 9.x 均可) | 向下兼容 SQL Server 2012 及以上版本 |
| 数据库管理工具 | SSMS 或 Azure Data Studio | 执行脚本、排查报错比命令行直观 |
注意 JDK 一个就够了,不要在同一台机器上装多个再去改环境变量,很容易把 Tomcat 启动脚本搞晕。SQL Server 安装时如果选了默认实例,实例名是 MSSQLSERVER;如果自定义了实例名,后面 JDBC 连接串里要写成“localhost\实例名”,这是新手最容易忽略的细节。SQL Server 配置管理器这个图形化工具安装后默认就有,后面排查 TCP/IP 全靠它。
2.3 用一段最小 Java 代码验证 SQL Server 连通性
我建议拿到源码之后先不要急着启动 Tomcat,先写一个只有二十行的 Java 类去连一下 SQL Server,把“代码还没跑、环境先通”这件事确认下来。新建一个文件夹 TestDB,把 mssql-jdbc 的 jar 放进去,再写这个文件:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class TestDB { public static void main(String[] args) throws Exception { // 1. 加载驱动类,老写法,主要验证 jar 是否在 classpath Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); // 2. 连接串:库名 bookstore,关闭加密,信任服务器证书 String url = "jdbc:sqlserver://localhost:1433;" + "DatabaseName=bookstore;" + "encrypt=false;trustServerCertificate=true"; Connection conn = DriverManager.getConnection(url, "sa", "123456"); // 3. 最简单的查询,确认能出数 Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT 1 AS ok"); while (rs.next()) { System.out.println("connect ok, value=" + rs.getInt("ok")); } conn.close(); } }这段代码的逻辑分三步:Class.forName 负责让 JVM 认识 SQL Server 驱动类,出的问题一般是“No suitable driver”,十有八九是 jar 没放进编译运行时的 classpath;DriverManager.getConnection 用的协议头是 jdbc:sqlserver,这是微软驱动的固定写法,不可能替换成别的;最后的 SELECT 1 只验证连通性,不依赖业务表是否存在。注意 URL 里的 encrypt=false 是本地开发用的,生产环境应该启 TLS 并把 trustServerCertificate 去掉。运行前先确认 SQL Server 服务已启动,否则会报连接超时或 “Connection refused”。这段跑通了,就说明驱动和数据库两件事都干净了,接下来可以放心进入建库和接项目的阶段。
3. 从建库到登录:数据表设计、JDBC 连接与 JSP 页面的调用链路
3.1 书店系统的三张核心表怎么设计
网上书店系统的模型一般不会复杂,核心就三块:用户、图书、订单。设计数据库时不要一上来把表建得很碎,先把用户表、图书表、订单表和订单明细表立起来,购物车可以用 Session 实现,也可以单独建表,课程设计级别用前三种比较多。SQL Server 里我习惯把字符串字段统一用 NVARCHAR,因为 SQL Server 的 VARCHAR 在不同排序规则下对中文处理的坑更多,NVARCHAR 配合正确的代码页会省掉一部分乱码问题。
-- 创建数据库 CREATE DATABASE bookstore; GO USE bookstore; GO -- 用户表 CREATE TABLE users ( user_id INT IDENTITY(1,1) PRIMARY KEY, username NVARCHAR(50) NOT NULL UNIQUE, password NVARCHAR(100) NOT NULL, email NVARCHAR(100) NULL, created_at DATETIME DEFAULT GETDATE() ); GO -- 图书表 CREATE TABLE books ( book_id INT IDENTITY(1,1) PRIMARY KEY, book_name NVARCHAR(200) NOT NULL, author NVARCHAR(50) NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, category NVARCHAR(50) NULL, image_url NVARCHAR(255) NULL ); GO -- 订单表 CREATE TABLE orders ( order_id INT IDENTITY(1,1) PRIMARY KEY, user_id INT NOT NULL REFERENCES users(user_id), total_amount DECIMAL(10,2) NOT NULL, create_time DATETIME DEFAULT GETDATE() ); GO -- 订单明细表 CREATE TABLE order_items ( item_id INT IDENTITY(1,1) PRIMARY KEY, order_id INT NOT NULL REFERENCES orders(order_id), book_id INT NOT NULL REFERENCES books(book_id), quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL ); GO这里几个参数值得说明:IDENTITY(1,1) 相当于 MySQL 的 AUTO_INCREMENT,让 SQL Server 自己维护主键;DECIMAL(10,2) 表示整数部分 8 位、小数 2 位,金额字段别用 FLOAT,否则算总价会出现一堆小数点;GETDATE() 取的是数据库服务器当前时间,比在 Java 代码里用 new Date() 再传参数更不容易碰到时区问题。顺序上也有讲究:先订单表再订单明细表,因为明细表的外键依赖订单主键;如果一次性执行整个脚本,SQL Server 的 GO 批处理符号必须保留,SSMS 或 sqlcmd 能识别,但有些图形化工具可能不吃这一套。
3.2 JDBC 直连还是连接池:两种写法和一个取舍
课程设计来源的商城项目里,最常见的写法不是连接池,而是一个 DBUtil 或 DBHelper 工具类,类里写死数据库地址、用户名、密码,每次需要数据库操作就调一次 getConnection(),用完再 close()。这种写法在并发量很小的学习项目里完全够用,而且逻辑直白,适合讲清楚 JDBC 的生命周期。Tomcat 连接池是更接近生产的方案,JSP/Servlet 通过 JNDI 拿到 DataSource,连接由容器管理,但配置复杂度高一截。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL = "jdbc:sqlserver://localhost:1433;DatabaseName=bookstore;" + "encrypt=false;trustServerCertificate=true"; private static final String USER = "sa"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError("请把 mssql-jdbc jar 放到 WEB-INF/lib 下"); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这段工具类的关键点有两处:静态代码块只执行一次,所以驱动类加载失败会立刻抛出异常,而不是等到第一次查库才报 ClassNotFoundException;getConnection 每次调用都新建物理连接,在高并发下这是性能瓶颈,但学习阶段反而方便观察“连接被关闭”的问题通常出在哪。很多老源码会在 JSP 页面里直接写 Class.forName,每访问一次页面就加载一次驱动,浪费资源不说,时序上还容易出现奇怪的偶发报错。我一般会把驱动加载挪到静态块里,然后把获取连接的逻辑收敛到这一个类,后面的 Servlet 和 JSP 都只调 DBUtil.getConnection(),这样排查数据库问题只需要看一个文件。
3.3 登录页面里 JSP、Servlet 和 JDBC 是怎么串起来的
登录功能是所有带用户体系的系统里最值得先跑通的模块。典型流转是:用户在 login.jsp 输入用户名密码,表单提交给 LoginServlet,Servlet 里调 DBUtil 拿连接,用 PreparedStatement 查 users 表,比对通过后把用户信息放进 Session,然后重定向到 index.jsp;比对失败则返回 login.jsp 并带一个错误参数。注意流程里不应该把密码明文到处传,但课程设计源码很多就是这样写的,作为复现先跑通,再接改进。
<%@ page import="java.sql.*" %> <% request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); String url = "jdbc:sqlserver://localhost:1433;DatabaseName=bookstore;" + "encrypt=false;trustServerCertificate=true"; conn = DriverManager.getConnection(url, "sa", "123456"); String sql = "SELECT * FROM users WHERE username=? AND password=?"; ps = conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); rs = ps.executeQuery(); if (rs.next()) { session.setAttribute("loginUser", rs.getString("username")); response.sendRedirect("index.jsp"); } else { out.print("<script>alert('用户名或密码错误');history.back();</script>"); } } finally { if (rs != null) rs.close(); if (ps != null) ps.close(); if (conn != null) conn.close(); } %>这段是很多老项目里真实存在的写法,直接放在 JSP 里而不是拆成 Servlet,它演示了三件事:request.setCharacterEncoding 必须放在取参数之前,否则中文用户名会乱码;PreparedStatement 用 ? 占位符再 setString 传参,这种写法能防掉最常见的字符串拼接注入问题;finally 里关闭顺序是从 ResultSet 到 Connection 倒着关,避免连接没释放把 SQL Server 的连接数占满。这段代码同时暴露了它的上限:业务逻辑写在 JSP 里,难以维护。所以它适合用来“先跑通”,不适合当作最终架构。熟练之后,把它改成 Servlet + DAO 是性价比最高的重构方向,也是后续能让面试官认可你“不只会复制”的关键差异。
4. 部署跑通阶段的避坑排查:TCP/IP、sa 密码、中文乱码与重复提交四个翻车点
4.1 部署顺序没有想象中那么随意
用源码包部署网上书店系统,我常用的是一个非常朴素的顺序:SQL Server 建库导数据,再把整个项目目录复制到 tomcat/webapps 下,改数据库连接参数,然后启动 Tomcat 访问 http://localhost:8080/项目名/。如果是 Eclipse 或 IDEA 导出的 WAR 包,放进 webapps 就行,Tomcat 启动时会自动解压。这套流程里最容易出问题的并不是最后一步,而是建库时没有按说明文档里的排序规则和登录方式执行,导致后面连接串怎么写都不对。
4.2 四个翻车点的现象、原因和解决
第一个坑,SQL Server 装了但 Java 连不上。现象是驱动明明加载成功,报 “The TCP/IP connection to the host localhost, port 1433 has failed”。原因多半不是代码,而是 SQL Server 配置管理器里 TCP/IP 协议默认处于禁用状态。解决方法是打开 SQL Server 配置管理器,找到“SQL Server 网络配置”里的实例,双击 TCP/IP,协议状态改成“已启用”,然后到“SQL Server 服务”里重启实例。这个坑十个里能遇到六个,连字符串写一万遍都没用。
第二个坑,sa 账号登录失败。现象是连数据库时提示 “Login failed for user 'sa'”,或者 18456 错误。原因通常是安装 SQL Server 时选了“Windows 身份验证模式”,没有启用 SQL Server 身份验证。解决方法是先用 Windows 身份验证连进 SSMS,在服务器属性里的“安全性”页选择“SQL Server 和 Windows 身份验证模式”,再找到 sa 账号,重新设置一个强密码并开启登录映射。顺手再把服务器属性里的密码策略暂时关掉,否则密码复杂度不够一样会失败。
第三个坑,部署后页面中文乱码。现象是图书名称、作者名显示成??。原因是 JSP 页面、Java 代码和数据库三处的字符编码没有统一。步骤上是先在 JSP 顶部加 pageEncoding="UTF-8",再在取参数前执行 request.setCharacterEncoding("UTF-8"),最后在 JDBC 连接串里加上 characterEncoding=UTF-8,三处一致后重启 Tomcat。SQL Server 建库时我用的是 NVARCHAR,这类中文问题基本能兜住,但注意数据库排序规则如果选错了区域,可能仍然会出现排序和显示不一致的怪现象。
第四个坑,购物车或订单数据被重复提交。现象是用户下单后发现订单数量翻倍,尤其很多教材喜欢把按钮动作绑定在一个超链接上,页面刷新一次就重新提交一次。原因是前端用了 GET 请求触发下单动作,浏览器刷新会原样重放请求。解决方法是把购物车加购和提交订单改成 POST,并在提交成功后用 response.sendRedirect 跳转到新的结果页,也就是 PRG 模式,刷新结果页时不会把上一次提交操作再执行一次。这个问题在一些 JSP 教材的“页面上线后自动刷新”示例里尤其常见。
4.3 页面能打开但数据加载不出来的排查清单
如果 Tomcat 起来了、网页也能显示,但是列表页没有数据,问题通常不在业务代码而在下面几个位置:第一,看 Tomcat 的 localhost.yyyy-MM-dd.log,今天日期对应的日志文件里如果出现 ClassNotFoundException,是驱动 jar 没复制到项目的 WEB-INF/lib 目录;如果出现 “Cannot create PoolableConnectionFactory”,是连接串或数据库服务问题。第二,确认你访问的项目路径是对的,Tomcat 默认端口是 8080,项目名不要拼错。第三,用 F12 看浏览器请求返回的状态码,500 说明服务端代码抛异常,404 说明路径没对上。第四,SQL Server 2019 以后的驱动默认要求加密连接,如果驱动版本太旧,连接时还会报 SSL 相关错误,方案是在 URL 里临时加 encrypt=false;trustServerCertificate=true,把加密层的变量先排除掉。这步做完,数据还是不出来,就去 SSMS 里单独执行一遍查询脚本,确认不是 SQL 语法的问题。
5. 把图书检索改成存储过程调用:给这套网上书店加一个能写进简历的加分项
5.1 在 SQL Server 里创建一个图书检索的存储过程
这套网上书店如果只能做登录和下单,那它在简历里就是最普通的 CRUD 项目。想让它有差异化,第一个值得动手的点,是把图书列表页那个拼接 LIKE 的 SQL 改造成存储过程。这不只是把字符串换个位置,而是把“数据库结构”和“页面表现层”做了一个切割。存储过程写好后,JSP 页面不需要知道 books 表里有哪些字段,只需要调用过程名和传入参数。
USE bookstore; GO CREATE PROCEDURE dbo.SearchBooks @keyword NVARCHAR(50) AS BEGIN SET NOCOUNT ON; SELECT book_id, book_name, author, price, stock FROM books WHERE book_name LIKE '%' + @keyword + '%' OR author LIKE '%' + @keyword + '%' ORDER BY book_id OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY; END GO这段脚本里 OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY 是 SQL Server 2012 之后的标准分页写法,比老式的 ROW_NUMBER() 子查询可读性好很多。如果换成 MySQL,分页语法是 LIMIT 10 OFFSET 0,很多从 MySQL 转过来的人第一次写 SQL Server 都会在这里翻车。存储过程里 SET NOCOUNT ON 是习惯,不让 SQL Server 在返回结果集之前先发一条受影响行数的消息,Java 这边拿 ResultSet 会更干净。LIKE 的写法这里不多做优化,实际大量数据下不要用前置百分号,会阻挡索引利用。
5.2 在 JSP/Servlet 里调用存储过程并验证结果
Java 调用存储过程用的是 CallableStatement,不是普通 PreparedStatement。差别在于调用语法是 {call 过程名(?)},并且参数下标从 1 开始,和 PreparedStatement 一致。拿到源码后我把原来直接在 DAO 里写 SQL 的地方,先替换成 5.1 这个存储过程,再验证页面检索是否返回相同的数据。
String sql = "{call SearchBooks(?)}"; try (Connection conn = DBUtil.getConnection(); CallableStatement cs = conn.prepareCall(sql)) { cs.setString(1, keyword); try (ResultSet rs = cs.executeQuery()) { while (rs.next()) { // 每行数据封装成 Book 对象,或直接输出到 JSP String name = rs.getString("book_name"); // 业务代码:填充列表,交给页面渲染 } } }这段代码的核心价值是调用路径变简单了:页面层只依赖过程名和参数,不暴露表字段。验证时先在 SSMS 里执行 EXEC SearchBooks N'Java',确认能返回 Java 相关书目,再用浏览器访问检索页面对比结果。这个改动做完,你简历里写这个项目的时候,就多了一个“用存储过程封装图书检索,分离数据访问与页面逻辑”的描述,面试官顺着这个点往下聊,你也能从索引、分页、参数化三个方向接住话。我早期学习时有一个教训:拿到老项目不要急着整个推倒重写,先把一个最常用功能从 JSP 内嵌 SQL 改成存储过程调用,跑通后再逐步把其他模块挪进 Servlet + DAO。这种小步重构的方式,比一次性重写二十个 JSP 更稳妥,也更能让你理解每层到底是做什么的。希望这篇对你有帮助。
本文还有配套的精品资源,点击获取