
我之前带过好几个实习生发现一个特别有意思的现象大家学JavaWeb的时候视频看了、笔记抄了但真到自己用IDEA新建一个项目、连上MySQL、跑通一个完整案例往往要折腾好几天。尤其是搜idea运行javaweb项目配置、javaweb项目完整案例mysql这些关键词的同学大概率是卡在了某个配置环节或者照着教程做却始终跑不起来。这篇文章是我整理的第05期JavaWeb实战笔记核心就三件事IDEA里JavaWeb项目的正确运行方式、MySQL接入时那些容易踩的坑、以及一个能完整跑通的用户管理模块案例。适合正在学JavaWeb的初学者也适合那些看完了黑马视频但动手能力还欠火候的同学。我会把教程里没细讲、但实操中必须知道的细节全部摊开来说包括为什么要这么配、报错了怎么一步步排查。看完你至少能独立把一个JavaWeb项目从零搭起来跑通。1. 动手前先理清JavaWeb项目的骨架目录、依赖和运行链路很多同学一上来就跟着教程点鼠标结果项目长什么样、文件为什么放那个位置、Tomcat到底在中间扮演什么角色完全没有概念。这样一旦脱离教程换个版本、改个目录立刻抓瞎。我建议你先把JavaWeb项目的骨架搞清楚再谈运行。1.1 一个JavaWeb项目由哪些部分组成标准的JavaWeb项目从IDEA里看大致分这么几块src目录存放Java源码通常按包名分层比如com.example.dao、com.example.service、com.example.servletweb目录旧版叫WebContent存放JSP、HTML、CSS、JS等前端资源WEB-INF目录这是核心web.xml在这里classes编译后的class文件也在这里lib第三方jar包也在这里pom.xml或build.gradle如果用Maven/Gradle管理声明项目依赖这里有个新手经常搞混的点WEB-INF下面的东西是浏览器直接访问不到的。JSP页面如果放在WEB-INF里面必须通过Servlet转发才能访问直接输入URL是404。这个设计是为了安全但很多第一次接触的人会以为是自己路径写错了。如果你用的是Maven项目目录结构又会多一层src/main/java、src/main/resources、src/main/webapp。本质上跟上面是一回事只是Maven帮你自动管理了依赖和构建流程。我强烈建议你从Maven开始学因为以后工作里基本都是Maven或Gradle手工导jar包的时代已经过去了。1.2 从URL到数据库一次请求的完整旅行理解一次请求是怎么走的比背一百个API都有用。假设你在浏览器输入了一个地址比如http://localhost:8080/user/list背后发生了什么浏览器发出HTTP请求到Tomcat端口8080Tomcat根据请求的路径找到对应的Servlet通过web.xml或注解映射Servlet调用Service层处理业务逻辑Service层调用Dao层访问数据库通过JDBC或MyBatisDao层从MySQL查出数据逐层返回给ServletServlet把数据放进request域转发到JSP页面渲染成HTMLTomcat把HTML响应给浏览器整个过程里Tomcat是Web服务器也是Servlet容器。它负责监听端口、接收请求、创建Servlet实例、管理Servlet生命周期。很多人把Tomcat当成部署工具其实它更准确的定位是Java Web应用的运行环境。没有Tomcat你的Servlet代码就只是一堆.class文件没法对外提供服务。1.3 为什么新手总在跑不起来上卡住我总结了一下卡住的根本原因通常是这三类一是环境割裂。IDEA、JDK、Tomcat、MySQL各自安装但版本之间的兼容性没人告诉你。比如JDK17和Tomcat9、Tomcat10的兼容性就不同Tomcat10把javax.servlet换成了jakarta.servlet旧代码复制进去直接报ClassNotFoundException。二是缺依赖。写Servlet、连数据库都需要第三方jar包。用了Maven没配好依赖没用Maven又漏了jar包导致运行时ClassNotFound。三是不懂报错。看到一屏红色日志就慌其实很多报错只需要看最上面几行Caused by就能定位。所以接下来的内容我会围绕这三个痛点把配置和排查讲透。2. IDEA运行JavaWeb项目必踩的配置细节与正确姿势IDEA里运行JavaWeb项目本质上是把Web应用部署到Tomcat上。这个操作不算难但配置项多而且IDEA的界面版本之间有差异导致很多人照着截图都找不到按钮。我以IntelliJ IDEA 2023版本为例讲几个最关键的点。2.1 Tomcat集成别把Artifact选错在IDEA里添加Tomcat运行配置时有一个叫Deployment的选项卡新手最爱在这里翻车。你需要先把项目打成Artifact通常是war exploded格式然后把这个Artifact加到Tomcat的Deployment列表里。这里面有两个坑第一war和war exploded的区别。war是把项目打包成一个war文件再部署适合最终上线war exploded是把项目解压后的目录直接作为部署目录IDEA里调试基本都用这个改了代码后可以热部署不用反复重启Tomcat。第二Application context的路径。你填的/还是/user直接决定你访问URL的前缀。如果填的是/访问地址是http://localhost:8080/xxx如果填/myapp就要http://localhost:8080/myapp/xxx。这个值会被写进Tomcat的conf目录下的配置文件如果改了没生效大概率是IDEA缓存了旧配置试试clean再rebuild。完整的操作是这样打开Run - Edit Configurations - 点左上角号 - 找到Tomcat Server - Local - 在Server选项卡里选好Tomcat安装目录关键是选到Tomcat的根目录不是bin目录- 切到Deployment选项卡 - 点号 - Artifact - 选择你项目的war exploded - Application context填/ - Apply。2.2 项目结构里的lib和FacetIDEA隐藏的坑很多非Maven项目jar包都是放在WEB-INF/lib目录下的。但光放进去还不够IDEA必须知道这些jar包在编译和运行时都能被找到。做法是File - Project Structure - Libraries - 号 - Java - 选中lib目录。这样IDEA就会把整个lib目录下的jar包都加入classpath。还有一个很多人忽略的东西叫Facet。Project Structure里Facets面板需要给项目添加Web类型并且指定Web资源目录就是webapp或web目录、Web部署描述符web.xml的位置。否则你在web.xml里配置的Servlet映射IDEA根本不会识别。我遇到过最典型的情况代码看着没问题web.xml也没问题但一启动Tomcat就报404。最后发现是IDEA的Facet里Web资源目录路径配错了它指向了一个不存在的目录。所以新建完项目先检查Project Structure里的两个地方Modules是否识别了src为源码目录Facets是否识别了Web资源目录。2.3 热部署与日志查看技巧调试的时候我最烦反复重启Tomcat。IDEA里Tomcat配置有两个跟热部署有关的选项On Update action当代码更新时触发什么操作建议选Update classes and resourcesOn frame deactivation当IDEA失去焦点比如你切到浏览器时自动更新这个我一般也勾上这两个配合起来改一下JSP或者Java方法体切回浏览器刷新就能看到效果。但要注意如果改动的是方法签名、新增了Servlet类、改了web.xml还是需要重启Tomcat的。热部署不是万能的它只是帮你在不重启的情况下替换掉改动的class文件和静态资源。日志查看也有讲究。Tomcat的控制台日志在IDEA的Run窗口里能看到但如果项目部署到独立Tomcat就需要看Tomcat安装目录下的logs文件夹重点是catalina.out或catalina.日期.log和localhost.日期.log。前者记录启动和全局异常后者记录Web应用的访问细节。如果你部署了项目但Tomcat启动后没报错却访问不到多半要看localhost日志那里会有Deployment of web application ... has finished in xxx ms之类的信息以及上下文路径的注册记录。3. 接入MySQL连接池、JDBC和SQL注入的三个层次JavaWeb项目十有八九要接数据库。这一块我想分三个层次讲最基础的JDBC连接、工程上必须用的连接池、以及安全性层面的SQL注入问题。这三个层次代表了三类不同水平的代码你可以对照一下自己写到了哪一步。3.1 从DriverManager到连接池为什么不能用最简写法教科书里最常见的JDBC写法是Class.forName(com.mysql.cj.jdbc.Driver); String url jdbc:mysql://localhost:3306/javaweb_demo?useSSLfalseserverTimezoneAsia/Shanghai; String user root; String password 123456; Connection conn DriverManager.getConnection(url, user, password); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(select * from t_user);这段代码能跑通但绝对不能用在真实项目里。原因有两个一是每次请求都创建物理连接而MySQL建立连接需要TCP握手、权限验证、SSL协商代价很高。高并发场景下几百个请求就能把数据库连接数打满。二是根本没有资源释放逻辑。Connection、Statement、ResultSet如果不在finally里关闭连接会泄漏。MySQL默认的max_connections是151泄漏几十次就再也连不上了必须重启数据库才能恢复。工程上的做法是使用连接池比如HikariCP或Druid。连接池的思想很朴素预先创建一批连接放在池子里用的时候借出来用完了还回去而不是扔掉。HikariCP在SpringBoot里是默认选择性能很好Druid是阿里开源自带监控页面国内中小项目用得很多。使用Druid时配置一把梭DruidDataSource dataSource new DruidDataSource(); dataSource.setUrl(url); dataSource.setUsername(user); dataSource.setPassword(password); dataSource.setInitialSize(5); dataSource.setMinIdle(5); dataSource.setMaxActive(20);关键参数的含义initialSize是启动时创建多少个连接maxActive是最大活跃连接数maxWait是获取连接的超时时间超过这个时间拿不到连接就报错。如果你发现系统卡在获取连接上先看看是不是maxActive设小了或者有连接泄漏没归还。3.2 表结构设计一个用户管理模块的最低要求不管多复杂的案例只要是用户管理核心表基本上就一张用户表。我的设计习惯是CREATE TABLE t_user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL COMMENT 用户名, password VARCHAR(100) NOT NULL COMMENT 密码加密后, email VARCHAR(100) DEFAULT NULL, phone VARCHAR(20) DEFAULT NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT 1启用 0禁用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;几个容易被忽视的点字符集一定要用utf8mb4不是utf8。utf8在MySQL里是utf8mb3的别名最多支持3字节像emoji表情和部分生僻字会存不进去。utf8mb4才是完整的UTF-8。password字段不要用char(32)存MD5现代项目至少用SHA-256加盐或者直接用BCrypt。create_time和update_time写成DEFAULT CURRENT_TIMESTAMP可以在插入和更新时自动维护时间少写不少Java代码。逻辑删除deleted字段的话查询条件里别忘带where deleted 0否则你会查出已经删除的数据。3.3 PreparedStatement为什么是底线很多人写SQL拼接是这么写的String sql select * from t_user where username username and password password ;这段代码如果被用户输入admin or 11拼接出来的SQL就变成了select * from t_user where username admin or 11 and password xxx因为or 11恒成立整条查询无条件通过这叫万能密码注入。更严重的是攻击者可以用; drop table t_user; --来删表。解决方案是PreparedStatement预编译占位符String sql select * from t_user where username ? and password ?; PreparedStatement pstmt conn.prepareStatement(sql); pstmt.setString(1, username); pstmt.setString(2, password); ResultSet rs pstmt.executeQuery();PreparedStatement为什么能防注入因为参数在预编译阶段就被当作纯数据传给数据库数据库只把它当成字符串值永远不会被解析成SQL关键字或表达式。这也是为什么我在代码审查时只要看到字符串拼接SQL几乎都会打回去。4. 完整案例复盘用户管理模块从建表到上线验证前面讲了很多理论这一节我带你完整走一个案例。这个案例很典型就是用户管理模块的增删改查加一个登录验证。它是JavaWeb项目完整案例里出现频率最高的场景你在搜javaweb项目完整案例mysql时看到的十有八九是这个。4.1 需求与页面原型我们的需求很简单用户输入用户名和密码点击登录验证通过后跳转到用户列表页用户列表页展示所有用户支持按用户名模糊搜索、删除用户、跳转到编辑页新增/编辑用户共用同一个表单页提交后保存到数据库退出登录为什么选这个案例因为它麻雀虽小五脏俱全Servlet映射、请求转发、重定向、JDBC增删改查、列表循环渲染、条件查询JavaWeb的核心知识点全用上了而且逻辑不绕适合用来打通前端 - Servlet - Service - Dao - MySQL的完整链路。页面我直接用JSPJSTLEL来写不用前端框架。原因很现实JavaWeb阶段重点在后端JSP烂熟于心后面学SpringMVC和Vue时会轻松很多。JSP里如果对EL表达式和C标签不熟很容易在渲染时踩坑。4.2 后端分层Servlet、Service、Dao怎么划分很多同学写代码喜欢把逻辑全塞在Servlet里一个方法搞定查询和数据库操作页面上确实能跑但做完这个案例你就会感觉越写越乱。我建议从第一次写就养成三层结构。先建包com.example.user.entity -- 实体类 User com.example.user.dao -- 数据库访问层UserDao com.example.user.service -- 业务逻辑层UserService com.example.user.servlet -- 控制层LoginServlet、UserListServlet、UserEditServlet、UserDeleteServlet分层有个关键原则在Service层体现得最清楚事务控制的边界。比如删除用户的同时需要写入日志表如果其中一步失败应该整体回滚。事务必须放在Service层统一管理放在Dao层的话每个Dao方法各开各的事务无法保证原子性。以查询用户列表为例UserDao里写一个方法public ListUser findByUsername(String username) { String sql select id, username, email, phone, status, create_time from t_user where username like ?; try (Connection conn DbUtil.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, % username %); try (ResultSet rs pstmt.executeQuery()) { ListUser list new ArrayList(); while (rs.next()) { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); // ... list.add(user); } return list; } } catch (SQLException e) { throw new RuntimeException(查询用户失败, e); } }注意这里的try-with-resources写法Java7及以上版本可以用它会在代码块结束后自动关闭Connection、Statement、ResultSet比在finally里手动关简洁多了而且不会漏。Servlet这边只负责三件事接收请求参数、调用Service、决定跳转到哪个页面。不要写SQL不要写业务判断。WebServlet(/user/list) public class UserListServlet extends HttpServlet { private UserService userService new UserService(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); if (username null) { username ; } ListUser userList userService.findByUsername(username); req.setAttribute(userList, userList); req.setAttribute(searchUsername, username); req.getRequestDispatcher(/WEB-INF/jsp/userList.jsp).forward(req, resp); } }这里有个细节搜不到结果时username可能是null如果直接把null传给ServiceSQL会变成where username like %null%查不出来任何东西。所以先做空值处理。类似这种小坑你多写几个案例就记住了。4.3 联调测试使用Postman和浏览器验证接口项目跑起来之后不要一上来就点页面。我习惯分三步验证第一步验证数据库连接。写一个最简单的Servlet或者直接跑一段JDBC代码能查出数据说明环境和连接串没问题。第二步用Postman直接测Servlet接口。比如访问http://localhost:8080/user/list?usernameadmin服务端日志里能看到SQL执行浏览器或Postman里能看到JSP渲染后的HTML。这一步能帮你把控制层和数据层的问题分开如果Postman返回200而且内容正确说明后端逻辑OK剩下的只是页面展示的问题。第三步在浏览器里完整走一遍用户流程新增 - 列表看到新用户 - 搜索 - 编辑 - 删除。这一步主要检验Cookie/Session和跳转逻辑。还有一个联调时特别容易踩的坑表单提交方式。我见过很多人把新增功能的form设成methodpostServlet里却只实现了doGet方法结果提交报405。要记住URL直接访问、超链接跳转是GET表单提交、Ajax的POST是POST。Servlet的doGet和doPost都要想清楚用哪个或者统一在service方法里处理。我常用的做法是在Servlet里这样写Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String method req.getMethod(); if (POST.equalsIgnoreCase(method)) { doPost(req, resp); } else if (GET.equalsIgnoreCase(method)) { doGet(req, resp); } }这样无论表单GET还是POST都能进到统一的处理逻辑里。当然生产环境建议还是严格区分因为GET请求的参数会被记录在日志和浏览器历史里不适合传敏感信息。5. 运行中的异常排查黑马笔记里没细说的实战经验项目能跑通不算完真正考验你水平的是出问题了怎么排查。这一节我把JavaWeb最常见的三类异常按完整排查链路写出来你看看有没有命中过。5.1 404、500、ClassNotFoundException的排查顺序Web应用报错先分大类404请求的地址在服务器上不存在。按顺序排查URL路径里的Application context有没有写对比如之前配了/myapp但URL忘记带web.xml或WebServlet里映射的路径是否和URL一致Servlet类是否编译后被放到了WEB-INF/classes对应的包目录下项目是否成功部署到了Tomcat。500服务端代码运行时出错了。此时IDEA控制台或Tomcat日志里一定有堆栈信息。先看最底下的Caused by那是根本原因。常见的有NullPointerException某个对象是null常见于参数没传到Servlet、ClassNotFoundExceptionjar包缺失检查依赖、NoSuchMethodErrorjar包版本冲突。启动时就报错那就不是你的代码问题了大概率是Tomcat配置坏了或者项目里的web.xml写错。看启动日志里第一次出现ERROR的位置。我处理项目报错有一个固定的排查顺序先看部署有没有成功再看URL映射对不对最后才看代码逻辑。很多时候部署这一步就错了后面全白搭。怎么确认部署成功看Tomcat启动日志找一行Deployment of web application archive [xxx.war] has finished。如果没看到看error级别日志比如Failed to deploy。5.2 数据库连接失败从时区到权限一网打尽数据库连不上报错通常是Communications link failure或Access denied for user。前者是网络/连接层面的问题后者是账号权限问题。先看URL写法。MySQL 8.x要求驱动是com.mysql.cj.jdbc.DriverURL里必须带时区参数比如serverTimezoneAsia/Shanghai。如果你代码里写的是com.mysql.jdbc.Driver在MySQL 8.x下会直接报错因为那个旧驱动已经移除了。再看MySQL服务有没有启动。Windows下你在任务管理器看看MySQL服务Linux下systemctl status mysqld。最朴实的方法是命令行直接连一下mysql -u root -p能连上说明数据库正常问题出在Java代码里。Access denied for user的话按这个思路查-- 查看所有用户和host SELECT user, host, authentication_string FROM mysql.user; -- 给指定host授权%代表所有主机 GRANT ALL PRIVILEGES ON javaweb_demo.* TO root% IDENTIFIED BY 123456; FLUSH PRIVILEGES;还有一个很隐蔽的坑数据库连接串里的端口写错了。MySQL默认3306但你在服务器上可能装了Docker版MySQL端口映射成了33061这时候Java代码连3306自然连不上。5.3 字符编码问题一个看不见的坑页面显示乱码、插入数据库中文变问号这两类问题折磨了无数新手。根源是编码在四个环节里不一致客户端页面 - HTTP请求 - Java字符串 - 数据库存储。解决方案要四管齐下JSP页面顶部加% page contentTypetext/html;charsetUTF-8 languagejava %Servlet里在读取参数前执行req.setCharacterEncoding(UTF-8)设置响应编码用resp.setContentType(text/html;charsetUTF-8)数据库连接URL加useUnicodetruecharacterEncodingutf8建表时指定CHARSETutf8mb4这四个环节只要有一个不一致就可能出乱码。我之前的经验页面乱码多半是JSP或Servlet响应编码问题数据库里变问号多半是连接串或建表字符集问题提交的参数在Servlet里就是乱码那大概率是req.setCharacterEncoding(UTF-8)这行没写或者顺序错了必须在getParameter之前调用。顺便提一句Tomcat 8及以后版本POST请求的默认编码才是UTF-8GET请求的编码取决于Tomcat的URIEncoding配置。如果GET请求中文乱码可以在Tomcat的conf/server.xml里给Connector加上URIEncodingUTF-8。这是很多人查了半天查不出来的原因。6. 我踩过几次坑之后的几点体会文章写到这里该讲的都讲了。最后分享几个我个人在反复练习JavaWeb项目时总结的体会不一定都在书本上但确实实用。第一做项目别追求大而全。网上随便搜一下就有号称几十万行代码的案例但那种项目你根本看不完也消化不了。我建议就做用户管理这种经典模块做完一个再把分页、过滤器、文件上传、AJAX、Redis会话共享一个个往里加每加一个功能你的知识树就长出一根新枝。第二遇到报错先读日志不要急着问人。很多人一看到Exception就复制粘贴到搜索引擎但你自己能读日志的话往往花两分钟就能定位。记住一个原则堆栈信息从下往上读先看Caused by再看是本项目的哪一行代码抛出来的。排查过程本身就是最有价值的练习。第三配置类的坑解决一次就够了。像IDEA运行配置、Tomcat乱码、MySQL时区这些属于一次配置永久受益的内容。建议把你遇到的所有坑记录下来包括报错信息、原因、解决办法。下次再遇到类似问题翻自己的笔记比重新搜索快得多。我电脑里就有一份自己积累的JavaWeb踩坑记录.md已经写了几百条现在遇到多数问题都能在五分钟内解决。第四别忽略基础工具。有些同学连curl、Postman都不太用全程靠浏览器点。其实这些工具在排查问题时非常好用——用curl可以模拟任意请求头用Postman可以保存各种接口测试。花一个小时学会它们后面能省几十个小时。JavaWeb是整个Java后端开发的地基地基不牢后面学Spring、SpringBoot、微服务都会很虚。希望这篇笔记能帮你把IDEA运行配置MySQL接入完整案例这条线打通。等你把这个用户管理模块跑通了再回头看那些热搜词里的问题你会发现其实没当初想的那么难。