做JavaWeb项目,很多人一上来就想着Spring Boot、微服务,反而把最基础的东西丢了。其实用JSP + Servlet + JDBC + MySQL写一个共享租车信息管理系统,才是真正能把JavaWeb整条调用链吃透的方式。这套技术组合看起来传统,但它恰好把HTTP请求、Servlet生命周期、JSP页面渲染、JDBC连接数据库、SQL设计、事务控制全部串了一遍。这个系统解决的是共享租车场景里最核心的问题:用户注册登录、浏览车辆、下单租车、还车结算、后台管理车辆和订单。如果你是做课程设计、毕业设计,或者刚学完Servlet想找一个完整项目练手,这个项目非常适合拿来当参考模板。下面我把整个项目的设计思路、数据库建模、环境配置、核心代码实现和排查记录一次性写清楚。
1. 项目整体设计与技术选型解析
1.1 为什么选 JSP+Servlet+JDBC 这套组合
先聊一个很多人会纠结的问题:现在Spring Boot这么火,为什么还要用JSP+Servlet这种“老古董”?
我的答案是:因为这套组合能让你看到JavaWeb最真实的运行过程。JSP负责页面展示,Servlet负责接收请求和调度,JDBC负责操作数据库,MySQL负责存储数据。这四样东西组合起来,就是最朴素的MVC三层架构:JSP是View,Servlet是Controller,JDBC操作DAO层是Model的一部分。没有框架帮你遮遮掩掩,一个请求从浏览器发出,经过Tomcat找到对应的Servlet,Servlet再调用JDBC查询数据库,最后把结果通过转发或重定向送回页面,每一步都可以在代码里找到对应位置。
这就像学车先学手动挡,虽然换挡踩离合很麻烦,但你能理解汽车是怎么运作的。等以后再学Spring MVC、MyBatis,你反而会觉得轻松,因为你知道框架底层在帮你做什么。所以我个人强烈建议:毕设或课程设计选这种传统技术栈,不是落后,而是最能体现基础掌握程度。而且这类项目网上参考资料多,部署包小,运行环境也好配。
1.2 系统功能模块拆解
共享租车信息管理系统,核心业务其实不复杂:平台上有车辆,用户注册登录后可以租车、还车,管理员负责维护车辆和处理订单。
我把功能拆成两个端,方便后面做数据库设计和代码分层。
| 模块 | 功能点 | 说明 |
|---|---|---|
| 用户端 | 注册、登录、个人信息查看 | Session保存登录状态,密码需要加密存储 |
| 用户端 | 车辆列表浏览、关键字查询 | 按车型、品牌、日租金筛选 |
| 用户端 | 下单租车、还车结算 | 核心流程,涉及事务控制 |
| 用户端 | 我的订单、取消订单 | 订单状态流转 |
| 管理端 | 账号登录、权限校验 | 区分管理员和普通用户 |
| 管理端 | 车辆信息管理 | 增删改查,上下架,维护状态 |
| 管理端 | 订单管理 | 查看所有订单,执行审核、完成操作 |
| 管理端 | 用户管理 | 查看用户列表,禁用异常账号 |
功能不算多,但覆盖了一个JavaWeb项目该有的全部环节:表单传参、文件上传(车辆图片可选)、列表分页、条件查询、事务操作、Session会话管理、Filter过滤器(登录验证)、Listener统计在线人数等基础能力。等会儿我会重点讲几个关键实现,因为这些都是面试和答辩时最容易被打听的地方。
2. 数据库设计与核心表结构
2.1 MySQL 建模思路
这次项目用的数据库是MySQL,版本我推荐8.0,后面会专门讲驱动问题。先聊设计思路。
共享租车场景涉及三个核心实体:用户、车辆、订单。用户和车辆之间是“多对多”关系,一个用户能租多辆车,一辆车也会被多个用户租过,但这个多对多通过订单表关联起来了。所以最核心的表就是这三张:用户表(t_user)、车辆表(t_car)、订单表(t_order)。
另外,为了区分普通用户和管理员,我最初的想法是建一个用户表加一个role字段,但这样在代码里需要自己判断角色。更清晰的做法是再加一张管理员表(t_admin),不过从权限设计角度,统一用用户表加角色字段其实更合理,适合后期扩展。我最后采用了“用户表 + 角色字段”的方式,表结构清爽,也方便以后对接更复杂的权限系统。
2.2 核心表结构设计
下面是我实际建表用的SQL关键片段,删减了注释,方便直接看字段。
用户表:
CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(64) NOT NULL COMMENT '建议MD5或SHA-256后存储', phone VARCHAR(11) DEFAULT NULL, role TINYINT DEFAULT 1 COMMENT '0表示管理员,1表示普通用户', status TINYINT DEFAULT 1 COMMENT '1正常,0禁用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP );车辆表:
CREATE TABLE t_car ( id INT PRIMARY KEY AUTO_INCREMENT, car_no VARCHAR(20) UNIQUE NOT NULL COMMENT '车牌号', brand VARCHAR(50) NOT NULL, model VARCHAR(50), price_per_day DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 1 COMMENT '1可用,2已租出,3维修,4下架', create_time DATETIME DEFAULT CURRENT_TIMESTAMP );订单表:
CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) UNIQUE NOT NULL, user_id INT NOT NULL, car_id INT NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, total_price DECIMAL(10,2) DEFAULT 0.00, status TINYINT DEFAULT 1 COMMENT '1已下单,2租赁中,3已还车,4已取消', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user_id (user_id), KEY idx_car_id (car_id), CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES t_user(id), CONSTRAINT fk_order_car FOREIGN KEY (car_id) REFERENCES t_car(id) );有几个设计细节值得说明:
- 金额字段用DECIMAL而不是Float或Double。凡是涉及钱的都要用定点数,避免浮点数精度丢失。这是Finance业务的基本常识,放到租车项目里同样适用。
- 状态字段用TINYINT存数字,代码里定义一个常量类或枚举去对应,不要直接在JSP里写死数字,否则后期维护很痛苦。
- 订单号order_no用UUID或时间戳+随机数生成,不用数据库自增主键直接给用户看,主要出于安全考虑,也方便未来对接第三方支付。
2.3 表关系与索引设计要点
外键约束在互联网公司实际生产环境里经常被禁用,但学习项目里保留外键有好处,能理清关系,防止插入脏数据。我在这个项目里保留了两条外键:订单表关联用户表和车辆表。这样在删除用户或车辆时,数据库层面就能阻止误删。
不过要注意,外键会带来额外的数据库开销,而且对插入性能有影响。所以我在订单表里额外手动建立了index_user_id和index_car_id,因为列表查询经常按user_id查“我的订单”,按car_id查“该车辆使用记录”。如果数据量增大,这个索引就是命根子。
另外,车辆列表页经常按status筛选可用车辆,所以在车辆表的status字段也加了一个普通索引。这些设计在答辩时讲出来,面试官会认为你真的设计过系统,而不是简单建了几张表。
3. 环境配置与项目运行实操
3.1 JDK、Tomcat、MySQL 版本搭配
环境配置是新手最容易卡住的地方,我先把推荐组合列出来,这些我都实测过。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 | JavaWeb项目最稳定的版本 |
| IDEA | 2022+ | 自带Maven和Tomcat集成 |
| Tomcat | 9.0 | 兼容Servlet 4.0,JSP支持好 |
| MySQL | 8.0 | 新版驱动类名和时区配置需要注意 |
| Maven | 3.6+ | 用Maven创建war项目,管理依赖更方便 |
如果你喜欢用MySQL 5.7,那驱动用5.x系列也行,但网上教程混杂,容易踩坑。个人感觉直接上MySQL 8.0更省心,只要注意驱动名称和URL参数就好。
3.2 IDEA 创建 JavaWeb 项目和导入依赖
推荐用Maven结构创建JavaWeb项目,IDEA里选择“Java Web”骨架,或者直接创建普通Maven项目后加上war包插件。关键是在pom.xml里配置下面几个依赖:
<!-- Servlet API,Tomcat容器提供,所以scope用provided --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- JSP API,同样由容器提供 --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!-- JSTL标签库,方便JSP中使用forEach等标签 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- MySQL 8连接器 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>Maven会帮你自动下载依赖,但有时IDEA下载依赖会失败,尤其是公司网络受限的时候。遇到“Cannot download from Maven Central”这类问题,我一般先把IDEA的Maven仓库镜像改成阿里云镜像,再在File -> Settings -> Build Tools -> Maven里设置好settings.xml。改完基本就顺了。
3.3 JDBC 连接 MySQL 的完整配置与常见坑
项目里我一般会把数据库连接信息放到db.properties配置文件中,然后写一个工具类统一获取连接。这样避免代码里到处写死数据库地址。
db.properties:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/car_share?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456工具类简化版:
public class DBUtil { private static String url; private static String user; private static String password; static { try { InputStream in = DBUtil.class.getClassLoader() .getResourceAsStream("db.properties"); Properties props = new Properties(); props.load(in); Class.forName(props.getProperty("jdbc.driver")); url = props.getProperty("jdbc.url"); user = props.getProperty("jdbc.username"); password = props.getProperty("jdbc.password"); } catch (Exception e) { throw new ExceptionInInitializerError("数据库配置加载失败"); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } }几个必须要注意的坑:
- MySQL 8的驱动类名是
com.mysql.cj.jdbc.Driver,不是5.x的com.mysql.jdbc.Driver。如果看到ClassNotFoundException,十有八九是版本和类名不匹配。 - URL里一定要加
useSSL=false。MySQL 8默认开了SSL,本地开发环境没有证书,不加这个参数会报Communications link failure或SSL connection error。 - 加
serverTimezone=Asia/Shanghai,否则会因为时区差异报The server time zone value '???ú±ê׼ʱ¼ä' is unrecognized,尤其是当时数据库是默认时区的时候。 - 中文乱码问题,URL里需要
characterEncoding=utf8,并且后续会结合Filter统一设置请求编码。
MySQL的安装配置也是热词里很多人卡的地方。Windows下安装MySQL 8,我用的是免安装版ZIP,解压后配置my.ini,然后以管理员身份执行:
mysqld --initialize-insecure net start mysql如果net start mysql提示服务无法启动,最常见的两个原因是my.ini路径配置错误、以及data目录没有初始化。用mysqld --initialize-insecure初始化后再启动基本就能解决。
4. 核心功能实现详解
4.1 Servlet 控制器设计与请求流转
传统JavaWeb项目,Servlet负责接收请求。我不建议一个功能写一个Servlet类,否则类会爆炸。我的做法是在一个模块下用一个Servlet类,通过action参数区分操作。
比如用户相关的UserServlet:
@WebServlet("/user") public class UserServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String action = request.getParameter("action"); if ("login".equals(action)) { login(request, response); } else if ("register".equals(action)) { register(request, response); } else if ("logout".equals(action)) { logout(request, response); } // ... 更多操作 } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } private void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); User user = userService.login(username, password); if (user != null && user.getStatus() == 1) { request.getSession().setAttribute("loginUser", user); response.sendRedirect(request.getContextPath() + "/index.jsp"); } else { request.setAttribute("errorMsg", "用户名或密码错误,或账号已被禁用"); request.getRequestDispatcher("login.jsp").forward(request, response); } } }这里有个细节:doGet里直接doPost,这样无论用户用GET还是POST访问同一个逻辑都走同一种编码设置。如果你只重写doPost,用GET方式提交表单会空白,这是个很常见的低级错误。
4.2 JSP 页面渲染与表单交互
JSP页面里我强烈建议使用EL表达式和JSTL,而不是直接在写<% Java代码 %>。作为早期JavaWeb项目,JSP中夹杂大量脚本代码会让页面非常难维护。
车辆列表展示核心片段:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <table> <tr> <th>车牌号</th> <th>品牌</th> <th>车型</th> <th>日租金</th> <th>状态</th> <th>操作</th> </tr> <c:forEach items="${carList}" var="car"> <tr> <td>${car.carNo}</td> <td>${car.brand}</td> <td>${car.model}</td> <td>${car.pricePerDay}</td> <td> <c:choose> <c:when test="${car.status == 1}">可租</c:when> <c:when test="${car.status == 2}">已租出</c:when> <c:otherwise>维修</c:otherwise> </c:choose> </td> <td> <a href="${pageContext.request.contextPath}/car?action=detail&id=${car.id}">详情</a> <a href="${pageContext.request.contextPath}/order?action=rent&carId=${car.id}">租车</a> </td> </tr> </c:forEach> </table>页面里所有链接都要加上${pageContext.request.contextPath},也就是项目的上下文路径。否则部署到Tomcat后,如果路径带了项目名,你的所有静态资源、超链接都会404。这是我做项目时踩过最多的坑。
关于热词里提到的“jsp页面让加载完后刷新一次”,其实很多人遇到的问题是:提交表单后按F5刷新,浏览器弹出“确认重新提交表单”的对话框,导致重复下单。正确做法是在Servlet处理完业务后使用重定向而不是转发:
// 错误:转发会让刷新时重新执行表单提交 request.getRequestDispatcher("orderList.jsp").forward(request, response); // 正确:Redirect After Post 模式,刷新只重新请求列表页 response.sendRedirect(request.getContextPath() + "/order?action=myOrders");重定向的原理是浏览器先收到一个302响应,然后自动发出新的GET请求。用户刷新时刷新的是GET请求的页面,订单不会重复插入,这个技巧非常实用。
4.3 JDBC 增删改查与事务处理
JDBC的基础功能是增删改查,网上有大量模板。但租车下单这个业务必须使用事务,这是整个项目中最核心、最值得写的部分。
租车流程简单说有三步:
- 检查车辆状态是否可用;
- 创建订单,状态设为“已下单”;
- 把车辆状态改成“已租出”。
如果第2步成功了,第3步失败,那么会出现“有订单但车辆仍可租”的数据不一致情况。解决方式就是事务。
public boolean createOrder(int userId, int carId) { Connection conn = null; PreparedStatement ps1 = null; PreparedStatement ps2 = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 查询车辆并锁定行,防止并发下订单 String checkSql = "SELECT status FROM t_car WHERE id = ? FOR UPDATE"; ps1 = conn.prepareStatement(checkSql); ps1.setInt(1, carId); rs = ps1.executeQuery(); int status = -1; if (rs.next()) { status = rs.getInt("status"); } if (status != 1) { conn.rollback(); return false; } // 2. 插入订单 String insertOrder = "INSERT INTO t_order (order_no, user_id, car_id, start_time, status) VALUES (?,?,?,NOW(),1)"; ps2 = conn.prepareStatement(insertOrder); ps2.setString(1, generateOrderNo()); ps2.setInt(2, userId); ps2.setInt(3, carId); ps2.executeUpdate(); // 3. 更新车辆状态 String updateCar = "UPDATE t_car SET status = 2 WHERE id = ?"; ps2 = conn.prepareStatement(updateCar); ps2.setInt(1, carId); ps2.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } throw new RuntimeException("创建订单失败", e); } finally { DBUtil.close(conn, ps1, rs); DBUtil.close(null, ps2, null); } }这里最关键的是第1步里的SELECT ... FOR UPDATE。这是一个悲观锁,在事务提交之前,其他事务执行同样的查询会阻塞等待。如果不用这个锁,两个用户同时请求同一辆车,可能会都查询到“可用”,然后都创建订单,最终车辆状态被后来的更新覆盖,但库里出现了两笔成功订单。这个并发问题在答辩时讲出来,老师会觉得你确实理解数据一致性。
还车结算稍微简单一点:根据当前时间和订单里start_time计算天数,乘以金额,更新总费用,并把车辆状态改回“可用”,订单状态改成“已还车”。这里同样需要使用事务,道理一样。
5. 常见问题与排查技巧
5.1 问题速查表
我把实际运行过程中遇到的典型问题整理成一张速查表,基本覆盖了所有新手会踩的坑。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 浏览器访问localhost:8080白屏或404 | 项目没部署成功,或访问路径缺少项目名 | 检查IDEA中Artifacts是否已部署,访问http://localhost:8080/项目名/ |
| Tomcat启动报端口被占用 | Tomcat的8080端口被其他程序占用 | 命令行`netstat -ano |
ClassNotFoundException: com.mysql.jdbc.Driver | 驱动jar包版本不对或加载类名写错 | MySQL8使用com.mysql.cj.jdbc.Driver,检查Maven依赖是否真正下载成功 |
SSL connection error | MySQL8默认开启SSL,但本地没有配置证书 | JDBC URL加useSSL=false |
| 插入中文数据后乱码 | 数据库、表、连接URL编码不一致 | 统一使用UTF-8,连接URL加characterEncoding=utf8,Tomcat配置URIEncoding |
| 前端页面表单提交后刷新重复下单 | 用了forward转发,导致POST请求被重复提交 | 改用response.sendRedirect重定向,也就是PRG模式 |
Access denied for user 'root'@'localhost' | 数据库密码错,或MySQL8默认root密码策略 | 确认密码,或通过ALTER USER重置密码 |
jsp页面里=null直接显示 | 后端某个字段赋值空值 | 在JSP用<c:if>或${empty xxx}做判空 |
| mysql服务无法启动 | my.ini配置错误或data目录未初始化 | 删除data目录,执行mysqld --initialize-insecure,再用net start mysql |
5.2 Filter 过滤器与登录状态校验
用户和管理员有些页面需要权限控制。我在项目里写了一个LoginFilter,拦截所有.jsp和Servlet请求,把不需要过滤的页面(登录页、注册页、车辆列表等)排除掉。
@WebFilter("/*") public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; String uri = req.getRequestURI(); if (uri.endsWith("login.jsp") || uri.contains("/user?action=login") || uri.contains("register.jsp") || uri.endsWith(".css") || uri.endsWith(".js") || uri.endsWith(".png")) { chain.doFilter(request, response); return; } HttpSession session = req.getSession(); Object loginUser = session.getAttribute("loginUser"); if (loginUser == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp"); return; } chain.doFilter(request, response); } }注意Filter的执行顺序以及在web.xml里配置的顺序,如果用了@WebFilter注解,顺序一般按类名排序,不稳定。我习惯在web.xml里手写Filter配置,顺序可控。
5.3 三个独家避坑技巧
第一个技巧:每次修改Java代码或配置文件后,一定要重建并重启Tomcat。IDEA有时只是简单地热更新class,但配置文件不一定生效。我踩过最惨的一次是改了db.properties里密码后没有重启,结果一直报数据库密码错误,排查了半小时。
第二个技巧:JSP页面调试时如果报500,先去看Tomcat的日志,不要只在浏览器里刷新。IDEA控制台底部Tomcat Localhost Log和Catalina Log里会有完整异常堆栈。很多时候是空指针或者是SQL语法问题,一看堆栈就知道。
第三个技巧:数据库连接千万别忘记关闭。我最初开发时写了个DAO方法没关ResultSet,连续跑了几次测试后数据库连接数直接满了,Tomcat卡死老半天。后来我在DBUtil里统一封装了close方法,所有DAO都用同一个关闭工具类,才彻底杜绝资源泄漏。
6. War 包打包与部署经验
6.1 传统 JSP 项目如何打包 War
课程设计或小组作业经常需要把项目部署到别的电脑上。传统JSP项目打包成war其实很简单,Maven项目直接执行:
mvn clean package然后target目录下会生成xxx.war。把这个war文件丢到Tomcat的webapps目录下,启动Tomcat后,war会自动解压部署。如果用的是IDEA的Artifacts,也可以手动添加Web Application Archive构建,效果一样。
打包之前记得检查pom.xml里有没有加打包插件。如果不加,Maven默认只能打jar,这个jar无法直接被Tomcat运行。
6.2 数据库初始化脚本与一键部署思路
为了让项目在另一台电脑上快速跑起来,我写了一个init.sql脚本,包含建库建表语句和基础测试数据。部署时只需要先安装MySQL,然后用命令执行:
mysql -u root -p < init.sql然后在db.properties里改好数据库连接信息,启动Tomcat就能用了。
我也建议把MySQL的字符集、排序规则在创建数据库时指定:
CREATE DATABASE IF NOT EXISTS car_share DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;使用utf8mb4而不是utf8,是因为utf8mb4才能完整存储某些生僻字和表情符号,虽然租车系统的车辆备注里一般不会用到,但这是好习惯。
7. 后续扩展与优化方向
把基础功能跑通之后,这个项目的可扩展性其实很强。我自己接下来想去加的东西有这么几个方向。
第一个是数据源连接池。现在每次获取连接都用DriverManager直接创建一个新连接,在高并发下性能很差。切换到Druid或HikariCP连接池,配置一个数据源,性能会有肉眼可见的提升。而且连接池还能监控慢SQL,非常实用。
第二个是密码加密。现在用户表里存的密码还是MD5加密后的结果,但其实MD5不够安全,推荐用BCrypt或SHA-256加盐。虽然这个项目只是课设,但养成安全意识很重要。
第三个是把车辆列表的分页和查询优化。现在如果车多,一次性查出来页面加载会慢。我打算改成用LIMIT分页,查询条件用动态SQL拼接,再配一个PageBean类,这样系统就更有“成品”的样子。
第四个是前端升级。传统JSP页面确实不够美观,可以引入Bootstrap或者Vue + Element UI,把展示层做得更现代。JSP后端保持不动,前端通过Ajax请求Servlet返回JSON,接口风格会更清晰。
这些优化如果时间充裕,完全可以一个个往项目里加。每次加完一个功能,你会对整个JavaWeb体系理解更深一层。
8. 一些真实的项目心得体会
这个共享租车信息管理系统我从零开始,写到现在大概用了两周的业余时间。最大的心得是:别小看JSP+Servlet+JDBC这套老旧组合,它让我对HTTP请求的生命周期、Servlet容器的工作原理、JDBC的事务边界有了很扎实的认知。
我在自己电脑上跑通第一笔租车订单的时候,那种感觉比用Spring Boot做一百个接口还有成就感。因为每一步都是自己写的SQL,自己控制的连接,自己处理的异常。尤其是用SELECT ... FOR UPDATE解决并发下单的那一刻,我真正理解了为什么框架要封装那么多东西。
如果你现在正是被JavaWeb课设折磨的阶段,我的建议是不要一上来就复制网上的完整项目代码。先自己把数据库表建好,再从用户登录开始写,每一个功能能自己实现就自己实现,实在卡住了再去看参考写法。这个项目代码量并不大,认真写一遍,你对JavaWeb的掌握程度会上升一个台阶。
最后再分享一个调试小技巧:如果页面报500错误或者数据加载不出来,先看Tomcat的logs目录下的catalina.out文件,里面会有完整的异常堆栈。很多时候你甚至不用百度,看一眼第一行“Exception”就定位到问题了。遇到奇怪的数据库问题,就用Navicat或命令行客户端手动执行一遍SQL,看看是不是SQL本身的问题,分清楚是前端、后端还是数据库的问题,排查速度会快很多。