看到“JavaWeb_09”这个标题,我一下子就想起了当初跟着黑马那套经典教程一路敲代码的日子。这应该是整套JavaWeb学习路线里第九个阶段的项目实战了,多半是围绕一个完整案例来做的,比如用户管理、商品列表、购物车、订单结算这种一套流程走下来的web应用。用IDEA去跑JavaWeb项目,配合MySQL做数据存储,用Servlet和JSP把前后端串起来,最后部署到Tomcat上。这几乎是每个Java后端初学者都绕不过去的一套组合拳。
这篇博文我就结合自己当初从零到一完成这个项目的全过程,把IDEA里运行JavaWeb项目的整套配置、MySQL表结构设计、项目完整案例的实现思路,还有那些教科书里不会明说但实测很关键的坑,一次性都摊开来讲。适合正在学JavaWeb、准备做课程设计或者刚入职需要快速上手老项目的朋友参考。
1. 内容整体设计与思路拆解
先说清楚,JavaWeb_09这个项目,本质上是在之前学过的Servlet、JSP、JDBC、Filter、Listener这些散装知识点之上,做一个完整收口的综合性案例。它解决的核心问题不是“某个技术怎么用”,而是“一套web项目从零到能跑起来,到底要经历哪些步骤”。所以它的设计思路和单纯写几个demo完全不是一个量级。
1.1 核心需求解析
我那年拿到的题目要求很朴素,但信息量不小:做一个基于Servlet+JSP+MySQL的简单商品管理系统,支持用户注册登录、商品分类展示、购物车添加删除、订单提交,管理员可以管理商品上下架。听起来功能不算多,但真正动手做的时候才会发现,这里面的每一个模块都在逼你回头翻笔记。
首先是用户模块。注册要考虑用户名唯一性、密码不能明文存、邮箱格式要校验;登录要考虑Session失效、Cookie记住密码、过滤器拦截未登录用户。这块直接考察的是JavaWeb里状态管理的核心逻辑。
其次是商品模块。要分页、要有分类筛选、图片怎么上传、价格怎么存(用decimal而不是double)。这一块如果MySQL的列类型选错了,后期数据出问题心态直接崩。
然后是购物车和订单模块。购物车用Session存临时数据还是存数据库?订单提交要保证库存和下单是原子操作,这里就得引入事务。不过作为JavaWeb_09阶段的项目,我还没上到Spring,用的是JDBC手写事务,反而让我把Connection、Statement的生命周期理解得更透彻了。
最后是管理端。管理员登录、商品增删改查、订单状态修改,虽然没做权限框架,但通过Filter拦截路径前缀就能实现粗粒度的权限控制,这其实是很多公司老项目的通用做法。
1.2 为什么选传统JavaWeb而不是Spring Boot
很多新手会问:现在都Spring Boot了,学这种Servlet+JSP的老技术还有意义吗?我的看法是,JavaWeb_09这个阶段就是为了让你看透web底层的运行机制。Spring Boot把Tomcat、DispatcherServlet、参数绑定全封装了,你在IDEA里点一下按钮项目就跑起来了,但一旦出问题,你连日志在哪看都不知道。
传统JavaWeb项目里,你要自己往IDEA里配置Tomcat、手动引入jar包或用Maven管理依赖、在web.xml里注册Servlet和Filter、把请求路径一个一个对着源码找。这个过程确实繁琐,但它把所有“约定优于配置”背后藏起来的细节都暴露了一遍。比如你配置完web.xml之后,第一次理解为什么路径要带项目名,为什么Servlet-class要写全限定名,为什么连接数据库的driver要写Class.forName。这些疑问弄懂了,后面学Spring MVC、Spring Boot就是降维打击。
所以这个项目的设计思路,本质上是“把地基挖出来让你看清再填回去”。它在整个JavaWeb学习路线里的位置,恰好是承前启后——前面是零散知识点,后面是框架时代。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心细节解析与实操要点
真到了动手环节,第一步就折磨人:怎么在IDEA里把JavaWeb项目正确配置起来,然后让它跑在Tomcat上。这一节我把从新建项目到最终运行成功的每个关键步骤都拆开讲,包括那些让你怀疑人生的配置项。
2.1 IDEA运行JavaWeb项目的完整配置
先说版本,我用的IDEA 2021.2版本,Tomcat 8.5,JDK 1.8,MySQL 5.7。这个组合是当年黑马教程里最经典的,也是现在很多学校机房和学生笔记本里最常见的。
第一步,新建项目。选择Java Enterprise,注意这里不是选Java普通项目。在Additional Libraries里勾选Web Application,然后确认Servlet和JSP的依赖被正确引入。如果这一步漏了,后面写代码时所有javax.servlet类都会报红。
第二步,配置Tomcat。在Run/Debug Configurations里点+号,找到Tomcat Server里的Local。然后配置Application server选项,选择你本地解压好的Tomcat目录。这里有个极易踩坑的点:默认情况下Deployment里没有Artifact,你需要手动点+号,选Artifact,把web项目打包出来的war exploded包加进去,然后修改Application context为/,这样访问的时候就不用带项目名了。项目名带斜杠那种URL在写页面跳转时极其容易出错,尤其JSP里的绝对路径,稍微不注意就是404。
第三步,配置依赖。如果用Maven管理依赖,在pom.xml里加上Servlet、JSP、JSTL、MySQL驱动、也许还有FastJson或Jackson。但很多课程案例是直接用IDEA添加Library的,我建议新同学直接用Maven,因为后面做真实项目离不开它,早晚要学。我当年用的pom.xml核心依赖大概这样:
xml复制<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
注意Servlet和JSP的依赖scope要写provided,因为Tomcat里面自带这两个库,如果你不写provided,编译后打war包时会把它们打进去,在Tomcat里就有版本冲突的风险。
第四步,配置数据库连接。我个人不推荐在Servlet里直接写死数据库信息,而是写一个db.properties文件放在src目录下,再用一个工具类去读取。这样以后换数据库环境,只改配置文件就行。这个习惯对JavaWeb项目特别重要,因为老项目里数据库信息到处写死的话,排查起来就是一场灾难。
第五步,确认web.xml。如果你用的是Servlet 4.0,可以不用web.xml而是用@WebServlet注解注册Servlet。不过如果这个项目要交作业或者做课程设计,建议还是保留web.xml,毕竟很多课设评分标准里还要看web.xml写没写。而且Filter的配置在注解方式下有时会因执行顺序问题踩坑,用web.xml里面的filter-mapping顺序反而更直观。
2.2 MySQL数据库设计与建表注意
商品系统光建表这一环就有很多讲究。我见过不少同学把价格字段设成float或double,后面计算总价的时候出现一堆小数位,这就是没想明白浮点数在计算机里的存储方式。价格必须用decimal,比如DECIMAL(10,2),这样钱的计算才不会失真。
用户表的设计要留uid、username、password、email、phone、create_time这些字段。密码存储一定不要明文,至少用MD5加盐,虽然MD5在安全圈已经被说烂了,但作为JavaWeb阶段的练习够用了,比明文强一百倍。有些要求高的还能用BCrypt,不过那一般放到学Spring Security之后再做。
商品表要有id、name、price、stock、image、category_id、status、create_time。注意商品状态字段,1表示上架,0表示下架,别用is_shelf这种布尔型,因为以后商品可能有“推荐”、“预售”等更多状态,用一个int型字典枚举更灵活。
分类表很简单,id和name。订单表要把订单主表和订单明细拆成两张:order_main存收货人、总价、下单时间、状态;order_item存每个订单里的商品id、数量、单价。这样拆的好处是统计订单里的商品明细时不用去解析文本串。
下面是当时设计的简化建表SQL,可以直接参考着调整:
sql复制CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL UNIQUE,
`password` varchar(64) NOT NULL,
`email` varchar(100) DEFAULT NULL,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `tb_category` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `tb_product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`price` decimal(10,2) NOT NULL,
`stock` int(11) NOT NULL DEFAULT 0,
`image` varchar(255) DEFAULT NULL,
`category_id` int(11) NOT NULL,
`status` tinyint(4) DEFAULT 1,
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `tb_order_main` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`total_price` decimal(10,2) NOT NULL,
`status` tinyint(4) DEFAULT 1,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `tb_order_item` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`quantity` int(11) NOT NULL,
`price` decimal(10,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这里要特别强调一下,所有字符集都要统一成utf8mb4,不只是表本身,连接数据库的URL也要带上characterEncoding=utf8和useSSL=false参数,否则插入中文数据时很可能会出现乱码。
3. 实操过程与核心环节实现
有了配置和表结构,下面就是最肝的部分:用Servlet+JSP把功能一个个实现出来。JavaWeb_09阶段项目通常已经不用纯JSP写业务逻辑了,而是MVC的思路——JSP做视图,Servlet做控制器,JavaBean/DAO做模型。这个分层思想一定要在动手前想清楚,不然代码会越写越乱,最后自己都不想看。
3.1 三层架构与包结构设计
我在项目里用了经典的包结构:
code复制com.example.shop
├── entity/ # 实体类 User, Product, OrderMain, OrderItem
├── dao/ # 数据访问层 UserDao, ProductDao, OrderDao
├── service/ # 业务逻辑层 UserService, ProductService, OrderService
├── controller/ # Servlet 控制器 UserServlet, ProductServlet, OrderServlet
├── filter/ # CharacterEncodingFilter, AdminFilter
├── util/ # DBUtil, MD5Util, StringUtil
└── web/ # JSP 页面
实体类很简单,就是对应数据库表的普通JavaBean,属性类型和数据库列类型对应。这里教大家一个快速写实体类的方法:用IDEA的Generate功能,或者直接用MyBatis Generator那个思路手写,关键是注意price要用BigDecimal,create_time要映射成java.util.Date或者是LocalDateTime,不要用字符串去存日期。
DAO层是直接操作数据库的,要用JDBC的东西。之前学过的PreparedStatement在这一定要用上,千万别用Statement去拼接SQL,不然SQL注入是一测一个准。DBUtil这个工具类负责获取连接和关闭连接,为了避免代码里到处写Class.forName,统一放在静态代码块里。
Service层是业务逻辑所在,比如注册时先检查用户名是否存在,再插入用户;下单时先查库存、再插入订单主表和明细、再扣库存。这些步骤不是一步能完成的,在Service里串起来。
Controller层就是Servlet,核心工作就是收参数、调Service、根据结果往哪个页面转发或重定向。JSP页面上尽量别出现Java代码片段,虽然老教程里会写<% %>,但实际开发和课程设计里用JSTL+EL表达式才是正经做法。
3.2 注册登录模块的完整实现流程
先讲注册模块。前端页面上一个form表单,method用post提交到/user/register。在UserServlet里面判断action参数:注册和登录我放在同一个Servlet里,不同操作通过action=register还是action=login来区分。
注册流程:
- 先用request.getParameter拿到username和password。
- 做基础校验:用户名长度、密码长度、两次密码是否一致。这些东西后端必须重新校验一遍,不能只依靠前端JS,因为接口可以被绕过。
- 调UserService.register方法:先查一下username是否已存在,存在就提示“用户名已存在”,不存在就把密码做MD5加盐,然后插入数据库。
- 注册成功就重定向到login.jsp,失败就转发回register.jsp并带出错误信息。
登录流程稍微复杂一点:
- 根据用户名查用户,如果不存在直接简单提示“用户名或密码错误”。注意为了安全,最好不区分“用户名不存在”和“密码错误”,防止别人逐个试探。
- 存在就比对加密后的密码,一致就说明登录成功。
- 登录成功后要做两件事:把用户对象放进Session中,然后重定向到首页。这看起来简单,但有个关键点,登录成功后绝对不能用转发,要用重定向。因为如果用户双击刷新页面,转发会再次触发登录逻辑,很可能表单重复提交。
- 如果要记登录状态,可在登录时把这个用户名和密码加密后写进Cookie,设置MaxAge为7天,下次访问时通过一个AutoLoginFilter自动登录。
这个模块里我踩过的第一个坑是Session的值没更新。场景是这样的:用户修改个人资料后,我更新了数据库里的数据,但Session里存的还是就的对象,结果页面上显示的依然是旧信息。解决方案是每次更新完用户数据后,把最新的用户对象重新setAttribute到Session里。
3.3 商品列表与购物车的实现思路
商品列表就是典型的展示页。ProductServlet中的action=list从Service拿到所有上架商品,放到request域里转发到product_list.jsp。JSP页面用JSTL的<c:forEach>循环渲染表格,也可以加分类筛选,传category_id给SQL的where条件。分页要做的话,自己封装一个PageBean,包含当前页码、每页条数、总条数、总页数和当前页数据列表。SQL用LIMIT offset, rows,offset等于(pageNum-1)*pageSize。这一步对SQL基本功要求挺高,建议大家都自己写一遍。
购物车的实现,我选择的是存Session里而不是数据库里,因为购物车本质上是临时性的,而且没登录也可以加购物车。用一个Map<Integer, CartItem>来存,key是商品id,value是包含商品信息、数量、小计的CartItem对象。加购物车时,先判断Map里有没有这个商品,有就数量+1,没有就new一个CartItem塞进去。
购物车页面显示时,遍历Map就能得到每条记录。这里有个小细节:Map的遍历顺序不稳定,希望展示时按添加顺序来,可以把Map换成LinkedHashMap。
下单模块是最容易出logical bug的地方。点击“提交订单”按钮时,前端要提交一个订单,后端OrderService要做这几件事:
- 从Session里拿到用户id和购物车map。
- 计算总价:遍历购物车,把每个商品的
price * quantity累加,注意用BigDecimal来做加法,不要用double。 - 检查每个商品库存是否足够,不足直接抛业务异常,整个订单取消。
- 插入订单主表,拿到自增的order_id。
- 遍历购物车,依次插入订单明细表。
- 扣减库存,执行
update tb_product set stock = stock - ? where id = ?。 - 全部成功后,清空Session里的购物车。
这几步必须放在同一个数据库事务里。JDBC里实现事务很容易:
java复制Connection conn = DBUtil.getConnection();
try {
conn.setAutoCommit(false);
// 执行多条SQL
conn.commit();
} catch (SQLException e) {
conn.rollback();
e.printStackTrace();
} finally {
// 关闭资源
}
当时我为了检查是否真的回滚,故意把库存设置成0,然后下单,发现数据库里还是没创建订单,这才安心。这个测试方法很笨,但很有效。
3.4 Filter过滤器与字符编码处理
JavaWeb项目一个非常烦人的问题就是中文乱码。表单提交是POST,接收端要设置request.setCharacterEncoding("UTF-8");响应端要设置response.setContentType("text/html;charset=UTF-8")。如果每个Servlet里都写一遍,代码冗余不说,还容易漏写。最优雅的解决方式就是写一个CharacterEncodingFilter,配置到所有路径上:
java复制@WebFilter("/*")
public class CharacterEncodingFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=UTF-8");
chain.doFilter(req, resp);
}
}
这个过滤器还有个大用处:处理GET请求乱码。Tomcat8和以上版本,GET请求的URI解码默认已经是UTF-8了,所以一般不会乱。但如果你的Tomcat版本比较老,且URL中还有中文,就需要改server.xml里的URIEncoding="UTF-8",这是一般都没注意到的问题。
AdminFilter是留给商品管理的。商品管理页面的前缀是/admin/,所有以/admin/开头的请求都经过这个过滤器,如果Session里没有管理员对象,就重定向到登录页。这个设计我在没学Spring Security之前,算是理解了filter的威力。
4. 常见问题与排查技巧实录
自己写这个项目时遇到的坑,比看教程时想象的要多得多,这里整理了当时反复折腾过的四个典型问题,每个都附上排查思路。
4.1 IDEA启动Tomcat后无法访问
最常见的问题是Tomcat起来了,但登录页怎么都打不开,控制台还报404。先别急着去改代码,按这个顺序排查:
- 看IDEA底部Run窗口是否显示“Server startup in xxx milliseconds”而不是异常堆栈。
- 打开浏览器访问
http://localhost:8080/index.jsp,如果没有404,说明服务器没问题,问题在项目部署路径。 - 检查Run Configuration里的Deployment,看Application context是不是
/example或者别的什么。访问的时候就必须带这个前缀。 - 如果页面上JSP引用CSS或JS用了相对路径
css/style.css,当前URL是/product/list时,浏览器会去请求/product/css/style.css,必然404。解决方法是JSP页面里用<%=request.getContextPath()%>拼出应用的根路径,或者用${pageContext.request.contextPath}配合JSTL。
我当时就是因为没改Application context,结果所有页面都404,还一度以为Tomcat坏了。
4.2 数据库空指针与驱动加载失败
写JdbcUtil时,最常见的就是java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。这个错误的解决方式很简单:项目没引入mysql-connector-java的jar包。如果你用Maven,确认pom.xml里有没有依赖;如果你是自己导入jar包,确认它在Artifact里,而不仅仅是本地文件。
但还有种更隐蔽的坑,是我当年怎么Debug都查不出来的:明明引入了jar包,仍然报找不到类。研究了半天,最后发现是因为IDEA里Tomcat运行的Artifact根本没有把jar包同步过去。解决方法是,在Project Structure的Artifacts里,找到WEB-INF下的lib目录,把依赖的jar加进去,如果lib目录为空,实际运行时的Classpath里就没有这些驱动类。用Maven管理依赖并且IDEA自动部署war包时一般没这个问题,但自己配了包结构的路容易在这翻车。
4.3 JSP页面Java代码与静态资源冲突
有些同学在JSP里到处写<% %>,遇到页面显示null,特别容易误以为是取值方式不对。其实在JSP页面里直接嵌Java片段对初学者来说确实不难,但很容易犯“忘加el表达式”的毛病。比如从request里取值用request.getAttribute("product"),到JSP里想显示名字,结果写成了<%=request.getParameter("name")%>,取值取错了自然显null。建议彻底改用EL表达式${product.name},简洁、不易错。
还有个静态资源加载的问题。如果你在webapp下面放了css/js目录,而且用Servlet的注解路径来设置静态资源的映射,可能会被/*这个过滤器拦下来。CharacterEncodingFilter还好,AdminFilter就麻烦了——如果拦截范围是/admin/*,但静态资源放在/admin/css下,最好把静态资源放到一个不会被拦截的路径,或者干脆用web.xml里<servlet-mapping>方式,让静态资源直接交由default servlet处理。
4.4 事务不回滚的排查思路
这是业务逻辑里最坑的一个。明明在Service里写了conn.setAutoCommit(false),下单失败以后数据还是变了。排查思路是从资源如何使用入手:
- 确认Service里的所有数据库操作都用的是同一个Connection对象。很多新手在DAO层里自己又调用DBUtil.getConnection()取了一个新链接,事务在A连接提交了,B连接的操作当然不受事务控制。解决办法就是Service层拿到一个连接,传入DAO的方法参数里。
- 确认rollback被真正执行到了。如果前面的SQL抛了异常后,你没有catch,而是在finally里直接关连接,而连接关闭时默认可就自动提交了,之前未提交的操作照样会生效。正确做法是在catch块里rollback,然后抛出自定义业务异常通知上层。
- 确认没有把异常吃掉。我之前为了不让控制台报红,在catch里写了个
e.printStackTrace()然后继续往下走,手动下单时数据居然正常,但多测试几次总会遇到库存负数的情况。后来把事务代码统一封装成模板方法,问题才彻底解决。
自动提交和手动提交的行为差异,可以用一句话概括:Connection只有commit之后,修改才是永久性的,否则一切程序里看起来生效的改动都可能是假象。
5. 进阶优化与项目扩展建议
JavaWeb_09做完之后,别急着开心,还有很多可以做。我个人认为这个项目的价值不仅在于完成作业,更在于能以此为基础延伸出一堆能在简历上写的东西。这里给几个当时实际做过的优化方向。
5.1 分页查询与批量操作
商品列表页的分页是很多课设的加分项。我建议不要用第三方组件,手写一个分页工具类,把总页数算法的边界情况想清楚。其实分页SQL不难,难的是前端显示页码那一块,怎么生成数字列表、怎么处理当前页高亮、首页尾页在边界时要不要显示省略号。这个花点时间调试一下,对前后端交互的理解帮助很大。
批量操作方面,比如批量上架和下架,用SQL的update ... where id in (...),然后在Servlet里接收String类型的id主键,通过逗号或下划线拆分后拼接成List,再用PreparedStatement批量执行。注意拼接占位符的时候别直接用字符串拼接SQL,容易有注入隐患。
5.2 图片上传与本地存储
商品图片是很多项目没有实践到的难点。传统的JavaWeb项目可以这么做:
- 依赖
commons-fileupload和commons-io包。 - Servlet里用FileUpload解析multipart/form-data表单。
- 把图片保存到项目根目录的upload文件夹下,文件名用UUID重命名,防止冲突。
- 数据库中保存“upload/xxx.jpg”这种相对路径,JSP里结合项目根路径渲染。
这里有个重要经验:保存图片后,访问图片不能直接通过/upload/xxx.jpg,因为本地开发时项目运行的Artifact目录可能和你的项目源目录不一致。要么在Tomcat里配置虚拟目录映射,要么通过一个DownloadServlet去读图片文件流输出,两种方式我后来都试过。如果只是课设,建议直接配置虚拟路径,用IDEA里的External Libraries部署目录结构时,图片跟着Tomcat走,实用又简单。
5.3 AJAX局部刷新
起初页面都是点击表单然后整个页面刷新,体验很差。后来我在注册功能里加了username是否可用的AJAX校验。原理很简单:
- 页面先获取input框的值,通过XMLHttpRequest或JQuery的
$.get发送到/user/checkUsername。 - 后端这个Servlet只返回JSON字符串,比如
{"valid":true}。 - 前端根据返回结果在input框下方提示红色或绿色字体。
这样做的意义是让你提前感受一下JSON格式的数据交互方式,同时它也是很多前后端分离项目的最初形态。JavaWeb_09阶段虽然不需要把所有功能都改造成AJAX,但做一个注册校验或者搜索联想功能,能明显提升项目的完成度。
6. 常见问题速查表与避坑指南
最后整理一个速查表,这些都是在同学群里看到大家高频踩中的问题,比我自己遇到的还要全,汇总成一张表放在这里,遇到问题直接对照着排查。
| 问题现象 | 原因 | 解决方案 |
|---|---|---|
| 启动报“No artifacts marked for deployment” | IDEA没配置部署Artifact | Run Configurations → Deployment → 添加war exploded |
| Tomcat启动后访问404 | 项目部署路径不对或用错了URL前缀 | 检查Application context是否与访问路径一致 |
| JDBC操作报Unknown database | 数据库URL里的库名没写或写错 | jdbc:mysql://localhost:3306/shopdb 确保数据库已创建 |
| 连接数据库报SSL异常 | MySQL高版本默认开启SSL校验 | URL加参数useSSL=false |
| 中文乱码问题 | 请求/响应编码未统一 | 配置CharacterEncodingFilter,JSP页面<%@ page contentType="text/html;charset=UTF-8"%> |
| 部分页面中文正常,部分乱码 | PHP般场景下GET请求路径带中文 | 修改Tomcat的server.xml加URIEncoding="UTF-8" |
JSP页面显示${}原样 |
Tomcat没引入JSTL依赖 | 在pom.xml或lib目录中加入jstl和standard相关jar |
| 表单提交两次 | 前端按钮重复点击 | 提交后立即禁用按钮或使用重定向避免刷新重、新提交 |
| 下单后库存变成负数 | 事务没控制好或没有校验库存 | 在Service层加事务,SQL中用stock = stock - ?并加where判定 |
| 修改资料后Session还是旧数据 | 只更新了数据库,没更新Session | 更新后重新request.getSession().setAttribute("user", newUser) |
| 页面图片全部裂开 | 图片路径是绝对磁盘路径 | src属性要用request.getContextPath()+"/upload/xxx"拼接 |
| Web项目Export成war后打不开 | 依赖jar没有打包进WEB-INF/lib | 检查Artifact的Output Layout,确认lib下所有依赖都被包括 |
这些坑说来说去,其实根源都差不多:对JavaWeb项目的运行机制理解不透。Tomcat并不是简单“读取项目文件”就跑起来的,它需要一套编译、部署、类加载的完整流程。所以做JavaWeb_09这个项目的过程,本质上就是在帮大脑建立那一套完整的运行模型。
最后再分享一个我个人的做事习惯。项目做完了别删,把每一阶段能跑通的版本都打个zip包存起来,包括数据库的建表SQL、pom.xml、甚至当时的截图。因为后面学框架时,你会很需要这些“老代码”来做对比,比如学完Spring MVC回头再看Servlet的路径映射,很多之前想不通的设计一下就明白了。如果你还在做这个项目,遇到没思路的时候就多看两眼这几个模块:用户Session、事务、Filter拦截、分页SQL。这四个地方弄扎实,等于JavaWeb阶段的地基就稳了大半。
