简介:基于JavaWeb的网上书店系统是一套可直接运行的完整项目源码,主要面向正在准备Java课程设计、期末大作业或毕业设计的计算机相关专业学生,也适合希望快速熟悉Servlet/JSP开发流程的自学者。项目采用JSP+Servlet+JDBC的经典MVC架构,代码注释完整,前台包含图书分类浏览、关键词检索、购物车管理、订单结算以及用户注册登录等功能模块,后台覆盖图书信息管理、库存调整、订单状态更新等常用操作,并附带数据库初始化SQL脚本,导入Eclipse或IDEA后即可启动运行,方便按需二次开发。压缩包共131个文件,其中38个JSP页面负责视图展示,25个Java类承担业务逻辑,7个Jar包提供外部依赖支持,另有CSS、JavaScript和大量图片资源用于美化页面与交互,搭配SQL脚本、工程配置文件以及字体图标等资源,整体仅4.84MB,结构清晰、层级分明,适合学习者逐层拆解对照。目前已有685人学习下载,无论是想弄懂MVC分层思想与JavaWeb请求响应流程,还是需要一套能够直接答辩演示的毕业设计项目,都能从中获得实实在在的帮助。
1. JavaWeb网上书店系统:大作业清单里“最眼熟也最容易翻车”的一份源码
期末前两周,很多人会从网盘里翻出一个类似名字的文件:基于JAVAweb的网上书店系统源码.zip。解压后是一整套JSP页面、Servlet类、一个database.sql,注册登录、图书分类、购物车、下单结算看起来都有。把源码丢进IDEA以后战斗才开始——Tomcat版本不匹配、MySQL 8的驱动类名变了、页面样式全404,能在三天内把整套流程跑通,已经算顺利。
这套源码的定位很清晰:JSP + Servlet + JDBC + Tomcat + MySQL的老牌组合,是JavaWeb课程设计和毕业设计里最稳的技术选型。它贴近教材、业务直观、答辩好讲,而且改造空间大——加个分页、做个订单状态流转、换个Bootstrap皮肤,都是现成的加分点。适合两类人:正在找JavaWeb课程设计或毕业设计模板的学生,以及想借一个完整项目把Servlet/JSP底子补扎实的入门开发者。
提示:先别急着把源码当成品用。能跑通才算开始,真正值钱的是后面怎么把它改成自己的东西。
2. 源码导入IDEA前先对齐三件套:JDK与Tomcat版本、MySQL连接,一个不对都启动失败
2.1 先识别这套源码是什么结构:Maven工程还是普通Web工程
老课设源码就两种形态。一种根目录带pom.xml,是Maven工程;另一种根目录是WebContent或WebRoot,lib下面躺着servlet-api.jar、mysql-connector-java.jar,是普通动态Web项目。两种形态导入IDEA的方式完全不同:Maven工程等依赖下载完就能直接编译,普通Web工程要手动把lib目录挂到Artifact里,否则运行时一堆ClassNotFound。
不确定形态时,打开终端在解压目录里跑两条命令,比肉眼翻目录快:
# 查有没有Maven标记文件 ls -la | grep pom.xml # 查传统Web目录和已经打包好的第三方jar ls -d WebContent WebRoot src 2>/dev/null find . -name "*.jar" | head -20第一行有输出,就当Maven工程处理;没输出,就继续看第二行找出来的目录名。find列出来的jar里,重点关注有没有mysql-connector-java,后面的版本号直接决定你在MySQL那步怎么配。这里有个高频坑:lib里放的是5.x驱动,本机装的是MySQL 8,后面连库几乎必报错。哪怕你手里有黑马JavaWeb那套笔记,按老教程一步一步来,也不如先把驱动版本对齐到8.x再往下走。
2.2 版本选型:Tomcat 9配合JDK 8,数据库驱动用新类名
这套技术栈对版本非常敏感,踩错一个,后面所有报错都会变得很玄学。Tomcat 10之后把javax.servlet整体改成jakarta.servlet包,老代码里写死的import javax.servlet.http.HttpServlet直接在编译期就找不到类;JDK 11以上对老JDBC驱动也有模块化访问限制,启动时经常冒出一堆反射警告。长期验证下来,最稳的组合是下面这张表:
| 组件 | 推荐 | 不推荐的原因 |
|---|---|---|
| Tomcat | 9.0.x | 10以上Servlet包名变为jakarta,老工程编译不过 |
| JDK | 8 | 11以上对老JDBC驱动有模块限制,排查成本高 |
| MySQL | 5.7 / 8.0 | 8.0必须用新驱动类com.mysql.cj.jdbc.Driver |
| 构建方式 | 原工程是Maven就沿用 | 普通Web工程别强行转Maven,转完还是一堆环境错误 |
Tomcat 9最好找官网的Core zip版本,解压后免安装,IDEA里指定到目录即可。JDK 8的安装包网上还能找到,注意IDEA的Project Structure里要设置到同一个JDK,不要出现编译用JDK 8、运行用JDK 17的情况。MySQL 5.7和8.0都能跑,但8.0的驱动类名从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver,这一个字母的差别会带来一整页的报错。如果本机已经装了Tomcat 10,最省事的做法不是去改源码里的import,而是再下载一个9.x给这个项目单独用——同一个IDEA里可以同时配置多个Tomcat版本,Run Configuration里单独选,不互相影响。
2.3 IDEA运行JavaWeb项目的最小配置:Artifact、依赖和启动项
Maven工程的话,pom.xml里几个关键依赖要先锁对。一个能在这套项目里正常编译的依赖组合长这样:
<dependencies> <!-- 编译期需要,Tomcat 自带,所以 provided --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- JSP 同样由容器提供 --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!-- JDBC 驱动要打进产物里 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> </dependencies>servlet-api和jsp-api这两个依赖,编译时需要,但Tomcat容器里已经带了实现,所以scope必须写provided。如果漏掉scope,打出的war会把Tomcat的类也塞进去,部署时出现重复类加载,报错非常难查。mysql-connector-java不加scope,默认编译和打包都有,运行时才能找到驱动。注意,如果最终换用了Tomcat 10,这里要同步换成jakarta.servlet-api,不然编译期就开始报红。
JDBC连接参数集中在src/main/resources或src目录下的jdbc.properties里,老项目经常叫db.properties。这是JavaWeb连接MySQL数据库时最常改的文件,改不好后面全是数据库的报错:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/bookstore?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=123456url里三个参数是血泪经验攒出来的。serverTimezone解决MySQL 8的时区校验报错;characterEncoding=utf8让中文不乱码;useSSL=false跳过本地开发时的证书握手,少一层麻烦。如果连的是MySQL 5.7,驱动类名可以继续用老写法com.mysql.jdbc.Driver,但我现在统一用com.mysql.cj.jdbc.Driver,两个版本都认。密码按自己库的实际值改,username默认root。
如果源码是普通Web工程,导入方式略有差别:IDEA里先Open解压目录,然后在Project Structure的Modules里把lib目录添加为依赖,再到Artifacts里选择Web Application: Exploded,确认lib被打进artifact,最后在Run Configuration里配置Tomcat Server Local,Deployment里添加这个artifact。Application context建议统一设为/bookstore,后面访问路径和静态资源路径都依赖它。这一步就是网上常说的“IDEA运行JavaWeb项目配置”,80%的启动失败发生在这里,因为很多人直接Run了一个普通Java类,根本没启动Tomcat容器。
2.4 首次启动后的自检顺序:日志、页面、数据库三层判断
第一次点运行,不要急着开浏览器。先看IDEA下方的Tomcat日志,有红色异常就按异常信息查;没异常再看控制台有没有Server startup in xxx ms,看到这一行说明容器已经起来了。接着在浏览器访问部署路径,这里有个很实用的判断逻辑:如果首页能打开,但点图书列表报错,问题大概率在数据库;如果连Tomcat默认页都出不来,回到Artifact配置里查war有没有挂上。
数据库这层有个快速验证方法,直接在IDEA右侧的Database面板试连一次,账号密码和URL能通过,说明连接配置没问题;连不上就拿报错去搜,九成是时区或驱动类名不对。整个自检顺序我固定为Tomcat日志、首页转发、数据库查询三层,能少走很多弯路,不用在404和数据库报错之间来回猜。
3. 一个JavaWeb项目完整案例(MySQL版)的核心链路:从查询图书到提交订单
3.1 一条请求是怎么从JSP走到MySQL的
JSP页面填完表单提交给Servlet,Servlet里取参数、调DAO,DAO用JDBC拼SQL查MySQL,结果再一层层传回JSP渲染。网上书店的业务看起来多,拆开就是三块:用户模块的注册登录、图书模块的分类与搜索、订单模块的购物车和结算。理解这条链路对改代码很重要——后面加功能,都要在这三层里各动一处。
老项目里偶尔有人把JDBC代码直接写进JSP,这种写法能跑,但答辩时被问“为什么不用三层结构”会很难接。常见做法是准备一个DBUtil负责拿Connection,一个DAO对应每张表的查询。下面这3.2到3.4给的是这套源码里最值得改写的三个点,也是判断一个JavaWeb项目完整案例(MySQL版)是否合格的三个标尺。
3.2 登录注册里的Session处理:一个能直接替换的LoginServlet
登录是整个系统的入口,也是答辩必问的“你怎么保持登录状态”的落点。常见实现是登录成功后把User对象塞进Session,后续页面通过session.getAttribute("loginUser")判断是否登录。我一般会顺手在注册逻辑里加一层密码哈希再入库,虽然网上书店这种课设对安全要求不高,但能多一个地方回答提问。
@WebServlet("/login") public class LoginServlet extends HttpServlet { private UserDao userDao = new UserDao(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); User user = userDao.findByUsernameAndPassword(username, password); if (user != null) { req.getSession().setAttribute("loginUser", user); resp.sendRedirect(req.getContextPath() + "/index.jsp"); } else { req.setAttribute("error", "用户名或密码错误"); req.getRequestDispatcher("/login.jsp").forward(req, resp); } } }逻辑说明:doPost里先固定UTF-8编码,再从请求参数里拿username和password,调DAO查库。查到用户就把对象放进Session然后重定向到首页;查不到就在request里放一个error消息,转发回login.jsp展示。关键参数是req.getSession(),它创建的Session对象在同一个浏览器的多次请求里共享,退出登录时要记得调用invalidate。redirect和forward的区别要能讲清楚:redirect是浏览器发起第二次请求,地址栏会变;forward是服务端内部转发,地址栏不变,适合携带错误信息。
3.3 图书分类查询:一个DAO方法串起三个页面
图书列表是主页、分类页、搜索页共用的逻辑。用PreparedStatement拼条件,既防SQL注入,也方便传分类ID和关键字。这个方法是源码里改动频率最高的位置,也是图书模块的核心。
public List<Book> findByCategory(int categoryId) { String sql = "SELECT id, name, author, price FROM book WHERE category_id = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, categoryId); try (ResultSet rs = ps.executeQuery()) { List<Book> list = new ArrayList<>(); while (rs.next()) { Book book = new Book(); book.setId(rs.getInt("id")); book.setName(rs.getString("name")); book.setPrice(rs.getBigDecimal("price")); list.add(book); } return list; } } catch (SQLException e) { throw new RuntimeException("查询分类图书失败", e); } }逻辑说明:先写SQL模板,占位符?对应categoryId,PreparedStatement在setInt时做类型处理,避免字符串拼接SQL的注入风险。try-with-resources写法保证连接、语句、结果集在方法退出时自动关闭,老代码里手动close经常漏掉。参数说明:categoryId按页面约定传,有的项目里0表示“全部图书”,那就需要在入参时判断一下,为0时换成不带WHERE的SQL。想给图书模块加分页时,也是在这个方法上加LIMIT ? OFFSET ?两个占位符,改动范围很小。
3.4 购物车:Session里放一个Map就是最简单实现
购物车是课设里最能体现理解程度的模块。常见做法是Session里存一个Map<图书ID, 数量>,每次点“加入购物车”就往里放,同一本书数量加一。这个方案不用额外建表,刷新页面不丢,缺点是换浏览器就清空,但对课程设计来说完全够用,也好在答辩时讲清楚会话机制。下面这段是能直接替换的CartServlet:
@WebServlet("/cart") public class CartServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action = req.getParameter("action"); if ("add".equals(action)) { int bookId = Integer.parseInt(req.getParameter("bookId")); HttpSession session = req.getSession(); Map<Integer, Integer> cart = (Map<Integer, Integer>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); } cart.put(bookId, cart.getOrDefault(bookId, 0) + 1); session.setAttribute("cart", cart); resp.sendRedirect(req.getContextPath() + "/cart?action=list"); } else { req.getRequestDispatcher("/cart.jsp").forward(req, resp); } } }逻辑说明:用action参数区分加购和展示两个分支。第一次访问时Session里没有cart,new一个HashMap再存回去;getOrDefault的作用是同一本书加第二次时不覆盖原值,而是取旧值加一。参数说明:后面想支持“删除购物车项”,只要在action里加一个remove分支,调用cart.remove(bookId)再存回Session即可。展示分支转发给cart.jsp,页面上遍历Map,key是图书ID,value是数量,再根据ID去查书名和单价。
3.5 订单生成:补上事务代码是答辩加分点
订单模块如果只有插入订单记录、没有扣库存,会被问“两个操作之间断电了怎么办”。一个能讲清楚的方案是把下单放进事务:先查库存,够就UPDATE book SET stock = stock - ?,再INSERT订单和明细,最后统一commit,任何一步失败就rollback。源码里如果没写,用DBUtil拿Connection后手动setAutoCommit(false)补上,工作量不大,但这个点经常能在答辩里救场。事务涉及的多条SQL要使用同一个Connection连接,不能每次操作都从连接池拿新连接,否则事务就断开了。
4. MySQL数据库设计与初始化脚本:五张表把书店业务交代清楚
4.1 从购物流程反推表结构:用户、分类、图书、订单、订单项
网上书店的业务数据落在五张表上。用户表存登录信息;分类表管图书分组;图书表是核心商品;订单表记录一次购买行为;订单明细表记录这次购买里的每一本书。为什么订单和订单明细要拆开:一张订单可以包含多本书,只有两张表配合,才能既记“谁在什么时候买了多少钱”,又能列出“每本书买了几本、当时什么单价”。这里有个细节是订单明细表里要冗余存book_name和price,因为图书表里的价格以后可能会改,订单自己留一份快照,对账才准确。
老课设源码里常见两个建表问题:表名用了user、order这类MySQL保留字导致建表失败;金额用float导致计算出现小数点误差。下面的脚本把这两个问题一起避开,直接执行就能建库。源码自带的SQL脚本如果和项目里的实体对不上,以SQL为准,改实体字段比改表结构容易得多。
4.2 建库建表SQL:UTF-8、金额精度和外键一次对齐
课程设计里的建表脚本,最好满足三个条件:字符集用utf8mb4、金额用DECIMAL、关键字段带外键。一张初始化脚本直接把数据库底子打对,后面跑源码会省很多事。
CREATE DATABASE IF NOT EXISTS bookstore DEFAULT CHARACTER SET utf8mb4; USE bookstore; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, name VARCHAR(100) NOT NULL, author VARCHAR(50), price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, publish_date DATE, CONSTRAINT fk_book_category FOREIGN KEY (category_id) REFERENCES category(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `user` ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT '0待支付 1已支付 2已取消', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES `user`(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, book_id INT NOT NULL, book_name VARCHAR(100), price DECIMAL(10,2), quantity INT NOT NULL, CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES orders(id), CONSTRAINT fk_item_book FOREIGN KEY (book_id) REFERENCES book(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;逻辑说明:user和orders加反引号,是因为它们是MySQL保留字,不加反引号建表直接报语法错误。价格和金额都用了DECIMAL(10,2)而不是float,避免浮点误差。stock库存字段是后面做扣减事务的前提,用INT就够了。参数说明:外键除了保证数据一致性,答辩画E-R图时也直接照抄。如果现有源码里表名不一样,比如把orders叫t_order,只要把SQL里的表名和DAO里的SQL同步改掉就行。
建完表再补几条演示数据,否则图书列表页面打开是空的,容易让人怀疑是查询写错了:
INSERT INTO category (name) VALUES ('计算机'), ('文学'), ('历史'); INSERT INTO book (category_id, name, author, price, stock) VALUES (1, 'Java核心技术', 'Cay S. Horstmann', 99.00, 20), (1, '深入理解Java虚拟机', '周志明', 89.50, 15), (2, '活着', '余华', 35.00, 30), (3, '明朝那些事儿', '当年明月', 58.00, 12);两组INSERT分别在分类表和图书表里插入基础数据,图书表里的category_id要和分类表的id对应上。这样登录进系统后,不用自己拿管理后台录入,首页直接就能看到图书列表。
4.3 统计查询模板:课程设计里“数据展示页”现成能抄
加了统计页会让项目显得完整很多。两个常用SQL模板先收着:分类图书数和月度订单金额。配合ECharts或者Chart.js画柱状图,就是很能撑场面的大作业展示页。
-- 每个分类下的图书数量 SELECT c.name AS category_name, COUNT(b.id) AS book_count FROM category c LEFT JOIN book b ON b.category_id = c.id GROUP BY c.id, c.name; -- 月度已支付订单金额统计 SELECT DATE_FORMAT(create_time, '%Y-%m') AS month, SUM(total_amount) AS total_amount FROM orders WHERE status = 1 GROUP BY DATE_FORMAT(create_time, '%Y-%m') ORDER BY month DESC;逻辑说明:第一句用LEFT JOIN,能让没有图书的分类也显示数量0,而不是从结果里消失;第二句用DATE_FORMAT把时间格式化成年月再GROUP BY,正好对应统计图表的X轴。参数说明:status = 1表示只看已支付订单,如果这套源码的订单状态字段含义不同,先回orders表看数据再改。统计结果的字段名month和total_amount,就是给前端JSON或者JSP选项锅要用的名字。
5. 避坑:把网上书店源码跑起来最容易翻车的五个点
5.1 服务能启动但页面500:javax.servlet包名在Tomcat 10下直接缺失
现象:IDEA能启动Tomcat,但访问首页或login.jsp时报java.lang.ClassNotFoundException: javax.servlet.http.HttpServlet。Tomcat默认页能打开,只有访问具体Servlet才报错,特别容易让人怀疑是自己代码写错了。
原因:Tomcat 10之后Servlet API从javax包整体迁移到jakarta包。老课设代码import的都是javax.servlet,容器里没有这个类,自然加载失败。
解决:换Tomcat 9.x最省事。如果必须用Tomcat 10,就要把代码里所有import javax.servlet改为jakarta.servlet,pom依赖也同步换成jakarta.servlet-api。我的建议是给这个项目单独配Tomcat 9,不要动代码,避免改import改出一堆连带问题。
5.2 数据库连接报Loading class com.mysql.jdbc.Driver
现象:启动后点图书列表,后台打印Loading class 'com.mysql.jdbc.Driver'. This is deprecated...,或者直接报ClassNotFoundException: com.mysql.jdbc.Driver。
原因:源码lib目录里的驱动是5.x版本,类名是com.mysql.jdbc.Driver;MySQL 8的驱动类改成了com.mysql.cj.jdbc.Driver,老名字只存在于旧驱动里。
解决:把JDBC驱动替换成mysql-connector-java 8.x版本,同时把jdbc.properties里的driver改成com.mysql.cj.jdbc.Driver。注意lib目录下如果同时存在新旧两个驱动jar,一定要删掉旧的,否则类加载顺序会带来各种诡异行为,这是最容易忽略的一点。
5.3 时区报错:The server time zone value 乱码
现象:数据库查询抛SQLException,提示The server time zone value '...' is unrecognized,中间时区值经常是一串乱码。
原因:MySQL 8与驱动之间的时区校验算法发生了变化,本地MySQL沿用了系统时区设置,驱动不认识。
解决:连接URL上追加serverTimezone=Asia/Shanghai和useSSL=false,重启Tomcat即可。这个报错里的乱码很容易让人误以为是字符集问题,实际上就是时区。最稳妥的做法是把参数固化在jdbc.properties里,不要只写在IDEA的临时运行配置里,换台机器重新导入源码时又丢一次。
5.4 页面打开展示正常但CSS、图片全碎:静态资源404
现象:首页能打开,但样式全丢、图片裂开,F12里看到css/style.css返回404。
原因:部署路径带了context path,比如http://localhost:8080/bookstore/,但页面里写的是href="/css/style.css"。这个以/开头的绝对路径指向了Tomcat根目录,自然找不到。
解决:把所有静态资源引用改成基于项目上下文的写法,JSP里用${pageContext.request.contextPath}拼前缀。
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css"> <script src="${pageContext.request.contextPath}/js/jquery.min.js"></script>这个表达式输出的是http://localhost:8080/bookstore,后面的/css/style.css正好落在正确位置。老项目里手写的相对路径在首页能用,但换个目录级别的页面就错位,统一换成contextPath最稳。换机器、换IDEA工程名后这个问题才暴露,属于典型的“环境变了才翻车”。
5.5 中文变问号:JSP、MySQL、连接串三处任一处不一致都乱
现象:页面显示书名全是??,或者注册的用户名存进数据库变成乱码。
原因:JSP的pageEncoding、MySQL的表字符集、JDBC连接URL的characterEncoding参数,三处没有统一为UTF-8。
解决:JSP头写成pageEncoding="UTF-8",建库语句用utf8mb4,连接URL带characterEncoding=utf8。改完重启Tomcat,之前已经写入的乱码数据要DELETE掉重新插入。排查时用SHOW CREATE TABLE book;确认表字符集,少了哪一处改哪一处。这是中文业务系统的经典乱码三件套,缝隙往往出在“JSP改了但MySQL没改”这种不一致上。
6. 从“能跑”到“能答辩”:给网上书店加点分的小改造
市面上的Java课程设计案例源码大多停在“能跑”这一步,真正拉开差距的是几个小改造。我每次拿到这类源码,会先挑三件事做:给图书列表加分页和搜索、给下单加事务、给订单加一个简单的状态流转页面。
分页是最容易出效果的。上面3.3的findByCategory方法加两个参数就行:
public List<Book> findPage(int categoryId, int page, int pageSize) { String sql = "SELECT id, name, author, price FROM book " + "WHERE category_id = ? LIMIT ?, ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, categoryId); ps.setInt(2, (page - 1) * pageSize); ps.setInt(3, pageSize); // 后续和 findByCategory 一样遍历 ResultSet } catch (SQLException e) { throw new RuntimeException("分页查询失败", e); } }逻辑说明:LIMIT后面的两个参数,第一个是偏移量,计算公式是(page - 1) * pageSize,第二个是本页大小。页码从1传进来时偏移量是0,正好从第一条开始。页面底部再放“上一页下一页”两个链接,把当前页码和分类ID拼在URL参数里,一个像样的分页模块就完成了。
第二个改造是下单事务。把扣库存和生成订单放在同一个Connection里,setAutoCommit(false)后执行多条SQL,结束时commit,捕获异常就rollback。这个改动需要动DAO层,但逻辑不复杂,答辩时讲“我用事务保证了数据一致性”,比讲“我用了什么框架”更能让人信服。
第三个改造是管理后台。把第4章的统计SQL接一个Servlet,返回JSON给前端,用ECharts画柱状图;再给orders表加一个待支付、已支付、已取消的状态切换按钮,整个项目的完整度立刻就不一样了。这三个改造做完,这套网上书店就不再是“网上下载的源码”,而是“基于源码改造的完整系统”。
我做这类课设源码的习惯是:先把JSP里散落的JDBC代码全部清掉换DBUtil,再按“登录→逛书→加购→下单→统计”的顺序跑通一遍主流程,确保每个环节在自己机器上能复现,然后才开始加功能。顺着这个顺序走,既不会被环境问题卡死,也不会在答辩时被问到“这行代码什么意思”而答不上来。希望帮到你。
本文还有配套的精品资源,点击获取