简介:面向JavaWeb课程设计与毕业设计同学,这份网上书店/书城系统项目包可直接下载运行,代码完整,曾获导师指导并通过97分高分,兼具课程作业与实战参考价值。压缩包共645个文件,约12.47MB,内部以JSP视图页面、Java后台类、数据库SQL脚本、CSS/JS前端资源以及项目配置文件为主,目录结构清晰,便于按模块阅读、调试与二次开发。目前已有1183人学习/下载,热度较高。项目覆盖用户登录、图书展示、购物车、订单管理等典型电商业务逻辑,并附带书城相关图片素材与资源文件,可帮助读者快速搭建一套功能完整的网上售书环境,是JavaWeb课程设计与项目实战的高质量参考资料。
1. “javaweb网上书店系统源码+数据库”这类zip里装的到底是什么
javaweb网上书店系统源码+数据库网上书城系统,这类以“源码+数据库”捆绑打包的课程设计项目,在每年的Java课程设计和毕业设计里都算点名率最高的那批。zip解压后通常两部分:一是JavaWeb工程源码,包含jsp页面、Servlet、DAO和工具类;二是一个SQL脚本,负责把整个书店的用户、分类、图书、订单数据表一次性建好。跑起来以后,外网用户能注册、登录、检索图书、加购物车、下单,管理员进后台维护图书上下架和处理订单。
它解决的实际问题很直接:给刚学完JSP、Servlet、JDBC的开发者一套能看懂全链路的完整案例,也给课设/毕设没头绪的人一套可以改、可以讲、能演示的骨架。适合三类人:正在做JavaWeb课设的学生、想补完整项目经验的应届生、以及刚接触javaweb项目完整案例mysql想找参考的老手。但这里有个反直觉的结论:这类项目的业务代码大多不难,真正让新手卡住的,是把别人电脑上的项目搬到自己电脑上、把SQL脚本接进本机MySQL的这半小时环境配置。下面按我自己的落地顺序,从导入源码到跑通功能,一步步讲。
2. 把zip里的项目导入IDEA:idea运行javaweb项目配置的三处重点
拿到源码后不建议直接解压双击index.jsp,JavaWeb项目的运行链路必须有Servlet容器、编译后的class、以及能连通的数据库。老课程设计项目的技术栈一般固定在JSP+Servlet+JDBC上,容器用Tomcat,编译产物是整个工程的Artifact,数据库用MySQL。准备工作先列一张环境清单,版本不对后面处处翻车。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | 老源码在JDK 8上最稳,JDK 17编译jsp可能会有隐式依赖问题 |
| Tomcat | 8.5 或 9.0 | 老项目用javax.servlet包,Tomcat 10改用jakarta,老源码跑不了 |
| MySQL | 5.7 或 8.0 | 5.7踩坑少,8.0要注意驱动和认证改法 |
| IDEA | 2020+ 即可 | 需要支持Tomcat插件,社区版装Smart Tomcat插件也能跑 |
2.1 第一步先看项目形态:Maven工程还是普通Web目录
在终端里解压并查看目录结构,注意Windows用户直接用解压软件即可,命令是给macOS/Linux用户做参考的。
unzip 网上书店系统.zip -d bookstore cd bookstore ls -la find . -maxdepth 2 -name "pom.xml" -print第一条命令解压到当前目录下的bookstore文件夹,第二条进入工程,第三条看顶层结构,第四条专门找pom.xml。判断项目形态主要靠第四条:输出了pom.xml路径,就是Maven工程,IDEA里直接File → Open,选择pom.xml导入;没有pom.xml,就是普通的JavaWeb目录工程,用New → Module from Existing Sources,把bookstore目录指给IDEA,再由IDEA识别成Web项目。
普通Web工程最需要确认的是WEB-INF/lib目录下有哪些jar。老课设项目常见的依赖有servlet-api.jar、jsp-api.jar、mysql-connector-java.jar、jstl.jar。其中servlet-api和jsp-api必须和Tomcat版本一致,所以IDEA创建项目时有一个容易漏的操作:Project Structure → Modules → Dependencies,不能直接把servlet-api.jar加进来,而要把Tomcat作为Provided依赖提供给项目,否则运行时会报java.lang.LinkageError,这是Servlet容器和webapp自带类冲突造成的。
2.2 配置Artifact和Tomcat:必须手动确认的三个参数
普通Web项目导入后,IDEA默认不会生成可部署的Artifact,启动Tomcat时经常看到“No artifacts marked for deployment”的报错。打开Project Structure → Artifacts,点加号,选Web Application: Exploded,把项目里的web目录指成Web Resource Directory。这一步的意义是让IDEA知道哪些文件属于Web应用根目录,编译后的classes会自动放进WEB-INF/classes,lib目录里的jar放进WEB-INF/lib。
然后配置Tomcat。Run/Debug Configurations里新建一个Tomcat Server → Local,有三处要手动确认。
| 配置项 | 建议值 | 原因 |
|---|---|---|
| Application context | /bookstore | 访问路径前缀,和项目名一致即可,不要留空。留空会默认访问根路径,和Tomcat自带ROOT冲突 |
| On Update Action | Redeploy | 改了Java代码点更新图标时,自动重新部署。不想重启就选Hot Swap |
| HTTP port | 8080 | 如果本机8080被其他进程占用,日志里会提示Address already in use |
第一次运行这类老项目时,大概率遇到HTTP Status 404,页面能启动但访问不到内容。这时先在浏览器地址栏手动输入http://localhost:8080/bookstore/login.jsp而不是/bookstore/,因为很多网上书店的中心首页不叫index.jsp,而是login.jsp或main.jsp,web.xml的welcome-file-list里可能也没写对。如果直接访问带具体jsp路径能打开,说明部署成功,问题只在欢迎页配置上。
2.3 数据库连接配置:jdbc.properties与MySQL连接池的四个关键参数
解开源码先找资源目录下的jdbc.properties、db.properties或c3p0-config.xml,网上书店系统这类项目几乎都把数据库连接做成独立配置。典型的jdbc.properties长这样:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/bookstore?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456四个参数的含义很直接:driver是JDBC驱动类名,MySQL 5.x对应com.mysql.jdbc.Driver,MySQL 8.x要改成com.mysql.cj.jdbc.Driver,改错会报ClassNotFoundException。url里localhost是主机名,3306是MySQL默认端口,bookstore是数据库名,这个库名必须和SQL脚本里的CREATE DATABASE一致。useUnicode和characterEncoding控制中文不乱码,useSSL=false是为了避开MySQL 8的SSL握手告警,serverTimezone=Asia/Shanghai是为了避免时区报错。username和password填本机MySQL的账号。
如果项目用的是数据库连接池,常见做法是配c3p0或Druid。c3p0的配置文件叫c3p0-config.xml,里面有一段类似这样的内容:
<c3p0-config> <default-config> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/bookstore?useSSL=false&serverTimezone=Asia/Shanghai</property> <property name="user">root</property> <property name="password">123456</property> <property name="initialPoolSize">5</property> <property name="maxPoolSize">20</property> <property name="acquireIncrement">2</property> </default-config> </c3p0-config>这里注意两个细节。第一,xml文件里的&符号必须写成&,直接写useSSL=false&serverTimezone=Asia/Shanghai,解析器会报错,这是老项目的经典坑。第二,initialPoolSize是启动时创建的初始连接数,maxPoolSize是连接池最大连接数,acquireIncrement表示连接不够时一次补充几个。很多网上书店项目把maxPoolSize写成100,本机MySQL默认max_connections是151,如果同时跑多个连接池实例,很快就会出现Too many connections。单机课设项目maxPoolSize设为20以内就够了,连接池不是越大越好,连接拿不到再创建、空闲再回收,数据库资源都在本机,没必要开大军团。配置完数据库连接池,下一步就是导数据库脚本。
3. 数据库落地:网上书店建表、导入脚本与增删改查
网上书店系统的数据层是整个项目能跑起来的地基。这类zip里的SQL脚本一般分成两类:带CREATE DATABASE的完整脚本和不带建库语句只建表的脚本。先确认脚本里有没有CREATE DATABASE这一句,有就直接用source导入,没有就先手动建库再导入。
3.1 用命令行导入SQL:source命令与字符集设置
命令行导入是最可靠的方式,打开MySQL客户端执行以下操作:
mysql -u root -p -- 输入密码后进入mysql命令行 mysql> create database if not exists bookstore default character set utf8mb4; mysql> set names utf8mb4; mysql> source D:/bookstore/sql/bookstore.sql;第一条create database用来兜底,防止脚本里不包含建库语句。如果脚本自带CREATE DATABASE bookstore,这一条可以跳过,但字符集设置不能省。set names utf8mb4这一句的作用是告诉MySQL客户端,当前会话传输的字符集是utf8mb4,不执行这条,脚本里的中文注释和INSERT语句里的书名、作者很可能变成乱码。source后面必须跟绝对路径,Windows路径里的反斜杠在MySQL命令行里偶尔会被转义,稳妥做法是把路径里的反斜杠改成斜杠,也就是像我上面这样写D:/bookstore/sql/bookstore.sql。
如果项目里的SQL脚本本身是UTF-8编码,但本机MySQL的character_set_server是latin1,建出来的表默认字符集也是latin1,导入后中文照样乱。建议在导入后执行这条SQL检查:
mysql> show create table books\G看到DEFAULT CHARSET=utf8mb4就正常了。如果SHOW出来是latin1,说明建表语句里没有指定字符集,可以删掉表重新导入,或者在脚本里手动给CREATE TABLE加上DEFAULT CHARSET=utf8mb4。
3.2 核心数据表拆解:图书表、订单表、订单明细表的关系
网上书店系统的表结构大同小异,核心表一般不超过六张,理解这几张表的关系,整个系统的数据流就通了。
| 表名 | 作用 | 关键字段 |
|---|---|---|
| users | 用户表 | id, username, password, nickname, phone |
| category | 图书分类表 | id, name |
| books | 图书表 | id, name, author, price, stock, category_id, cover, description |
| orders | 订单表 | id, user_id, total_price, status, create_time |
| order_item | 订单明细表 | id, order_id, book_id, quantity, price |
orders和order_item是最值得看的两张表,老项目里它们可能长这样:
CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 1 COMMENT '1待付款 2已付款 3已发货 4已完成', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES users(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, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL, CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES orders(id), CONSTRAINT fk_item_book FOREIGN KEY (book_id) REFERENCES books(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;两张表用order_id关联,orders只存订单汇总信息,order_item存每个商品买了几个、当时的价格是多少。这里有一个值得注意的设计:order_item的price字段应该存下单那一刻的价格,而不是去关联books表实时查价格。否则图书改价以后,历史订单的成交价格就失真了。DECIMAL(10,2)用来存钱,10表示总位数,2表示小数位数,网上书店项目用float存价格是常见错误,金额计算会出现0.1+0.2不等于0.3的浮点误差,这是数据库设计层面最容易被忽略的细节。
3.3 从JDBC到DAO:数据库增删改查的标准写法
网上书店系统的后端代码通常按“Servlet → Service → DAO → MySQL”分层,DAO层是最底层的数据库操作。项目里一般会有一个JDBCUtils类,从连接池里取连接,然后再有一个BaseDao或DBUtils封装通用的增删改查方法。以登录为例,视图层传入用户名和密码到Servlet,Servlet调UserDao,UserDao里有一段类似这样的代码:
public User findUserByUsernameAndPassword(String username, String password) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JDBCUtils.getConnection(); String sql = "select id, username, nickname from users where username=? and password=?"; ps = conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); rs = ps.executeQuery(); if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setNickname(rs.getString("nickname")); return user; } return null; } catch (Exception e) { e.printStackTrace(); return null; } finally { JDBCUtils.close(conn, ps, rs); } }这段代码看起来平凡,但包含了三个必须养成的习惯。第一,SQL语句用PreparedStatement而不是Statement拼接,where username=? and password=?这种参数占位写法能避免SQL注入,网上书店这种面向全网用户的系统,登录接口被注入一票通直接爆破账号是老生常谈的风险。第二,密码在多数老源码里是明文存储的,直接拿用户输入的字符串去比对。这在课设里勉强能用,真实项目里一定要先MD5或加盐哈希再入库。第三,finally块里的JDBCUtils.close才是连接池的关键,这里关闭连接是“归还”给连接池,不是物理断开,如果省略这一步,连接池很快被耗尽,页面表现就是“登录一次之后,第二次操作就报连接超时”。
拿到源码以后,想快速定位每个表对应的DAO,就用项目里的表名去搜,比如搜索class UserDao、class BookDao、class OrderDao,这些类的名字和表名几乎一一对应。搜到之后重点看每个方法里PreparedStatement的SQL,就能把整个网上书店系统的数据库增删改查全貌摸清楚,比从头读几十个jsp页面快得多。
4. 核心功能代码走读:登录会话、购物车与下单事务
网上书店系统的功能模块,前台绕不开三个:登录注册、购物车、订单结算。这些代码在网上书店源码里各有各的实现套路,但核心机制是相通的——登录靠Session,购物车靠Session里的集合,订单靠数据库事务。下面按代码顺序拆开讲。
4.1 登录会话与拦截器:session会话和Filter过滤器的搭配
登录成功后,项目会把用户对象存进Session:
HttpSession session = request.getSession(); session.setAttribute("user", user); session.setMaxInactiveInterval(30 * 60);第一行是取Session,第二行把查询到的User对象放进Session,这个用户信息在后面的所有页面里都能取到,用来标识“当前登录的是谁”。第三行设置Session的有效时间为30分钟,单位是秒,30分钟没操作就自动失效,这是网上书店系统里控制登录态的常见做法。如果项目里没有这一行,用的是Tomcat默认的30分钟,效果一样。
光有Session还不够,用户没登录直接访问/cart.jsp或/order/create,系统必须拦住。老项目里一般有一个LoginFilter做这件事:
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; Object user = request.getSession().getAttribute("user"); if (user == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } chain.doFilter(req, resp); }这段过滤器逻辑是:先从Session里取用户对象,取不到说明没登录,直接重定向到login.jsp;取到就放行,让请求继续走到后面的Servlet。Filter本身要在web.xml里注册,网上书店类的老项目通常这么配:
<filter> <filter-name>LoginFilter</filter-name> <filter-class>com.bookstore.filter.LoginFilter</filter-class> </filter> <filter-mapping> <filter-name>LoginFilter</filter-name> <url-pattern>/cart/*</url-pattern> <url-pattern>/order/*</url-pattern> </filter-mapping>url-pattern决定哪些路径受保护。注意这里只拦/cart/和/order/,登录页、注册页、图书列表页是放开的,否则用户没登录连主页都看不了。有些新手拿到源码会把filter-mapping写成/*,导致首页都跳登录页,误以为项目坏了。源码里的路径设计,先看一遍filter-mapping再动手改。
4.2 购物车实现:藏在Session里的Map能省一张表
网上书店系统的购物车实现有两种路线:一种是建数据库购物车表,存userId、bookId、quantity;另一种更常见、也更适合课设的是直接塞Session。Session方案不用建表,代码量小,缺点是用户把浏览器关了购物车就丢。课设项目多选后者,毕竟演示时没人会把浏览器关了再打开。
Session购物车的添加逻辑通常是这样的:
List<CartItem> cart = (List<CartItem>) session.getAttribute("cart"); if (cart == null) { cart = new ArrayList<>(); } int bookId = Integer.parseInt(request.getParameter("bookId")); boolean found = false; for (CartItem item : cart) { if (item.getBookId() == bookId) { item.setCount(item.getCount() + 1); found = true; break; } } if (!found) { CartItem newItem = new CartItem(bookId, 1); cart.add(newItem); } session.setAttribute("cart", cart);这段代码的逻辑是:先从Session取购物车列表,第一次访问时取到null就新建一个空列表;然后读请求参数bookId,在现有列表里找有没有同ID的图书,有就数量加一,没有就把新书以数量1加入;最后把列表放回Session。CartItem这个类通常只有两个字段,bookId和count,再去关联查books表拿书名、价格、封面。
这个方案有一个必须注意的坑:如果项目里购物车用嵌套循环做“删除某项”操作,不能在for循环里直接调list.remove(item),会抛ConcurrentModificationException。正确写法是收集要删除的条目,循环结束后统一删除,或者用Iterator遍历。源码里如果已经有这个bug,页面表现是“删除购物车第二件商品后,后面几件全部消失”,这是集合遍历的经典翻车点。
4.3 下单事务:库存扣减为什么不能只写一条UPDATE
下单是网上书店系统里数据一致性要求最高的操作,流程一般是:校验购物车不为空 → 创建订单记录 → 把购物车里的每件商品插入order_item → 扣减books表库存 → 清空购物车 → 跳转订单或支付页。这五个动作必须在一个数据库事务里完成,否则“订单创建了、库存却扣了两次”或者“库存扣了、订单没生成”都会出现。
老项目里如果没接Spring事务,就用JDBC原生事务:
Connection conn = JDBCUtils.getConnection(); try { conn.setAutoCommit(false); String insertOrder = "insert into orders(user_id, total_price, status) values(?,?,1)"; PreparedStatement ps = conn.prepareStatement(insertOrder, Statement.RETURN_GENERATED_KEYS); ps.setInt(1, userId); ps.setBigDecimal(2, totalPrice); ps.executeUpdate(); ResultSet keys = ps.getGeneratedKeys(); int orderId = 0; if (keys.next()) { orderId = keys.getInt(1); } String updateStock = "update books set stock=stock-? where id=? and stock>=?"; for (CartItem item : cart) { ps = conn.prepareStatement(updateStock); ps.setInt(1, item.getCount()); ps.setInt(2, item.getBookId()); ps.setInt(3, item.getCount()); int rows = ps.executeUpdate(); if (rows == 0) { throw new RuntimeException("库存不足,取消下单"); } } conn.commit(); } catch (Exception e) { conn.rollback(); throw new RuntimeException("下单失败"); } finally { conn.setAutoCommit(true); JDBCUtils.close(conn, null, null); }这段代码最容易忽略的是setAutoCommit(false)和commit()之间的辛苦工作。setAutoCommit(false)是告诉MySQL“先别自动保存,等我全部做完再统一写盘”,commit()是“确认无误,正式保存”,rollback()是“中间有一步失败,全部撤销”。没有事务时,订单插进去了、库存还没扣,一旦程序中途报错,数据就缺了一条腿。
注意那条UPDATE语句里加了and stock>=?,这是网上书店并发环境下不超卖的关键。它的意思是:update影响行数只有在库存大于等于购买数量时才是1,如果库存不够,执行结果rows等于0,代码主动抛出异常回滚整个事务。不加这个条件的话,两个用户同时买同一本只剩1本的书,两个请求都能把库存从1改成0,超卖就发生了。课设项目可能并发量不大,带着这个条件写进去,答辩时能讲出“乐观库存校验”这个设计点,是加分项。
4.4 打开源码前先记住这张路由表
网上书店系统的页面和Servlet映射关系,在web.xml里能找到,或者在一个统一处理类里看到。为了打开源码时不迷路,先看这张对应关系表:
| 前端页面 | 请求路径 | 后端Servlet | 处理动作 |
|---|---|---|---|
| register.jsp | /register | RegisterServlet | 新增用户 |
| login.jsp | /login | LoginServlet | 登录校验 |
| index.jsp | /book/list | BookListServlet | 分页展示图书 |
| cart.jsp | /cart/add | CartServlet | 添加购物车 |
| order_confirm.jsp | /order/create | OrderServlet | 创建订单 |
| admin/order_list.jsp | /admin/order/list | OrderAdminServlet | 管理后台订单列表 |
老项目喜欢在web.xml里写<servlet-mapping>,新一点的项目直接用@WebServlet("/cart/add")注解。不管哪种写法,请求路径和Servlet的对应关系都可以通过Ctrl+Shift+R全局搜索路径字符串定位到。有些Servlet使用一种“一个入口多个方法”的套路,路径是/servlet/UserServlet?action=login&...,action参数从login切到register切到logout,这种写法集中在一个类的doGet/doPost里,先用request.getParameter("action")把动作取出来,再用switch或if分发,看代码时先找action参数的取值清单,就能掌握这个Servlet支持的所有功能。
5. 避坑指南:网上书店项目跑不起来的5个经典翻车点
就算按上面的流程把环境配好了,也会有几道坎稳定地挡在新手面前。以下五个问题,是我接触这类课程设计源码时反复遇到的,按“现象→原因→解决”写,每条都有对应的排查路径。
5.1 404白屏:Tomcat启动日志全绿,访问首页却找不到页面
现象:IDEA里Tomcat正常启动,控制台没有报错,浏览器访问http://localhost:8080/bookstore/显示404或白屏。
原因:最常见的情况有三个——Application context配置的不是/bookstore,导致URL里路径对不上;Artifact根本没有部署到Tomcat,IDEA的Deployment列表为空;项目欢迎页不叫index.jsp,web.xml里也没有welcome-file-list。
解决:打开Run/Debug Configurations → Tomcat Server → Deployment,确认列表里有这个项目的Artifact,Application context设为/bookstore。然后直接在浏览器里访问http://localhost:8080/bookstore/login.jsp,如果能打开,就去web.xml里看看welcome-file-list有没有配index.jsp或login.jsp,没配就补一个:
<welcome-file-list> <welcome-file>login.jsp</welcome-file> </welcome-file-list>这类源码的默认首页经常是main.jsp或login.jsp,不要死等根路径。
5.2 中文全是问号:数据库里看到的是正常的,页面上全是乱码
现象:页面正常显示中文,但MySQL里books表的书名全是问号;或者反过来,数据库里中文正常,页面上显示“??????”。
原因:数据库表字符集不是utf8mb4,或者jdbc.url里的characterEncoding=utf8参数缺失,再或者是jsp页面本身的pageEncoding不对。
解决:先确认表字符集,执行show create table books;,如果不是utf8mb4,重新导入SQL脚本。命令行导入前三步走:set names utf8mb4;、建库语句里显式带default character set utf8mb4、建表语句末尾带DEFAULT CHARSET=utf8mb4。然后检查jdbc.properties里的url是不是带上了useUnicode=true&characterEncoding=utf8。最后看jsp第一行有没有<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>,很多老项目的jsp默认用ISO-8859-1,改成UTF-8再重启就好了。
5.3 Access denied:数据库连不上,报using password: YES
现象:Tomcat启动时控制台报错Access denied for user 'root'@'localhost' (using password: YES),或者系统运行一会儿后所有需要连数据库的页面都报500。
原因:数据库密码和jdbc.properties里对不上;MySQL 8默认使用caching_sha2_password认证插件,而项目带的老版mysql-connector-java驱动只认识mysql_native_password;也可能root账号被限定只能从localhost登录。
解决:先用命令行验证本地MySQL的账号密码,mysql -u root -p能进说明密码没错。能进但项目还是报错,就检查驱动版本和url写法,把驱动jar换成mysql-connector-java 8.0.33以上,driverClass换成com.mysql.cj.jdbc.Driver,url里加上serverTimezone=Asia/Shanghai和useSSL=false。如果项目还用的是老驱动,也可以直接给root改认证方式来适配老驱动:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;这条SQL在单机课设环境里救过很多次急,但它只是兼容老驱动的临时手段,真实生产环境不建议这么改。
5.4 登录就500:连接池连接耗尽,页面能开数据库却罢工
现象:首页能打开,图书列表也能看,但只要一登录或者一提交表单,页面立刻500,日志里出现Connection pool exhausted或Too many connections。
原因:项目全局只有一个数据源,而每次请求用完连接没有归还;或者是连接池maxPoolSize配得太大,比如100甚至200,本机MySQL的max_connections只有151,多个连接池一叠加就爆了。网上书店里最典型的位置是DAO层的finally块没写close,或者写成了全空的JDBCUtils.close(conn, ps, rs)三连。
解决:先进入MySQL命令行执行SHOW PROCESSLIST;,看有多少连接挂在Sleep状态。如果几十条Sleep连接,就是连接没还池,重点检查所有DAO的finally块。如果连接数不多但还是爆池,把c3p0的maxPoolSize降到20,同时加上maxIdleTime=1800和unreturnedConnectionTimeout=30,让异常场景下的连接可以自动回收。顺带提一句:日志里如果出现Communications link failure,一般是MySQL把空闲超过8小时的连接断掉了,连接池要做保活配置,c3p0里对应的是testConnectionOnCheckout和preferredTestQuery。
5.5 改完代码不生效:明明改了jsp,运行还是旧页面
现象:改了一个jsp页面的标题,或者改了一个Servlet的跳转路径,IDEA自动编译、Tomcat也重启了,访问时页面还是旧内容。
原因:IDEA里没有“重新构建Artifact”,或者Tomcat这次跑的是上一次的部署副本,更常见的是浏览器缓存了旧页面。老项目改java文件必须重新编译才能进到WEB-INF/classes里,改jsp虽然不需要编译,但Tomcat要重新读取文件。
解决:养成一个习惯,改完代码后执行Build → Build Artifact → Rebuild,然后重启Tomcat。浏览器里按Ctrl+F5强制刷新,排除缓存干扰。IDEA里把Tomcat的On Update Action和On Frame Deactivation都设为Update classes and resources,改完jsp直接切换窗口就能热更新。如果上面都做了还是不生效,去Tomcat配置里看Deployment的打包路径,项目实际部署到的是tomcat/webapps下的一个临时目录,某些IDEA版本会把旧副本遗留在那里,清掉后再重新部署就行。
6. 跑通之后的验证清单:从注册到后台发货一条线测完
项目能启动、数据库能连上,不等于可以拿去演示了。建议按下面这条完整链路自测一遍,覆盖用户端和管理员端,也覆盖数据库连接、Session、购物车、事务几个关键模块。
| 测试步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 注册 | 在register.jsp填用户名密码提交 | 跳转登录页,users表新增一条记录 |
| 2 登录 | 用刚注册的账号登录 | 登录成功进入主页,Session里有user属性 |
| 3 浏览与搜索 | 首页按分类点图书,关键字搜索 | 列表分页正常,封面正常展示 |
| 4 加购 | 详情页点“加入购物车” | 购物车数量增加,同一本书重复加购数量累加 |
| 5 下单 | 购物车页点“去结算” | orders表生成订单,order_item生成明细,books表库存减少,购物车被清空 |
| 6 后台登录 | 用管理员账号进admin/login.jsp | 能看到订单列表和待发货订单 |
| 7 发货 | 后台把订单状态改为已发货 | 前台订单状态同步更新,数据库status字段变化 |
| 8 验证未登录拦截 | 退出登录,直接访问/order/create | 被过滤器重定向到login.jsp |
自测过程中如果第5步失败,优先看事务代码的日志和books表库存,一步一脚印地确认是SQL的问题还是事务回滚的问题。调试时用IDEA在LoginServlet和OrderServlet的入口打断点,配合浏览器的F12开发者工具Network面板,请求路径错了还是后台报错一目了然。这个习惯帮我排掉过无数“改了代码没效果”的玄学问题。
最后说一个我自己的习惯:拿到任何网上书店系统源码,先打开jdbc.properties改成本机数据库账号,再执行SQL脚本导库,最后才点Tomcat启动。顺序反过来的话,项目跑起来先报数据库连接错误,中间还混着字符集和端口问题,排查时三个因素搅在一起,难收拾。数据库这层地基先打好,之后功能验证都是水到渠成。希望帮到你。
本文还有配套的精品资源,点击获取