简介面向JavaWeb初学者的MVC增删改查完整项目源码基于JSPServlet三层架构演示数据管理流程适合正在学习Servlet、JSP及JDBC开发的读者。压缩包共40个文件约920KB包含9个JSP视图页面、9个Java控制类及对应class编译文件另有XML配置、SQL脚本等辅助资源可完整查看页面层与后端逻辑的对应关系。已有3753人学习下载。项目附带shopping.sql数据库脚本导入后即可运行体验重点展示新增记录、删除数据、修改字段和条件查询四大功能帮助读者理解从JSP表单提交、Servlet请求分发到JDBC执行SQL的完整调用链路同时体会MVC模式如何通过模型层封装数据库操作、视图层呈现数据是结合理论练习与代码参照的实用学习资料。1. 一个 javaweb 增删改查源码包值得你花一个晚上把它跑通“javaweb 增删改查含源码.zip”这类压缩包是后端入门最常见的练手资源一个能导入 IDEA 直接跑的 Web 项目自带数据库脚本、页面和后端代码跑起来就是对一张表做增删改查。我第一次拿到这类包时以为解压就能看界面结果花了一晚上配 Tomcat、换 MySQL 驱动、调中文编码才在浏览器里看到列表页。这个标题真正解决的是拿到别人写好的 javaweb 增删改查源码后怎么让它在本机跑通并且看懂浏览器、Servlet、DAO、MySQL 这条链路的每一环。适合课程设计、刚学完 Servlet 找完整案例对照、以及接手老项目需要快速摸清结构的开发者。2. 拆开 zip 之前先看清这套增删改查用什么技术栈在跑2.1 JSP Servlet JDBC为什么这类 javaweb 项目完整案例还在用这套组合这类 zip 的解压结构五花八门但九成能跑的项目都沿着同一个骨架JSP 负责页面Servlet 负责接收请求和跳转JDBC 负责操作 MySQLTomcat 负责把组件拉起来。这个组合在 Spring Boot 普及后看起来老但胜在链路短、依赖少、和教材同步。我见过不少课程设计交的就是这个结构评审老师也最容易接受。它的价值在于把 Web 开发的骨架暴露得很直白一个 HTTP 请求从 URL 到数据库每一层只有一个类断点打进去就能看到数据怎么流。相比之下 SSM 虽然更接近生产环境但容器、代理、配置多出几倍对“跑通增删改查”这个目标来说反而容易被框架细节盖住。注意标题里的“源码”并不代表这是多新的项目。多数这类包是 2018 到 2021 年之间的写法JDK 1.8 Tomcat 8.5 MySQL 5.7 是匹配度最高的运行环境后面章节会反复回到这套组合。2.2 解压后按顺序看这四个文件pom.xml、web.xml、数据库脚本、README拿到 zip 第一件事不是找启动脚本而是看目录结构。常见结构大概长这样javaweb-crud/ ├── README.md # 运行说明数据库账号、部署顺序 ├── pom.xml # Maven 依赖清单也可能没有 ├── db/ │ └── user.sql # 数据库初始化脚本 └── src/main/ ├── java/com/example/crud/ │ ├── dao/UserDao.java # 数据访问层写增删改查 SQL │ ├── entity/User.java # 实体类对应一张表 │ ├── servlet/UserServlet.java # 请求入口 │ └── util/DBUtil.java # JDBC 连接工具类 └── webapp/ ├── WEB-INF/web.xml # Servlet 映射和欢迎页配置 ├── list.jsp # 列表页 ├── add.jsp # 新增表单页 └── edit.jsp # 编辑表单页这段目录结构基本覆盖了这类项目的共同骨架。pom.xml存在说明是 Maven 工程IDEA 导入时选择“作为 Maven 项目打开”依赖会自动下载没有也无所谓传统做法是直接把 jar 包放进WEB-INF/lib。web.xml负责 Servlet 映射Tomcat 8/9 支持WebServlet注解老代码则依赖web.xml如果两种并存以web.xml为准。db/user.sql是数据库脚本必须先导入否则启动不报错、一查数据就空。README 经常被忽略但它通常写了数据库账号、Tomcat 版本、需要改哪些配置我一般第一步就看它这能省出半小时试错时间。2.3 选型边界JDBC 直连和 MyBatis/MyBatis-Plus 的改造成本差多少拿到源码后很多人第一反应是“要不要换成 MyBatis”。我的建议是先把原版跑通再考虑替换因为两套方案的复杂度差距不小对比项JDBC 直连MyBatis / MyBatis-Plus依赖数量一个 mysql-connector jarmybatis、spring-jdbc 等多个 jar上手难度低SQL 写在 Java 里中要理解 XML 或注解映射增删改查写法每个表写一套 PreparedStatement继承 BaseMapper 后自带通用 CRUD课程设计接受度最常见也可以但需要讲清框架选型JDBC 直连的好处是你能在代码里看清 SQL 和参数的对应关系出了问题可以直接在 MySQL 里把 SQL 复制出来跑一遍。对新手来说这比对着 MyBatis 的 mapper XML 猜映射更直接。标题里的“含源码”通常也指的是这种完整可读的写法而不是封装好的框架壳子。3. 数据库与核心代码把增删改查四个动作写进 DAO 层3.1 建表 SQL 与连接参数utf8mb4 和时区两个小细节增删改查落到数据库层面就是 INSERT、SELECT、UPDATE、DELETE 四条 SQL。先看初始化脚本这类项目通常围绕一张业务表展开比如用户表CREATE DATABASE javaweb_crud DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE javaweb_crud; CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, user_name VARCHAR(50) NOT NULL, password VARCHAR(50) NOT NULL, email VARCHAR(100) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段脚本有两点值得注意一是库和表都指定utf8mb4MySQL 8 之前的utf8实际是utf8mb3存不了生僻字和 Emoji统一用utf8mb4能从源头避开一类乱码问题二是表名user虽然不是 MySQL 保留字但和系统库里的mysql.user同名我一般会加上反引号user防止其他工具解析时报错。字段方面id用自增主键InnoDB 引擎这些都是源码包里的普遍写法不用大改。连接数据库的工具类 DBUtil 是另一个关键文件常见写法如下package com.example.crud.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/javaweb_crud ?useUnicodetruecharacterEncodingutf8 useSSLfalse serverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这段代码里URL 的后缀参数是踩坑重灾区。useUnicodetruecharacterEncodingutf8保证发给 MySQL 的 SQL 按 UTF-8 编码serverTimezoneAsia/Shanghai是给 MySQL 8 新驱动用的不加容易报时区异常allowPublicKeyRetrievaltrue解决 MySQL 8 默认认证插件导致的连接报错。驱动类名也要注意MySQL 8 的驱动类改成了com.mysql.cj.jdbc.Driver老代码写com.mysql.jdbc.Driver在 MySQL 8 下会直接 ClassNotFound。3.2 DAO 层四个方法参数顺序、返回值设计决定了代码好不好改DAOData Access Object是这套代码的核心层。以 UserDao 为例最值得抄的是 findAll 和 add 两个方法public ListUser findAll() { ListUser list new ArrayList(); String sql SELECT id, user_name, password, email FROM user ORDER BY id; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { User u new User(); u.setId(rs.getInt(id)); u.setUserName(rs.getString(user_name)); u.setPassword(rs.getString(password)); u.setEmail(rs.getString(email)); list.add(u); } } catch (SQLException e) { e.printStackTrace(); } return list; } public int add(User user) { String sql INSERT INTO user(user_name, password, email) VALUES(?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, user.getUserName()); ps.setString(2, user.getPassword()); ps.setString(3, user.getEmail()); return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } return 0; }这里有两个设计习惯值得沿用。第一SQL 全部用PreparedStatement的?占位符不拼接字符串避免 SQL 注入也省去手动处理单引号转义的麻烦第二新增和修改这类写操作返回int表示受影响行数调用方通过返回值判断成功还是失败比返回 boolean 信息量更大。try-with-resources写法会自动关闭 Connection、PreparedStatement、ResultSet老代码里的手动 close 容易出现连接泄漏遇到就顺手改掉。update 和 delete 的结构完全一致只是 SQL 不同写的时候注意参数顺序和 WHERE 条件的 id 要放对位置就行。3.3 Servlet 层转发、重定向与中文编码三个分工DAO 写完需要一个入口让浏览器请求到达 DAO。这类项目最常用的做法是一个 UserServlet 统一接收action参数区分不同操作WebServlet(/user) public class UserServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); String action req.getParameter(action); if (edit.equals(action)) { int id Integer.parseInt(req.getParameter(id)); req.setAttribute(user, userDao.findById(id)); req.getRequestDispatcher(/edit.jsp).forward(req, resp); } else if (delete.equals(action)) { int id Integer.parseInt(req.getParameter(id)); userDao.delete(id); resp.sendRedirect(req.getContextPath() /user); } else { req.setAttribute(userList, userDao.findAll()); req.getRequestDispatcher(/list.jsp).forward(req, resp); } } }这段逻辑里有两个关键动作转发和重定向。转发用getRequestDispatcher().forward()发生在服务端内部浏览器地址栏不变适合把查询结果带给 JSP 渲染重定向用sendRedirect()会让浏览器重新发起一次请求适合删除、新增、修改这类写操作避免用户刷新页面时重复提交。编码方面每个 Servlet 方法开头都要设置请求和响应编码否则后面加中文参数时很容易乱码。对应的列表页 list.jsp 核心片段大概是% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table border1 trthID/thth用户名/thth邮箱/thth操作/th/tr c:forEach items${userList} varu tr td${u.id}/td td${u.userName}/td td${u.email}/td td a href${pageContext.request.contextPath}/user?actioneditid${u.id}编辑/a a href${pageContext.request.contextPath}/user?actiondeleteid${u.id} onclickreturn confirm(确定删除吗)删除/a /td /tr /c:forEach /table这里最容易翻车的是链接路径。写a href/user?actionedit在部署上下文不是根路径时会 404必须用${pageContext.request.contextPath}拼出上下文路径也就是第四章要配好的 Application context。4. IDEA 运行 javaweb 项目的完整配置从导入 zip 到 Tomcat 启动4.1 两种导入方式Maven 工程和非 Maven 工程的处理差别拿到 zip 后先在 IDEA 里用 Open 打开解压目录。如果根目录有pom.xmlIDEA 会识别成 Maven 工程右下角弹出依赖导入提示等它把 jar 下载完即可。如果没有pom.xml需要手动处理新建一个空 Java Enterprise 工程把源码里的src/main/java和src/main/webapp内容分别拷贝到对应目录再把WEB-INF/lib下的 jar 重新加进模块依赖。这个步骤容易漏的是WEB-INF/lib因为 IDEA 不会自动把普通文件目录变成依赖需要右键 lib 目录 → Add as Library。对非 Maven 老项目我见过最多的失败原因就是 lib 目录没生效代码编辑器里不报错一运行 Tomcat 就 ClassNotFoundException。遇到这种情况优先确认部署产物的WEB-INF/lib里是否有对应的 jar。4.2 Tomcat 与 Artifact上下文路径决定了 404 还是 200运行 javaweb 项目前需要先配置 Tomcat 这个容器。在 IDEA 里走 Run → Edit Configurations → 左上角加号 → Tomcat Server → Local如果列表里没有 Tomcat 选项先在 Plugins 里确认 Tomcat 插件已启用。Server 页填写 Tomcat 安装目录、JRE 版本和 HTTP 端口默认 8080。关键在 Deployment 页。需要把当前 Web 项目添加为 Artifact并设置 Application context比如/javaweb-crud。这个值就是浏览器访问时的第一段路径例如访问http://localhost:8080/javaweb-crud/list.jsp。如果你发现页面一直 404先看两处是否一致地址栏里的路径和 Deployment 页里的 Application context。还要记得在启动前手动 Build → Build Artifacts → Rebuild否则改过的代码不会进部署产物。少数使用 Maven 的源码包会在 pom.xml 里配 Tomcat 插件这时命令行启动更简单plugin groupIdorg.apache.tomcat.maven/groupId artifactIdtomcat7-maven-plugin/artifactId version2.2/version configuration port8080/port path/javaweb-crud/path uriEncodingUTF-8/uriEncoding /configuration /plugin然后在项目根目录执行mvn tomcat7:run。这个插件的名字虽然叫 tomcat7实际能跑多数 javaweb 老项目。path就是上下文路径uriEncoding必须设成 UTF-8否则 Tomcat 解析中文请求参数时可能乱码。这种方式的好处是绕开 IDEA 的 Artifact 配置适合排查“IDEA 里跑不起来但不确定是代码还是配置问题”的情况。4.3 数据库前置检查先命令行验证再让项目连库跑项目之前我习惯先用命令行验证数据库本身是通的而不是等 Tomcat 报了错才回头查mysql -uroot -p123456 -h127.0.0.1 --default-character-setutf8mb4 use javaweb_crud; source /path/to/user.sql; select * from user;这段命令做了四件事连接 MySQL、切换数据库、执行建表脚本、查询一次数据。如果select能返回空表说明数据库侧没有问题了后面的问题集中在代码和 Tomcat 配置。--default-character-setutf8mb4很重要它保证命令行客户端和 MySQL 服务端的字符集一致避免手动插入测试中文数据时变问号。驱动 jar 方面MySQL 8 对应的是mysql-connector-java-8.0.x.jar里面同时兼容com.mysql.cj.jdbc.Driver。如果源码包里只有旧的 5.1.x jar可以先替换 jar再改 DBUtil 里的驱动类名和 URL 参数这两处不匹配项目必然在获取连接时报错。5. 避坑源码跑不通八成是 Tomcat、驱动和编码在打架5.1 现象Tomcat 启动成功页面却 404这是最挫败的场景控制台一堆启动日志浏览器打开却是一整页 404。原因通常有两个一是 IDEA 的 Artifact 没有真正部署二是访问路径和应用上下文不匹配。解决时先看 Deployment 页确认 Artifact 已添加再看地址栏是否带着上下文路径。我排查时会直接在浏览器输入http://localhost:8080/javaweb-crud/list.jsp如果还 404就去 Tomcat 的 webapps 目录看有没有生成对应的部署文件夹。老项目里还有一个小概率原因web.xml 里配置的欢迎页是index.jsp但这个文件根本不存在。5.2 现象ClassNotFoundException: com.mysql.jdbc.Driver这个报错十有八九是 MySQL 版本和驱动类名不匹配。MySQL 8.0 之后驱动类名变成了com.mysql.cj.jdbc.Driver而老源码里普遍写的是com.mysql.jdbc.Driver。解决方法是二选一把 DBUtil 的Class.forName改成新类名同时把驱动 jar 换成 8.x 版本或者把数据库降级到 MySQL 5.7继续沿用旧驱动。我建议直接升到 MySQL 8因为本机数据库通常不只服务这个项目新版驱动也修复了老版本的一些连接问题。5.3 现象插入中文后数据库里显示问号中文乱码是 javaweb 项目最经典的坑本质是链路里某一环的编码不是 UTF-8。至少四个地方要统一JSP 页面开头的pageEncodingUTF-8、Servlet 里的request.setCharacterEncoding(UTF-8)、Tomcat 的server.xml里 Connector 加URIEncodingUTF-8、JDBC URL 里带characterEncodingutf8。如果四选一漏了表现可能是列表页正常但新增乱码或者新增正常但编辑乱码。我一般会把上述四处全部改完再跑一次而不是只改一处反复试。数据库表字符集也要用utf8mb4这在一开始的建表脚本里已经提到。5.4 现象form 表单提交后 404但列表页打开正常增删改查里的“增”和“改”都依赖 form 提交。老源码里的 form 或链接如果写死了/user在部署上下文为/javaweb-crud时会错位因为请求实际去了http://localhost:8080/user而不是http://localhost:8080/javaweb-crud/user。解决方式是所有页面内链接都加上${pageContext.request.contextPath}前缀Servlet 重定向时用req.getContextPath()拼接。这也是我拿到源码后最先全局搜索的地方搜索action/和href/基本能定位全部问题点。5.5 现象MySQL 8 连接时报 Public Key Retrieval is not allowed这个报错只出现在 MySQL 8 新驱动组合下。MySQL 8 默认使用caching_sha2_password认证插件非 SSL 连接下驱动需要先请求公钥但又默认不允许这个动作于是报错。解决最快的方式是在 JDBC URL 追加两个参数allowPublicKeyRetrievaltrueuseSSLfalse。如果项目用的是连接池参数同样要加在数据源 URL 里。改完重启 Tomcat问题一般就消失。这个坑和代码逻辑无关纯粹是新版本驱动在使用习惯上的变化。6. 跑通之后这样验收四类操作各做一遍再看把 DAO 换成通用 CRUD项目跑起来不代表完事我习惯按真实用户路径验收一遍先在新增页提交一条含中文的数据回列表页确认这条数据出现再点编辑确认表单回显了刚才填的内容改一个字段保存列表页同步变化最后点删除确认弹窗确认后列表少了一条。到这一步程序的“增删改查”闭环才算真正验证完。接着去 MySQL 命令行执行select * from user;对比页面显示和数据表内容确认编码、主键自增都正常。这四步里任何一步卡住都值得回到对应章节排查。验收通过后如果想把这份 javaweb 源码做得更接近生产环境我建议从 DAO 层下手。JDBC 直连的代码里每个实体都要重复写增删改查的 PreparedStatement模板化严重。可以引入 MyBatis-Plus让 Mapper 接口直接继承 BaseMapper获得现成的通用 CRUD 方法import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface UserMapper extends BaseMapperUser { }这样selectById、insert、updateById、deleteById这些无状态增删改查方法都不用手写 SQL主键由框架自动填充。但我建议不要在大作业里直接替换先把它作为一个独立分支做等熟悉了项目的原有逻辑再合并。我的习惯是拿到任何源码包第一件事永远是看 README 和数据库脚本先跑通再改代码这个顺序能少走一半弯路。折腾一晚上之后你会发现这套看似老的 javaweb 增删改查反而是理解 Web 项目生命周期最好的教材。希望帮到你。本文还有配套的精品资源点击获取