简介:面向JavaWeb初学者的MVC增删改查完整项目源码,基于JSP+Servlet+三层架构演示数据管理流程,适合正在学习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 负责操作 MySQL,Tomcat 负责把组件拉起来。这个组合在 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 jar | mybatis、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) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这段脚本有两点值得注意:一是库和表都指定utf8mb4,MySQL 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" + "?useUnicode=true&characterEncoding=utf8" + "&useSSL=false" + "&serverTimezone=Asia/Shanghai" + "&allowPublicKeyRetrieval=true"; 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 的后缀参数是踩坑重灾区。useUnicode=true&characterEncoding=utf8保证发给 MySQL 的 SQL 按 UTF-8 编码;serverTimezone=Asia/Shanghai是给 MySQL 8 新驱动用的,不加容易报时区异常;allowPublicKeyRetrieval=true解决 MySQL 8 默认认证插件导致的连接报错。驱动类名也要注意:MySQL 8 的驱动类改成了com.mysql.cj.jdbc.Driver,老代码写com.mysql.jdbc.Driver在 MySQL 8 下会直接 ClassNotFound。
3.2 DAO 层四个方法:参数顺序、返回值设计决定了代码好不好改
DAO(Data Access Object)是这套代码的核心层。以 UserDao 为例,最值得抄的是 findAll 和 add 两个方法:
public List<User> findAll() { List<User> 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;charset=UTF-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 prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <table border="1"> <tr><th>ID</th><th>用户名</th><th>邮箱</th><th>操作</th></tr> <c:forEach items="${userList}" var="u"> <tr> <td>${u.id}</td> <td>${u.userName}</td> <td>${u.email}</td> <td> <a href="${pageContext.request.contextPath}/user?action=edit&id=${u.id}">编辑</a> <a href="${pageContext.request.contextPath}/user?action=delete&id=${u.id}" onclick="return confirm('确定删除吗?')">删除</a> </td> </tr> </c:forEach> </table>这里最容易翻车的是链接路径。写<a href="/user?action=edit">在部署上下文不是根路径时会 404,必须用${pageContext.request.contextPath}拼出上下文路径,也就是第四章要配好的 Application context。
4. IDEA 运行 javaweb 项目的完整配置:从导入 zip 到 Tomcat 启动
4.1 两种导入方式:Maven 工程和非 Maven 工程的处理差别
拿到 zip 后,先在 IDEA 里用 Open 打开解压目录。如果根目录有pom.xml,IDEA 会识别成 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> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/javaweb-crud</path> <uriEncoding>UTF-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-set=utf8mb4 use javaweb_crud; source /path/to/user.sql; select * from `user`;这段命令做了四件事:连接 MySQL、切换数据库、执行建表脚本、查询一次数据。如果select能返回空表,说明数据库侧没有问题了,后面的问题集中在代码和 Tomcat 配置。--default-character-set=utf8mb4很重要,它保证命令行客户端和 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 页面开头的pageEncoding="UTF-8"、Servlet 里的request.setCharacterEncoding("UTF-8")、Tomcat 的server.xml里 Connector 加URIEncoding="UTF-8"、JDBC URL 里带characterEncoding=utf8。如果四选一漏了,表现可能是列表页正常但新增乱码,或者新增正常但编辑乱码。我一般会把上述四处全部改完再跑一次,而不是只改一处反复试。数据库表字符集也要用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 追加两个参数:allowPublicKeyRetrieval=true&useSSL=false。如果项目用的是连接池,参数同样要加在数据源 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 BaseMapper<User> { }这样selectById、insert、updateById、deleteById这些无状态增删改查方法都不用手写 SQL,主键由框架自动填充。但我建议不要在大作业里直接替换,先把它作为一个独立分支做,等熟悉了项目的原有逻辑再合并。我的习惯是拿到任何源码包,第一件事永远是看 README 和数据库脚本,先跑通再改代码,这个顺序能少走一半弯路。折腾一晚上之后你会发现,这套看似老的 javaweb 增删改查,反而是理解 Web 项目生命周期最好的教材。希望帮到你。
本文还有配套的精品资源,点击获取