ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

JSP+Servlet+JDBC+MySQL租车管理系统课程设计完整开发实战

2026/10/1 3:24:09 拓冰建站 浏览量
JSP+Servlet+JDBC+MySQL租车管理系统课程设计完整开发实战 做课程设计选了这个题目前后忙了两周把租车系统从需求分析到部署上线完整走了一遍。这里把整个项目的结构、数据库设计、核心代码写法、常见坑全部整理出来给正在做类似项目的朋友一个可以直接参考的模板。先说这个系统的定位一个面向共享租车场景的后台信息管理系统核心技术栈是JSP Servlet JDBC MySQL。业务核心包括用户注册登录、车辆信息管理、租车订单流转、还车计费几大模块。适合JavaWeb课程设计、毕业设计参考也适合想动手把一个传统MVC项目从头到尾跑通的初学者。1. 项目整体设计与思路拆解1.1 为什么选JSPServlet这套技术栈现在JavaWeb项目很多直接用Spring Boot但课程设计和不少教学场景里JSPServlet依然是必修科目。原因很简单Servlet是JavaWeb一切框架的基础原型JSP本质上是被翻译成Servlet运行的把这套底层机制跑通了后面学Spring MVC会轻松很多ThreadLocal、过滤器、监听器、Session管理这些概念也都是在Servlet里第一次接触到的。我自己做这个项目最大的收获就是把Servlet的生命周期、请求转发与重定向的区别、Session与Cookie的作用范围这几个“必考题”真正搞清楚了。从开发角度讲JSPServlet的好处是结构直白。前端页面写在JSP里后台逻辑写在Servlet里数据库操作写在JDBC工具类里项目结构一眼到底出了问题也知道去哪查。相比Spring Boot全家桶动辄上千个依赖的状态这个传统栈跑起来的资源占用也小一台普通笔记本完全跑得动。1.2 需求分析与核心业务流程做租车管理系统前我没着急写代码先把业务跑了一遍。共享租车最核心的角色是两位普通用户和管理员。普通用户能做的事注册账号并登录浏览车辆列表按车型、租金、座位数筛选车辆查看车辆详情明确租车单价和押金下租车订单选择租赁天数查看个人订单历史确认还车并结算费用管理员能做的事登录后台管理界面车辆的添加、修改、上下架管理处理用户订单确认订单、标记已还车查看所有注册用户的信息整个系统的核心流程是一条租车链路用户注册 - 登录 - 浏览车辆 - 下单 - 管理员确认 - 用户取车 - 用户还车 - 按天结算费用。这个流程走一遍数据库表怎么设计页面之间怎么跳转Servlet要写几个基本就有数了。这里的关键点是“用户下单”和“管理员确认”这两个动作之间要通过订单状态字段做衔接而不是直接用数据物理删除来表达业务状态。1.3 项目目录结构与包分层设计代码组织方式直接决定这个项目后面好不好维护。我按照标准的三层结构来分包虽然代码量不算大但每一层的职责必须分清楚。entity包数据库实体类对应数据库表结构dao包数据访问层负责JDBC操作数据库的具体逻辑service包业务逻辑层处理具体业务规则与校验servlet包控制器层接收请求、分发任务、控制页面跳转filter包过滤器处理编码和登录拦截util包工具类例如数据库连接webapp下的jsp页面视图层只负责展示和收集表单数据分层的好处我在课程设计报告中写了整整两页但在实操中最大的体感是哪天想改计费规则只需要动service层哪张页面出了问题去JSP目录里找对应的文件就行不会出现一个Java文件两三千行、找Bug找到崩溃的情况。2. 数据库设计与核心表结构详解2.1 数据库设计的基本原则租车系统的表设计核心思路能总结为“用户-车辆-订单”三个实体外加一个用户与车辆之间的多对多关联记录也就是订单表。数据库我用的是MySQL 5.7也可以用8.0这套表结构两种版本都支持。在设计表时我把握了两个原则第一单表字段不能太多一个表只负责描述一个核心实体第二表与表之间的关联尽可能用ID而不在业务表里冗余大段文本。例如车辆表里放的是车辆类型ID字段而不是直接把“舒适型”“豪华型”这种中文长字符串直接嵌进去。好处是以后想加一个车辆类型只需要在类型表里加记录不需要改动每一辆车的数据。2.2 用户表与车辆表设计用户表是整个系统的基础设计字段如下id主键自增长username用户名password密码字段实际项目建议存加密后的哈希值phone手机号id_card身份证号create_time注册时间这里有个需要注意的细节密码字段不要设置成VARCHAR(20)因为即使用户只要求存6位密码一旦后面想改成加密存储字段长度不够会有大麻烦。至少要给VARCHAR(64)以上的冗余。车辆表围绕租车场景设计id主键car_name车辆名称car_type车辆类型plate_number车牌号daily_rent日租金使用DECIMAL(10,2)deposit押金金额status车辆状态0表示空闲1表示已出租2表示维修中img_url车辆图片地址description车况描述关于车辆状态字段我踩过一个坑一开始用了VARCHAR存中文“空闲”“出租中”后来业务多了发现问题很大。一方面中文占用的存储空间更多另一方面筛选车辆时写SQL特别别扭。改成0/1/2这种数字枚举后查询效率高代码里也只需要维护一个常量映射表。2.3 订单表与租车记录设计订单表是系统数据关系的核心连接了用户表和车辆表id主键order_no订单编号生成规则时间戳随机数user_id下单用户ID外键关联用户表car_id租用车辆ID外键关联车辆表rent_date取车日期return_date计划还车日期actual_return_date实际还车日期为空表明订单还在进行中total_price订单总金额status订单状态0待确认1租赁中2已完成3已取消create_time下单时间建议所有金额字段都用DECIMAL不要用FLOAT。FLOAT做累加计算时会产生小数精度误差租车金额涉及用户付款一两分钱的误差在测试阶段可能无所谓但放到真实业务场景就是事故。2.4 建表语句与初始化数据参考把完整的SQL建表语句写在这里可以直接拿去用。字符集我统一选utf8mb4因为要兼容车辆描述里面可能出现的中文标点和特殊字符。CREATE DATABASE IF NOT EXISTS car_rental DEFAULT CHARSET utf8mb4; USE car_rental; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, phone VARCHAR(20), id_card VARCHAR(18), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE t_car ( id INT PRIMARY KEY AUTO_INCREMENT, car_name VARCHAR(50) NOT NULL, car_type VARCHAR(20), plate_number VARCHAR(10) UNIQUE, daily_rent DECIMAL(10,2) NOT NULL, deposit DECIMAL(10,2) DEFAULT 0, status TINYINT DEFAULT 0, img_url VARCHAR(255), description TEXT ); CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id INT NOT NULL, car_id INT NOT NULL, rent_date DATE NOT NULL, return_date DATE NOT NULL, actual_return_date DATE, total_price DECIMAL(10,2), status TINYINT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES t_user(id), CONSTRAINT fk_order_car FOREIGN KEY (car_id) REFERENCES t_car(id) );初始化数据我准备了3辆车和1个管理员账号INSERT INTO t_car (car_name, car_type, plate_number, daily_rent, deposit, status, description) VALUES (大众朗逸, 舒适型, 京A12345, 280.00, 2000.00, 0, 2021年上牌自动挡油耗低), (丰田凯美瑞, 豪华型, 京B67890, 450.00, 3000.00, 0, 真皮座椅倒车影像), (别克GL8, 商务型, 京C11223, 600.00, 5000.00, 0, 7座商务适合全家出行); INSERT INTO t_user (username, password, phone) VALUES (admin, admin123, 13800000000);3. 核心技术点拆解JDBC、Servlet与JSP协作机制3.1 JDBC连接与数据库操作封装JDBC是访问数据库最底层的接口规范我们在代码里就是通过它来执行SQL。如果直接在每一个Servlet里写JDBC代码项目会变得异常臃肿因为要反复写“加载驱动、获取连接、创建Statement、执行SQL、关闭资源”这一整套流程。我封装了一个DBUtil工具类统一管理数据库连接。核心思路读取配置文件中的连接参数在静态代码块中加载驱动对外提供getConnection方法。public class DBUtil { private static String url jdbc:mysql://localhost:3306/car_rental?useSSLfalseserverTimezoneAsia/Shanghai; private static String user root; private static String password 123456; static { try { Class.forName(com.mysql.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (ps ! null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }这里特别提醒一个版本问题MySQL 5.7对应的驱动是com.mysql.jdbc.DriverMySQL 8.0之后驱动类名改成了com.mysql.cj.jdbc.Driver。很多人换到新版本数据库后报错ClassNotFoundException大多是这个原因。另外MySQL 8.0的连接URL中必须带上serverTimezone参数否则时区问题会直接报错。3.2 Servlet请求处理与页面跳转方式Servlet在整个系统里扮演控制器的角色。用户点击页面上的按钮浏览器就会向Servlet发送HTTP请求Servlet收到请求后调用Service层处理业务逻辑最后再把结果通过请求转发或重定向交还给页面。具体的写法是这样的WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); UserService userService new UserService(); User user userService.login(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); response.sendRedirect(carList); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }涉及页面跳转和应用状态管理的代码我的经验是如果登录成功后要避免用户刷新页面重复提交表单就用重定向如果登录失败后需要把错误信息回显在页面上就用请求转发因为转发能通过request作用域携带数据。关于路径问题Servlet里的相对路径和绝对路径很容易搞混。建议在JSP页面里统一使用${pageContext.request.contextPath}来拼接项目路径避免部署时因为应用名不同导致图片和链接全部破裂。3.3 JSP页面与Servlet的数据交互JSP页面做的是展示工作本身不负责业务逻辑。用户看到的车辆列表页面数据是怎么来的呢Servlet首先查询车辆列表把结果放进request作用域然后转发到list.jspJSP页面用JSTL标签和EL表达式把数据取出来循环展示。核心代码ListCar carList carService.getAllCars(); request.setAttribute(carList, carList); request.getRequestDispatcher(/carList.jsp).forward(request, response);carList.jsp的核心片段c:forEach items${carList} varcar div classcar-card h3${car.carName}/h3 p类型${car.carType}/p p日租金¥${car.dailyRent}/p a href${pageContext.request.contextPath}/carDetail?id${car.id}查看详情/a /div /c:forEach注意一点JSP页面中不建议写Java脚本片段也就是那些% %中的东西尽量只用在本命不该用的地方比如遍历可以用JSTL判断可以用EL表达式。脚本片段放到JSP里会让页面变得很难维护在课程设计答辩时这也是被老师重点追问的地方。3.4 会话管理Session与登录状态校验登录状态的管理是整个系统的重要环节。用户登录成功后我在Session中存入了user对象后续页面需要验证用户是否已经登录时只需要检查Session中是否存在该对象。写了一个LoginFilter统一做登录拦截WebFilter(/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); String path request.getRequestURI(); if (path.contains(login) || path.contains(register) || path.contains(.css) || path.contains(.js)) { chain.doFilter(req, resp); return; } if (session ! null session.getAttribute(loginUser) ! null) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() /login.jsp); } } }登录拦截的一个常见坑是静态资源也被拦截。CSS、JavaScript这些不需要登录就能访问的资源如果被拦截页面打开时样式会全部丢失登录页看起来就像被破坏了排版。在Filter里加白名单逻辑很关键。4. 核心功能模块实操实现4.1 注册登录模块的实现细节注册模块的逻辑相对简单但有几个点值得注意。注册时前端表单提交的用户名后端必须校验是否已存在密码建议用MD5加密后再存储不直接在数据库里保存明文。我在UserDao里写两个方法一个根据用户名查询用户一个插入新用户。public boolean register(User user) { String checkSql SELECT COUNT(*) FROM t_user WHERE username ?; String insertSql INSERT INTO t_user (username, password, phone, id_card) VALUES (?, ?, ?, ?); Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); ps conn.prepareStatement(checkSql); ps.setString(1, user.getUsername()); rs ps.executeQuery(); if (rs.next() rs.getInt(1) 0) { return false; } ps conn.prepareStatement(insertSql); ps.setString(1, user.getUsername()); ps.setString(2, MD5Util.md5(user.getPassword())); // 其他参数 return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } finally { DBUtil.close(conn, ps, rs); } }这里必须用PreparedStatement而不是Statement。一方面PreparedStatement有效防止SQL注入攻击另一方面预编译语句在多次执行时性能更好。传统的Statement拼接SQL字符串用户输入的内容很容易破坏SQL结构这在答辩中属于致命安全漏洞。4.2 车辆模块与列表查询车辆列表的展示是整个系统的门面。我在dao层封装了一个根据条件动态拼接SQL的查询方法核心代码public ListCar searchCars(String keyword, String status) { StringBuilder sql new StringBuilder(SELECT * FROM t_car WHERE 11 ); ListObject params new ArrayList(); if (keyword ! null !keyword.trim().isEmpty()) { sql.append(AND car_name LIKE ? ); params.add(% keyword %); } if (status ! null !status.isEmpty()) { sql.append(AND status ? ); params.add(Integer.parseInt(status)); } // 执行查询封装结果 }动态拼接SQL时在WHERE后面加上11是个常用的技巧这样后面所有条件都可以用AND连接不需要单独判断当前是不是第一个条件。这个写法能极大简化代码逻辑只要你清楚最终的SQL是交给数据库去解析执行的。4.3 租车下单与费用自动计算下单模块是整个系统的业务核心。用户选择车辆并填写租车天数后系统要根据日租金自动计算订单总额同时把车辆状态从“空闲”改为“已出租”。这个模块的代码涉及到事务操作因为有两个关键动作必须同时完成插入订单记录 更新车辆状态。如果插入订单成功但车辆状态更新失败系统就会面临“车辆已经租出去了但车还显示空闲”的数据不一致问题。因此需要在Service层加事务管理public boolean createOrder(Order order) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); OrderDao orderDao new OrderDao(); CarDao carDao new CarDao(); orderDao.insertOrder(conn, order); carDao.updateCarStatus(conn, order.getCarId(), 1); conn.commit(); return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }事务的隔离级别和回滚机制在课程设计的答辩中经常会被问到。这里的核心思路是先关闭自动提交所有数据库操作执行完毕后统一提交如果任何一个步骤抛出异常就调用rollback回滚到事务开始前的状态。4.4 还车结算与费用明细还车功能是租车流程的最后一步。管理员确认还车后系统要计算实际租车天数再结合日租金得出最终费用。租车天数按自然日计算我使用了java.time包里的Duration和ChronoUnitlong days ChronoUnit.DAYS.between(rentDate, actualReturnDate); if (days 0) days 1; BigDecimal totalPrice car.getDailyRent().multiply(BigDecimal.valueOf(days));这里有个隐藏问题很容易被忽略用户当天取车当天还车的情况Days计算出来是0但租车最少一天所以必须加一个“至少按一天计算”的判断条件。测试时我特意试了取还车日期相同的场景如果没有这个兜底逻辑价格计算直接变成0元。还车完成后还需要更新订单状态为“已完成”并把车辆的status置回“空闲”。这一步同样是事务操作跟前边的下单逻辑是一对对称关系。5. 开发环境搭建与部署运行5.1 开发环境准备与版本选择这套项目用到的开发环境我建议按下面的组合来搭兼容性最好JDK 1.8Servlet和JSP对JDK版本不高但JDK 8是JavaWeb项目最成熟的版本Tomcat 8.5或9.0直接用没有Bug的稳定版本IntelliJ IDEA社区版就够用MySQL 5.7或8.0Maven虽然是传统项目但我引入Maven做依赖管理省去了手动拷贝jar包到WEB-INF/lib的麻烦IDEA里创建Maven项目的骨架我选了maven-archetype-webapp这是一个标准的Webapp骨架生成目录后会自动得到src/main/webapp这个目录结构。相比手工搭建Web目录用骨架更不容易漏文件。5.2 数据库驱动下载与依赖配置很多新手在这个环节会卡住。传统JSP项目如果不用Maven需要去MySQL官网下载Connector/J的jar包然后手动放到WEB-INF/lib目录下。用Maven的话只需要在pom.xml里加一行依赖dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version /dependency如果你是MySQL 8.0把版本号改成8.0.33同时groupId会有一点变化。手动安装驱动包的朋友务必把jar包放进WEB-INF/lib目录这个目录下的jar包才会被Tomcat加载。放进普通lib目录而不在WEB-INF下即使本地成功连上数据库部署到Tomcat后仍然会报找不到驱动的错误。5.3 IDEA配置Tomcat并运行项目在IDEA里配置Tomcat是很多新手第一次会卡住的地方。流程是这样的点击Run菜单 - Edit Configurations - 左上角加号 - Tomcat Server - Local在Application server那里点击Configure选择Tomcat安装目录切到Deployment标签页点击加号选择ArtifactApplication context设置为/car_rental代表访问路径的前缀保存并点击运行按钮有一点要提醒IDEA的Tomcat配置里有个“On Update action”选项建议设置成Redeploy或Update classes and resources。这样修改了Java代码后在IDEA里按快捷键就能热部署不用每次手动重启Tomcat。我一开始不知道这个设置每次改一行代码就要重启整个服务器浪费了大量时间。5.4 项目打包与生产部署课程设计通常要求在本地环境运行演示但如果需要把项目部署到远程服务器上就要打WAR包。传统JSP项目的打包方式和Spring Boot不太一样。如果用的是Maven直接在Maven工具窗口执行package命令项目会被打成WAR包。然后把WAR包拷贝到Tomcat的webapps目录下启动Tomcat即可。Tomcat会自动解压WAR包访问路径就是WAR包的名字。如果没用Maven直接在IDEA里执行Build - Build Artifacts - car_rental:war - Rebuild效果一样。生产环境还要注意数据库配置问题。代码里数据库的地址是localhost:3306如果数据库在服务器上就要改成服务器的IP地址同时确保数据库允许远程连接。MySQL默认绑定了localhost需要修改my.cnf里的bind-address参数并授权远程访问用户这一步经常被忽略。6. 常见问题与排查技巧实录6.1 数据库连接失败的排查清单数据库连不上是整个系统最常出现的报错之一。不同表现对应不同的问题我整理了一个排查表报错信息可能原因解决办法ClassNotFoundException驱动jar包缺失或版本不匹配确认mysql-connector-java包在WEB-INF/lib中Communications link failure数据库服务没有启动服务里启动MySQL或命令行执行net start mysqlAccess denied for user用户名密码错误核对数据库账号密码Unknown database数据库名称错误确认数据库名称和代码中配置一致Server returns invalid timezoneMySQL 8.0时区问题URL加上serverTimezoneAsia/ShanghaiMySQL服务无法启动的坑我也碰上过。最常见的场景是安装MySQL时没设置密码或者安装路径有中文导致服务注册失败。这种情况下最直接的处理办法是检查MySQL安装目录下的data文件夹是否存在且为空然后以管理员身份运行命令行重新初始化数据目录并注册服务。6.2 中文乱码问题的三个层面中文乱码在传统JSP项目中几乎是100%会遇到的问题。乱码的本质是“编码和解码不对齐”解决乱码要从前端页面、后端代码、数据库连接三个层面分别检查。前端页面在JSP头部声明UTF-8% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%Servlet接收请求时设置请求编码request.setCharacterEncoding(UTF-8);数据库连接URL中指定字符集jdbc:mysql://localhost:3306/car_rental?useUnicodetruecharacterEncodingUTF-8这三个层面对齐后中文乱码问题基本不会再出现。如果还有乱码多半是Tomcat的server.xml里Connector的URIEncoding属性问题可以加一个URIEncodingUTF-8来解决GET请求的乱码。6.3 JSP页面报404或500的排查思路404和500是运行时最常见的两类错误但它们的含义完全不同。404的报错表示请求的资源不存在。排查思路检查URL是否和Servlet的WebServlet注解路径完全一致检查项目部署名是否正确检查WEB-INF目录下的web.xml配置是否有映射错误。爆一个典型的错误WebServlet(/login)写成(/Login)大小写敏感导致404找了很久才发现。500的错误表示服务器内部异常。最常见的原因JSP页面中的EL表达式写错、Servlet中调用了空对象的方法、数据库操作抛出SQLException。看到一个500报错第一时间去看IDEA的控制台日志堆栈信息会直接告诉你哪一行代码出了问题。6.4 Navicat连接MySQL常见问题很多同学用Navicat图形化工具操作数据库这里常遇到一个SSL连接报错。MySQL 5.7和8.0默认开启SSL配置Navicat连接时如果SSL配置不正确会报ssl连接错误。解决办法是在Navicat连接的高级设置选项卡里把“使用SSL”取消勾选。还有一种方式是给MySQL配置证书但对本地开发来说取消使用SSL是最快的方式。还有一个典型问题是Navicat连接不上刚安装好的MySQL报错里提到不能解析主机名或无法连接。通常是因为MySQL服务没启动先去服务里检查“MySQL”这个服务是否处于运行状态。很多情况下安装过程没有自动启动服务手动启动一下就好。6.5 常见错误的快速定位建议根据我整个开发过程中遇到的高频报错整理几条能快速定位问题的实用建议不要只看报错文字要看控制台的堆栈信息。第一次出现Exception时向下翻几行找到“at com.xxx.xxx.dao.xxxDao.method()”这种带包名的行那才是真正出错的业务代码位置。数据库相关报错在DAO层打印SQL语句。把拼出来的SQL复制到Navicat里手动执行一遍很多时候数据库报错信息比程序里报的信息更准确马上就能定位是字段名称写错还是数据类型不匹配。JSP报错时查看页面第一行的isErrorPage指令有没有设置。没有设置的话Tomcat返回500页面时只会显示一段苍白无力的错误描述根本看不出具体问题。7. 项目经验总结与优化方向这套租车管理系统开发下来我对JavaWeb的技术栈有了更深入的理解。做课程设计时着重做好以下几点会更出彩核心业务流程跑通、数据库设计合理、复用代码做好工具类封装、Filter统一鉴权、事务保证数据一致性。这些内容在答辩时如果被问起也都能说出设计理由来。从个人开发体验来看JSPServlet的项目业务逻辑复杂后会暴露出一些副作用JSP页面头部的include代码多、Servlet数量增长后页面跳转关系越来越难理清、JDBC操作代码里大量重复的try-catch资源关闭。这也是为什么Spring MVC、MyBatis这类框架能取代传统方式的原因。但如果把JSPServlet这套底层原理吃透了学习框架时你会发现它们本质上就是对Servlet机制的一次封装和解耦。给正在做的同学一个建议先别急着写代码。把数据库表建好把页面之间的跳转关系画清楚写出核心流程的顺序图之后再动手写Servlet和JSP整个开发过程会顺畅很多。我第一版做的时候先写了代码再建表结果发现字段缺一堆改来改去浪费了大量时间。这个项目后续想继续扩展的话有几个方向可以参考增加车辆图片上传功能、引入分页查询解决车辆多时的加载性能问题、使用连接池替换原生JDBC连接获取方式、密码改成加盐哈希存储。这些扩展点既是课程设计加分项也是后面学习进阶技术的一个自然过渡。