ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java Web网上书店管理系统实战:SQLServer2008+JDBC完整课设指南

Java Web网上书店管理系统实战:SQLServer2008+JDBC完整课设指南 简介基于Java与SQLServer2008实现的Web网上书店管理系统面向Java课程设计及毕业设计场景覆盖游客、会员和管理员三类角色的完整业务流程帮助学习者理解在线图书销售、购物车结算、订单审核、后台维护等典型功能设计。压缩包共包含460个文件大小约9.89MB主要文件类型有JSP页面、Java源码与编译后的class、数据库SQL脚本、用于界面展示的gif和jpg图片以及少量CSS、jar包等运行依赖其中JSP与Java代码实现了前台展示和业务逻辑SQL文件提供了建库建表脚本图片素材支撑前端UI。已有376人学习适合JavaWeb初学者作为课程设计参考。资源内含图书分类展示、关键字查询、会员注册登录与信息维护、购物车管理、订单提交与发货处理、管理员对图书和订单及新闻评论的管理等功能附带开发文档和数据库脚本目录结构清晰便于直接部署运行和二次开发。1. 网上书店管理系统是什么一套 Java Web 课设的完整骨架如果学校的课设题库里抽中了“网上书店管理系统”大概率你会看到这行标题基于 JavaSQLServer2008 实现Web网上书店管理系统。它不是什么高深架构就是用 JSP/Servlet 做后端SQLServer2008 做数据仓库浏览器里完成注册、登录、浏览图书、购物车和订单处理。难点在于这套组合的年代感很强——SQLServer2008 的分页方式、JDBC 驱动版本、中文编码踩过的坑足够写满一张答辩 PPT。这篇笔记不是给你一个现成包而是把链路拆开建库、连库、编码、部署每一步都给出可复制的参数和命令。适合正在做课设的学生也适合想快速搭一个商品交易型 Web 项目练手的初级 Java 工程师。2. 技术选型与数据建模先想清楚实体关系再写代码网上书店管理系统这类业务并不复杂核心就是“用户看到书、买书、生成订单”。但很多人在写代码之前就急着建表结果写到购物车时发现少了一张关联表又回头改表结构改到最后连外键都不敢加。我习惯先把选型和数据模型定下来再动手写 Servlet后面会顺手很多。2.1 选型JSPServlet 比 Spring Boot 更贴近这个标题标题里只写了“JavaSQLServer2008”没有指定框架。常见做法是用 JSPServletJDBC这是高校课设的默认形态。为什么不是 Spring Boot第一很多课程大纲还在用 Servlet 讲 Web 原理答辩时老师更爱问 JDBC 和 SQL 细节第二SQLServer2008 是老版本数据库Spring Boot 3.x 默认的驱动和相关依赖对它反而有兼容成本第三JSPServlet 打包成 WAR 放到 Tomcat 就能跑环境简单适合演示。当然你可以在 IDEA 2024 版本创建 web 项目时选择 Jakarta EE 或者直接建一个 Maven 项目引入 Servlet 依赖核心逻辑不变。我这里后面统一以 javax.servlet 3.1 版本的写法来讲因为 Tomcat 8.5/9.0 对这种老课设最友好。如果你用 Spring Boot连接串、事务、分页逻辑依然是同一套只是入口从 Servlet 换成了 Controller。2.2 实体拆解用户、图书、购物车、订单四类主表加两张子表做数据建模第一步是梳理角色。这个系统有两个角色普通用户和管理员。普通用户能注册、登录、浏览图书、加购物车、下单管理员能在后台维护分类、图书查看订单。根据这些行为我拆出六张表表名用途归属角色users用户账号与个人信息普通用户、管理员category图书分类管理员维护book图书基本信息、价格、库存前台展示、后台维护cart购物车临时记录普通用户orders订单主表记录总价和状态普通用户创建、管理员查看order_item订单明细每本书单独一行订单附属表users 表的典型字段是user_id 自增主键、username 唯一、password 存加密后的字符串、real_name、phone、address、is_admin 布尔位、created_at 默认时间。注意密码字段不要用 varchar(20)要留长一点因为 MD5 或者加盐 MD5 之后至少有 32 个字符。book 表要重点关注价格和库存。价格用 decimal(10,2)不能用 float否则累计金额会飘。库存用 int下单时扣减扣减要带条件防止超卖。另外我建议加一个 is_on_sale 字段做下架处理而不是直接删除记录这样订单明细还能引到历史上的图书名称。订单两张表为什么要拆分因为一个订单里可能有多种书orders 表只放 user_id、total_price、status、create_time而 order_item 表每一行记录 book_id、book_name、price、quantity。order_item 里冗余 book_name 和 price 是故意的图书信息之后改价或改名已经生成的订单不能被历史数据影响。这个设计思路也可以在答辩时讲。2.3 页面流转与角色权限普通用户和管理员的两条链路页面结构决定项目的可读性。我的习惯是非登录用户能看到 index.jsp、book_list.jsp、book_detail.jsp、login.jsp、register.jsp登录用户才能访问 cart.jsp、order_confirm.jsp管理员单独一套放在 admin 目录下例如 admin/index.jsp、admin/book_manage.jsp、admin/order_manage.jsp。Servlet 层的转发规则是登录成功后把用户对象塞进 session后续页面从 session 里取。购物车和订单相关 Servlet 都要先做会话检查不能只是把按钮藏起来。管理员权限用一个过滤器做最简单——如果 URL 以 /admin/ 开头而 session 里没有 is_admintrue就重定向到登录页。这个过滤器代码量不大但答辩时很加分。数据模型定下来之后下一步就是环境搭建。见过太多人卡在“驱动装不上、连不上库”这个起跑线上所以下面把 SQLServer2008 的初始化步骤一个一个列出来。3. 环境搭建与数据库初始化一台干净机器怎么跑通 JDBC网上书店管理系统需要两个环境SQLServer2008 和 Tomcat。SQLServer2008 是老版本安装包在学校机房常见但安装好之后不做防护配置JDBC 连不上是常态。这一章按“装好库、建好表、连上 Java”三步走。3.1 SQLServer2008 的安装后三件事开 TCP/IP、改混合认证、重启服务SQLServer2008 默认可能禁止 TCP/IP 连接这就导致 Java 程序通过 1433 端口连不上。很多人的 JDBC 报错“Connection refused”但表面上 SSMS 却连得很好原因就在这里。第一件事打开“SQL Server Configuration Manager”在左侧导航展开“SQL Server 网络配置”选中你的实例例如“MSSQLSERVER”在右侧找到“TCP/IP”协议。如果它的状态是“已禁用”右键启用。接着双击 TCP/IP在“IP 地址”页签最底下找到“IPAll”把“TCP 端口”改成 1433。这一步很关键默认 TCP 动态端口可能是空的或者不是 1433。第二件事用 Windows 身份验证登录 SSMS右键服务器属性切到“安全性”把服务器身份验证改成“SQL Server 和 Windows 身份验证模式”。然后在“安全性→登录名”里找到 sa右键属性设置一个强密码并把 sa 的状态改为“已启用”。这一步不做后面用 sa 登录会报 18456。第三件事回到 Configuration Manager在“SQL Server 服务”里右键重启数据库服务。改协议、改认证方式后必须重启否则配置不生效。如果是命名实例例如“localhost\SQLEXPRESS”连接串也要改后面会说。3.2 建库建表脚本BookStore 的第一次 SQL我习惯在 SSMS 里先手工执行一遍建库脚本确认没有语法错误再复制到 Java 代码或者 courseware 里。SQLServer2008 的脚本有一个特点用 GO 作为批处理分隔符。在 SSMS 里执行没问题但如果通过 JDBC 批量执行GO 要删掉否则会报“GO 附近有语法错误”。下面是一份能直接跑的最小脚本CREATE DATABASE BookStore; GO USE BookStore; GO CREATE TABLE dbo.users ( user_id INT IDENTITY(1,1) PRIMARY KEY, username NVARCHAR(50) NOT NULL UNIQUE, password NVARCHAR(64) NOT NULL, real_name NVARCHAR(50), phone NVARCHAR(20), address NVARCHAR(200), is_admin BIT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT GETDATE() ); GO CREATE TABLE dbo.category ( category_id INT IDENTITY(1,1) PRIMARY KEY, category_name NVARCHAR(50) NOT NULL ); GO CREATE TABLE dbo.book ( book_id INT IDENTITY(1,1) PRIMARY KEY, category_id INT NOT NULL, book_name NVARCHAR(100) NOT NULL, author NVARCHAR(50), publisher NVARCHAR(100), price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, sales INT NOT NULL DEFAULT 0, cover_url NVARCHAR(200), description NVARCHAR(MAX), is_on_sale BIT NOT NULL DEFAULT 1, FOREIGN KEY (category_id) REFERENCES dbo.category(category_id) ); GO CREATE TABLE dbo.cart ( cart_id INT IDENTITY(1,1) PRIMARY KEY, user_id INT NOT NULL, book_id INT NOT NULL, quantity INT NOT NULL DEFAULT 1, add_time DATETIME NOT NULL DEFAULT GETDATE(), FOREIGN KEY (user_id) REFERENCES dbo.users(user_id), FOREIGN KEY (book_id) REFERENCES dbo.book(book_id) ); GO CREATE TABLE dbo.orders ( order_id INT IDENTITY(1,1) PRIMARY KEY, user_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL DEFAULT GETDATE(), FOREIGN KEY (user_id) REFERENCES dbo.users(user_id) ); GO CREATE TABLE dbo.order_item ( item_id INT IDENTITY(1,1) PRIMARY KEY, order_id INT NOT NULL, book_id INT NOT NULL, book_name NVARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL, FOREIGN KEY (order_id) REFERENCES dbo.orders(order_id), FOREIGN KEY (book_id) REFERENCES dbo.book(book_id) ); GO逻辑说明users.username 加了 UNIQUE防止重复注册password 用 NVARCHAR(64)给加密预留空间book.category_id 指向 category构成外键order_item 冗余 book_name 和 price保证订单生成后图书改价不影响历史快照。status 字段用 TINYINT0 表示待支付、1 表示已支付、2 表示已发货简单清晰。参数要注意的地方是 DECIMAL(10,2)它最多存 99999999.99一个书店的订单总额不可能超过这个量级。如果不小心把价格列设计成 FLOAT累加多个商品后会出现 19.999999 这种尾巴财务对不上这就是用 DECIMAL 的原因。3.3 Java 端连接DBUtil 与 JDBC URL 的参数约定Web 项目里与数据库相关的连接逻辑我习惯收敛到一个 DBUtil 类里。先确认驱动 jar 放到了 WEB-INF/lib 下SQLServer2008 通常用 sqljdbc4.jar如果你的 Tomcat 是 9.0也可以尝试 sqljdbc42.jar但 2008 数据库的兼容性还是 4.0 更稳。package com.bookstore.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { // 驱动类名必须写全不能简写 private static final String DRIVER com.microsoft.sqlserver.jdbc.SQLServerDriver; // 默认实例用 localhost:1433命名实例要写成 localhost\\SQLEXPRESS private static final String URL jdbc:sqlserver://localhost:1433;DatabaseNameBookStore; private static final String USER sa; private static final String PASSWORD Your_Strong_Pass; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(找不到 SQLServer JDBC 驱动请检查 jar 是否在 WEB-INF/lib 下); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, java.sql.PreparedStatement ps, java.sql.ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (ps ! null) { try { ps.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }代码说明静态代码块里用 Class.forName 注册驱动如果 jar 缺失会在类加载时直接抛出清晰错误避免后续连接时出现莫名其妙的空指针。getConnection 返回 JDBC Connection调用方用 try-with-resources 或者 finally 关闭连接。close 方法把三个资源全关闭顺序是先 ResultSet 再 PreparedStatement 最后 Connection。URL 格式是jdbc:sqlserver://主机:端口;DatabaseName库名。注意这里的分号是英文分号后面不能加多余空格。SQLServer 的 JDBC 驱动不支持类似 MySQL 的useUnicodetruecharacterEncodingUTF-8参数加了反而可能报错中文问题的解决要靠数据库排序规则和代码里的编码设置后面避坑章节会展开。USER 是 saPASSWORD 是在 3.1 里设置的那个密码不要用空密码SQLServer2008 对空密码的登录策略很严格。环境通了之后就可以开始写核心功能了。网上书店看起来功能多但 80% 的分数集中在登录、分页、购物车下单这三块。4. 核心功能编码注册、登录、图书分页与下单事务这一章要写的不是全部业务代码而是把最容易卡住你的三个环节拿出来拆开讲。每一段代码我都写了注释并且会说明为什么要这么写。4.1 登录注册用 PreparedStatement 挡住第一类攻击先看登录 Servlet这是一个典型写法package com.bookstore.servlet; import com.bookstore.util.DBUtil; import com.bookstore.util.MD5Util; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; import java.sql.*; WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 读参数之前先设置编码否则中文用户名会乱码 request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); String username request.getParameter(username); String password MD5Util.md5(request.getParameter(password)); String sql SELECT user_id, real_name, is_admin FROM users WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { HttpSession session request.getSession(); session.setAttribute(userId, rs.getInt(user_id)); session.setAttribute(realName, rs.getString(real_name)); if (rs.getBoolean(is_admin)) { response.sendRedirect(request.getContextPath() /admin/index.jsp); } else { response.sendRedirect(request.getContextPath() /index.jsp); } } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } } catch (SQLException e) { e.printStackTrace(); response.sendError(500, 数据库异常); } } }逻辑说明密码在 Java 端先做 MD5再传给数据库比较这样数据库里不会出现明文密码答辩时也可以解释为“即使数据库泄露用户原密码也不会直接暴露”。为什么不直接存明文因为管理员在后台能看到所有用户密码如果数据被导出那么用户在其他系统上的同名密码也会遭殃。MD5 虽然不算强加密但课设层面比明文高一个档次。参数说明PreparedStatement 的 ? 占位符会在执行时被驱动处理避免字符串拼接造成的 SQL 注入。这是 java 开发工程师面试题里必问的点也是手写 JDBC 时最容易犯的错。注册功能类似只是把 SELECT 换成 INSERT插入前先查一次 username 是否存在这里不再重复写。4.2 图书分页SQLServer2008 不支持 LIMIT只能用 ROW_NUMBER()前台的图书列表必须分页否则几十本书一页展示太丑。如果用 MySQL你会很自然地写LIMIT offset, pageSize。但 SQLServer2008 没有 LIMIT也不支持 2012 版的OFFSET...FETCH。我第一次从 MySQL 转过来时就在这里翻车控制台报语法错误的时候一脸懵。正确写法是用 ROW_NUMBER() 生成行号然后在外层筛选int page 1; int pageSize 8; String pageParam request.getParameter(page); if (pageParam ! null pageParam.matches(\\d)) { page Integer.parseInt(pageParam); } int start (page - 1) * pageSize 1; int end page * pageSize; String countSql SELECT COUNT(*) FROM book WHERE is_on_sale 1; String listSql SELECT book_id, book_name, author, price, sales FROM ( SELECT ROW_NUMBER() OVER (ORDER BY sales DESC) AS row_num, book_id, book_name, author, price, sales FROM book WHERE is_on_sale 1) t WHERE row_num BETWEEN ? AND ?; PreparedStatement psCount conn.prepareStatement(countSql); ResultSet rsCount psCount.executeQuery(); int totalCount 0; if (rsCount.next()) { totalCount rsCount.getInt(1); } int totalPages (totalCount pageSize - 1) / pageSize; PreparedStatement psList conn.prepareStatement(listSql); psList.setInt(1, start); psList.setInt(2, end); ResultSet rsList psList.executeQuery();逻辑说明ROW_NUMBER() 必须包含 OVER 子句这里按销量排序形成当前索引页的行号范围。子查询必须起别名 t否则 SQLServer 会报“对象名称无效”。外层用BETWEEN ? AND ?start 和 end 是绝对行号不是偏移量。比如 page2pageSize8那么 start9end16取的正好是第二页。参数说明页码参数用正则\\d过滤防止用户把 page 传成字符串导致 parseInt 抛异常。计算 totalPages 用(totalCount pageSize - 1) / pageSize这是整数向上取整的通用写法。前端链接就是book_list?page2这种地址Servlet 里重新走一遍查询。如果你真的想在 SQLServer2008 用 FETCH不行它是 2012 以后的功能别在 2008 上试。4.3 购物车与下单事务里扣库存防止超卖购物车加入过程很简单检查购物车是否存在同用户同图书存在则加数量否则插入一条。真正难的是下单因为要同时插入订单主表、订单明细、扣库存、清空购物车任何一个环节失败都会产生脏数据。Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); // 1. 插入订单主表 String sqlOrder INSERT INTO orders(user_id, total_price, status) VALUES(?, ?, 0); PreparedStatement psOrder conn.prepareStatement(sqlOrder, Statement.RETURN_GENERATED_KEYS); psOrder.setInt(1, userId); psOrder.setBigDecimal(2, totalPrice); psOrder.executeUpdate(); ResultSet keys psOrder.getGeneratedKeys(); int orderId 0; if (keys.next()) { orderId keys.getInt(1); } // 2. 遍历购物车插入订单明细并扣库存 for (CartItem item : cartItems) { String sqlItem INSERT INTO order_item(order_id, book_id, book_name, price, quantity) VALUES(?, ?, ?, ?, ?); PreparedStatement psItem conn.prepareStatement(sqlItem); psItem.setInt(1, orderId); psItem.setInt(2, item.getBookId()); psItem.setString(3, item.getBookName()); psItem.setBigDecimal(4, item.getPrice()); psItem.setInt(5, item.getQuantity()); psItem.executeUpdate(); // 扣库存时带上 stock quantity可以防止超卖 String sqlStock UPDATE book SET stock stock - ? WHERE book_id ? AND stock ?; PreparedStatement psStock conn.prepareStatement(sqlStock); psStock.setInt(1, item.getQuantity()); psStock.setInt(2, item.getBookId()); psStock.setInt(3, item.getQuantity()); int affected psStock.executeUpdate(); if (affected 0) { throw new SQLException(库存不足事务回滚); } } // 3. 清空购物车 String sqlClearCart DELETE FROM cart WHERE user_id ?; PreparedStatement psClear conn.prepareStatement(sqlClearCart); psClear.setInt(1, userId); psClear.executeUpdate(); conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); conn.close(); }逻辑说明事务的本质是把所有操作绑定成一个原子任务。setAutoCommit(false)之后后续的 INSERT/UDPATE/DELETE 不会立刻提交直到手动conn.commit()。如果中间抛出异常rollback()会撤销前面所有操作。这个机制是 java 保证数据一致性的基础答案面试官问到了可以直接把这段代码演示给他看。扣库存的 SQL 里stock ?是关键。如果两个用户同时下单数据库行锁会让一个请求先执行第二个请求执行时发现 stock 已经不够UPDATE 影响行数为 0代码抛出异常触发回滚。这就避免了最后一件商品被两个人同时买走。参数说明Statement.RETURN_GENERATED_KEYS是为了拿到自增主键 order_id。在 SQLServer2008 中IDENTITY 列自增后可以通过 getGeneratedKeys 获取。failed to load plugins 这类插件报错和系统无关不用理会。下单成功后前端把 totalPrice 作为隐藏字段提交这个字段不能直接信任最好在 Servlet 里重新根据购物车计算否则用户可以篡改价格。这个细节我在答辩时被老师问过提前写上能加分。5. 避坑排查SQLServer2008 连接与中文乱码的五个典型翻车现场这一章写给所有被这套技术栈折磨过的人。每条都是“现象→原因→解决”的结构你可以直接对照自己的报错信息来找答案。5.1 sqljdbc4.jar 总是导致 ClassNotFoundException现象Tomcat 启动正常但访问任意一个触发数据库操作的页面控制台出现ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver。原因sqljdbc4.jar 没有放进 WEB-INF/lib 目录。IDEA 里如果你只是在项目结构中添加了 library但没有点选“Output 到 lib”打包或运行时驱动不会被复制到 WEB-INF/libTomcat 自然找不到。解决把 sqljdbc4.jar 物理复制到webapps/你的项目/WEB-INF/lib/下或者 IDEA 中右键 jar 选“Add as Library”然后在 Artifacts 的 Output Layout 里确认它出现在 WEB-INF/lib 下面。改完重新部署并清空 Tomcat work 目录。最直接的验证方式在 WEB-INF/lib 目录下能看到 jar 文件而不是只存在于 IDE 的依赖列表里。5.2 1433 端口连不上先检查 Configuration Manager而不是防火墙现象SQLServerManagementStudio 用 Windows 身份验证能连上数据库但 Java 程序执行到 DriverManager.getConnection 时抛出The TCP/IP connection to the host localhost, port 1433 has failed。原因SQLServer2008 安装后 TCP/IP 协议很可能是禁用状态或者监听端口不是 1433。很多人第一反应是关防火墙但 Windows 防火墙通常不会拦截本机回环真正问题是服务没开 TCP。解决打开“开始菜单→Microsoft SQL Server 2008→配置工具→SQL Server Configuration Manager”在“SQL Server 网络配置”中启用 TCP/IP。然后双击 TCP/IP查看“IP 地址”页签把 IPAll 的 TCP 端口设置为 1433删除动态端口。最后在“SQL Server 服务”中重启 SQL Server 服务。如果连的是云服务器上的数据库还要到云控制台的安全组入方向放行 1433这个往往被忽略。5.3 中文乱码请求编码、页面编码、数据库排序规则三管齐下现象在注册页面输入“张三”存入数据库后变成“???》或者页面显示乱码。原因这一条链条很长。第一JSP 页面没有设置pageEncodingUTF-8第二Servlet 里没有在读取参数前调用request.setCharacterEncoding(UTF-8)第三数据库表字段用的是 varchar而 varchar 按排序规则解释字节中文容易乱第四SQLServer JDBC 连接串不支持 characterEncoding 参数网上很多教程是照搬 MySQL 的写上去反而报错。解决JSP 头部统一% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %所有 Servlet 在request.getParameter()之前先设置 UTF-8。数据库表字段优先用 NVARCHAR因为它是 Unicode 存储。数据库排序规则可以在建库时设置成Chinese_PRC_CI_ASSSMS 里右键库属性选择排序规则。检查这三个位置中文乱码能解决掉 90%。另外Tomcat 的 URIEncoding 在 server.xml 里可以默认不改但如果你想直接传中文到 URL 参数可以把URIEncodingUTF-8加到 Connector注意这只是对 GET 请求有用。5.4 sa 登录失败错误 18456现象JDBC 报错Login failed for user sa或者 SSMS 用 sa 登录被拒绝。原因SQLServer2008 默认的身份验证模式是“Windows 身份验证”sa 账号默认可能禁用也可能密码为空但被策略禁止。这是微软的默认安全策略不是 bug。解决先用 Windows 身份验证登录 SSMS在服务器属性→安全性里切换到“SQL Server 和 Windows 身份验证模式”。然后到“安全性→登录名→sa”右键属性设置一个强密码状态改为“已启用”。最后执行一条语句让策略生效ALTER LOGIN sa WITH PASSWORD Your_Strong_Pass; ALTER LOGIN sa ENABLE;注意如果 SQLServer 只在 Windows 认证模式下运行即使你改了连接串也没用必须重启 SQL 服务。另外 18456 错误有很多状态码如果状态不是简单的密码错误可以查看 SQL Server 日志中对应消息。这属于系统级的折腾但课设环境里只要按上面的步骤走基本不会卡住。5.5 分页 SQL 语法报错别用 LIMIT也别用 OFFSET FETCH现象执行SELECT * FROM book ORDER BY sales LIMIT 0, 8提示关键字 LIMIT 附近有语法错误换成OFFSET 0 ROWS FETCH NEXT 8 ROWS ONLY同样报错。原因SQLServer2008 既没有 MySQL 的 LIMIT也没有 SQLServer2012 才引入的 OFFSET/FETCH。它的语法结构是 T-SQL 老版本分页只能用 ROW_NUMBER() 或者嵌套 TOP。很多人想当然地把 MySQL 语法搬过来报错后才开始找。解决用第 4.2 节那段 ROW_NUMBER() 的写法这是 SQLServer2008 最稳定、最常见做法。如果你项目中需要兼容 2008 和 2014建议抽一个 PageHelper 类根据数据库连接元数据判断版本生成不同方言的分页 SQL。这是经验之谈因为我曾经为了图方便在所有 SQL 里统一用 OFFSET FETCH结果测试环境是 SQLServer2008 全绿、生产环境也是 2008只能全改。所以一开始就用 ROW_NUMBER() 是最省事的。6. 部署到 Tomcat 的验证清单与连接池改造技巧6.1 按这条清单过一遍验收流程项目能不能演示成功不只是代码能不能跑。我给自己定了一条最小验证路径把项目导出成 WAR 放到 Tomcat webapps 下启动 Tomcat然后依次做——注册新用户、退出、用新用户登录、浏览图书分页、查看详情、加入购物车、调整数量、提交订单、管理员登录、后台看到新订单、修改图书库存。这个过程不允许跳过每一步都错了才算部署通过。部署时注意目录别搞错Tomcat 的 webapps 是放置 WAR 包的位置项目访问路径默认是 WAR 包名。如果你在 IDEA 里配置 Tomcat 的 Deployment 是多级路径要记下上下文路径。另外 SQLServer2008 的端口是 1433Tomcat 是 8080两者不冲突。验证时可以打开 SQL Server Profiler 看看查询是否真的走了数据库这在调试分页和事务时很有用。6.2 把 DBUtil 临时替换成 DBCP 连接池课设做到一百分的常见做法是把 DriverManager 换成连接池因为 DriverManager 每次连接都要重新建立物理 TCP 连接高并发场景下很慢。常见做法是使用 Apache Commons DBCP2 或 C3P0它们可以在 Tomcat 启动时初始化一定数量的连接需要时从池里借用用完归还。关键配置只有三步引入 commons-dbcp2、commons-pool2 两个 jar在项目里创建一个 BasicDataSource 实例把 DBUtil 的 getConnection 改成从 dataSource 拿连接。启动后连接池会预热Tomcat 控制台日志会变多但页面响应会更快。这里可以先不追求完美因为课设的数据量没到需要连接池的程度但写出来能反映出你对性能有认知。我最后再说一个习惯我在写这类 JSPServlet 系统时所有 SQL 都会先在 SSMS 里跑一遍确认字段名和函数没问题再复制到 Java 代码里。分页和事务最容易翻车但用这个顺序能提前拦截八成错误。希望这些参数和踩坑记录能让你少花几个通宵顺利把网上书店管理系统跑起来。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进