
简介一份面向Java Web初学者的车辆管理系统课程设计源码包基于JSPServletMaven搭建以MySQLcar_data库、fixed_car表为数据存储实现车辆颜色、车主、品牌、进出场时间等信息的登记与管理覆盖典型增删改查场景适合作为Java Web课程设计或毕业设计参考。包内共70个文件大小5.67MB主要包含12个JSP页面、4个Java核心类、24个XML配置/构建文件、8个class编译产物以及6个jar依赖库等结构清晰便于直接导入IDEA运行和学习。附带项目说明文档详细讲解数据库建库建表及初始数据插入方法可帮助快速搭建运行环境。目前已有381人学习使用。对于正在完成Java Web课程设计、或想通过真实项目提升Servlet与JSP实战能力的读者这份源码提供了完整可运行的参考实现和排错思路值得动手拆解。1. 用 Java 实现车辆管理系统课程设计真正考的是这四件事大多数人拿到“基于 Java 的车辆管理系统”这个题目第一反应是启动 IDE、建库、写登录页、拉一个列表出来三天后系统能跑但答辩时被问到“车辆状态为什么不用字符串”“出车单状态流转怎么防止跳变”“删一条车辆记录要不要连带删维保”就卡住了。课程设计和毕业设计不一样后者看重研究深度前者看重完整走一遍 Web 应用的建模、分层、联调和交付流程——功能可以简单流程不能缺。所以这个标题真正考验的是四件事业务模型画得准、技术栈选得合理、核心流程扛得住并发追问、交付物里数据库脚本和项目说明能让人照着重现。本文就用最常见的“企业车队 出车审批”模型从建表到部署自查完整走一遍参数、代码、踩坑点都放在对应章节里。2. 先分清业务模型再建表车辆管理系统的 ER 设计与状态机2.1 车辆管理系统到底管哪几类“车辆”“车辆管理系统”这个题目在不同场景下差得非常大。租车公司背景的车辆管理系统核心实体是车辆、客户、订单关注车辆在途时间和计费4S 店背景的车辆管理系统核心实体是车辆档案、维保记录、保险到期关注全生命周期成本企业车队背景的车辆管理系统核心实体是车辆、驾驶员、出车申请、审批流关注用车审批和里程统计。同一个标题背后可以是三种完全不同的数据库设计。课程设计里最推荐的是企业车队模式因为它的业务规则最像真实系统有角色区分管理员审核、普通员工申请、有状态流转申请、批准、出发、归还天然对应 Java Web 里的登录权限、事务控制和列表筛选。租车计费要处理金额、押金、违约金容易把表结构撑大4S 店维保要处理配件、工时容易陷入细节。先定模型再建表这一步省下来的时间后面都会在联调和答辩里体现出来。2.2 核心表设计从车辆档案到维保、出车记录以车队模式为例全系统只需要四张核心表加一张字典表就能覆盖 80% 的课程设计要求。车辆表存静态档案用户表管登录和角色出车记录表是业务主表维保记录表补充全生命周期数据。下面是可直接执行的 MySQL 建表脚本CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL COMMENT 建议存 MD5 或 BCrypt 后的密文, real_name VARCHAR(50) NOT NULL, role TINYINT NOT NULL DEFAULT 1 COMMENT 0管理员 1普通员工, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE InnoDB DEFAULT CHARSET utf8mb4; CREATE TABLE vehicle ( id INT PRIMARY KEY AUTO_INCREMENT, plate_no VARCHAR(10) NOT NULL UNIQUE COMMENT 车牌号如 京A12345, brand VARCHAR(50) NOT NULL COMMENT 品牌型号, seat_count TINYINT NOT NULL DEFAULT 5, buy_date DATE NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0空闲 1出车中 2维修中 3停用, current_mileage DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 当前总里程(公里) ) ENGINE InnoDB DEFAULT CHARSET utf8mb4; CREATE TABLE dispatch_record ( id INT PRIMARY KEY AUTO_INCREMENT, vehicle_id INT NOT NULL, driver_id INT NOT NULL COMMENT 关联 sys_user 的司机, apply_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, expected_return DATETIME NULL COMMENT 预计归还时间, start_mileage DECIMAL(10,2) NULL COMMENT 出发里程, end_mileage DECIMAL(10,2) NULL COMMENT 归还里程, status TINYINT NOT NULL DEFAULT 0 COMMENT 0申请中 1已批准 2已出发 3已归还 4已驳回, remark VARCHAR(255) NULL, INDEX idx_vehicle_id (vehicle_id), INDEX idx_driver_id (driver_id), INDEX idx_status (status) ) ENGINE InnoDB DEFAULT CHARSET utf8mb4; CREATE TABLE maintenance_record ( id INT PRIMARY KEY AUTO_INCREMENT, vehicle_id INT NOT NULL, maintenance_type TINYINT NOT NULL COMMENT 0保养 1维修 2年检, cost DECIMAL(10,2) NULL, mileage_at_repair DECIMAL(10,2) NULL COMMENT 保养时里程, next_maintenance DATETIME NULL COMMENT 下次保养时间, remark VARCHAR(255) NULL, INDEX idx_vehicle_id (vehicle_id) ) ENGINE InnoDB DEFAULT CHARSET utf8mb4;字段设计里有几个点要说明白。车牌号用 VARCHAR(10) 而不是 CHAR(8)因为新能源车牌是 6 位字符加上省字和字母会超过 8 位但也不能无限制加长10 位足够覆盖蓝牌和绿牌。车辆状态用 TINYINT 注释说明不要用字符串因为代码里用枚举或常量比对时数字的性能和维护成本都优于字符串。所有表的主键都选自增 INT课程设计的数据量和并发量不需要 UUID。时间字段这里特意全部用了 DATETIME没有用 TIMESTAMP。原因是 TIMESTAMP 的范围只到 2038 年而且会受 MySQL 时区参数影响同学在 Windows 和 Linux 环境之间迁移数据库时经常出现“时间差 8 小时”的诡异问题。DATETIME 不依赖时区存的是什么显示的就是什么配合 Java 侧 LocalDateTime 使用最省心。创建时间统一给 DEFAULT CURRENT_TIMESTAMP插入时少写一个字段。2.3 状态机设计业务约束比字段约束更关键四张表建好后真正的设计难点在字段之间的状态约束。车辆状态和出车单状态必须联动否则会出现车显示空闲、但库里挂着一条未归还的出车单的脏数据。正确流转路径只有一条申请通过 - 车辆状态改为 1(出车中)创建出车单状态 2(已出发) 归还登记 - 更新里程和归还时间出车单状态改为 3(已归还) - 车辆状态改回 0(空闲)这张状态流转表是答辩时必被问到的设计点。评审常问“你怎么保证一辆车不会被两个人同时申请”答案不在 Java 代码的 if 判断里而在 SQL 的条件更新中。后面第四章会专门给出并发控制的写法。设计层面还需要约定驳回申请时车辆状态不动只有“已批准”这个动作才改车辆状态维修和年检不走进出车表直接在 vehicle.status 上改 2同时追加一条维保记录。3. Java Web 技术栈选型Servlet JSP 还是 Spring Boot MyBatis3.1 课程设计场景下的选型判断做技术选型前先问两个问题一是课程上有没有指定必须用 JSP二是这个源码未来会不会继续演进成毕业设计。这两个问题的答案直接决定技术栈。如果课程要求是“基于 Java Web 实现”且指定了 Servlet JSP那就老老实实走传统路线Servlet 控制请求转发、JSP 渲染页面、JDBC 访问数据库。这个方案的好处是每一层都看得见摸得着答辩时可以把请求从浏览器到数据库的完整路径画在黑板上。缺点是代码量大列表页加一个筛选条件要改 Servlet、JSP、DAO 三个地方。如果技术栈自由Spring Boot MyBatis 是更稳妥的选择。它的分层更清晰事务注解、依赖注入、数据源配置都是行业标准写法面试官和答辩老师看到熟悉的技术栈也更容易沟通。下面是两种方案的对比对比项Servlet JSP JDBCSpring Boot MyBatis代码量多DAO 层要手写少Mapper 接口 XML事务控制手写 Connection 管理Transactional 注解页面技术JSP 标签库可继续用 JSP也可用 Thymeleaf答辩友好度请求流转路径直观分层规范贴近企业开发踩坑风险JDBC 连接泄漏、JSP 乱码依赖版本冲突、包扫描遗漏选择建议没人检查框架、且想给源码多留扩展空间直接用 Spring Boot MyBatis老师明确要看 JSP 页面用 Spring Boot 内嵌 JSP 也能做但打包必须用 war 包并配置 jasper 依赖这里不再展开。无论选哪条工程结构都要按 Maven 标准来建不能是 Eclipse 里一堆散落的 class 文件。3.2 Maven 工程的最小骨架与分层一个课程设计的 Maven 工程目录结构按下面的方式组织就足够vehicle-manager/ ├── pom.xml ├── src/main/java/com/example/vehicle/ │ ├── controller/ # 控制层接收请求、校验参数 │ ├── service/ # 业务层事务边界在这里 │ ├── mapper/ # MyBatis Mapper 接口 │ ├── entity/ # 数据库实体类 │ ├── dto/ # 页面传参对象 │ └── common/ # 常量、工具类、全局异常处理 ├── src/main/resources/ │ ├── mapper/ # MyBatis XML 文件 │ ├── application.yml │ └── sql/ # 建库建表脚本 └── src/main/webapp/ └── WEB-INF/jsp/ # JSP 页面pom.xml 按 Spring Boot 2.7.x MySQL 8 MyBatis 组合配这套组合的文档最多、遇到错误时最容易搜到答案。用 3.x 也可以但需要 JDK 17 起步很多课程设计还在用 JDK 8环境变量配的是 Java 8就会出现“明明安装了 JDK 却启动报错”的问题。这就是热词里 java 环境变量配置相关的坑先确认java -version输出的版本和 IDE 里 Project Structure 选的是同一个 JDK。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency /dependencies依赖里注意 mysql-connector-j 是最新的坐标名老的mysql-connector-java已迁移。application.yml 里配置数据源和 MyBatis 时最容易出问题的是 mapper-locations 路径写错导致启动时报Invalid bound statement。真实项目中我一般这样写spring: datasource: url: jdbc:mysql://localhost:3306/vehicle_db?characterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.vehicle.entitycharacterEncodingutf8解决中文乱码serverTimezoneAsia/Shanghai解决驱动连接时报时区错误。MyBatis 的type-aliases-package让 XML 里写 resultType 时可以只写类名不用全限定名这一步漏了会编译不报错、运行报错。3.3 登录会话与角色权限的最小实现车辆管理系统必须有登录功能而且必须有角色区分普通员工能申请出车、查看自己的出车记录管理员能审批、管理车辆档案、查看所有人记录。这个权限控制在 Spring Boot 里有多种实现课程设计不需要引入 Spring Security用拦截器就能讲清楚原理也更容易答辩。Component public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 允许未登录访问登录页和静态资源 String uri request.getRequestURI(); if (uri.contains(/login) || uri.contains(/css) || uri.contains(/js)) { return true; } Object user request.getSession().getAttribute(loginUser); if (user null) { response.sendRedirect(/login); return false; } return true; } }登录成功后把用户对象放进 session拦截器每次请求校验 session 里有没有 loginUser 就能挡住未登录访问。再进一步管理员接口单独校验角色在 controller 方法里取 session 判一下 role 字段即可。注意前端根据角色隐藏按钮只解决体验问题不解决安全问题真正的权限校验必须放在后端。答辩时被问到“权限是怎么实现的”就按这个逻辑回答会话保持登录态拦截器做统一入口校验角色字段做访问授权。4. 核心编码与排错时间查询、并发更新和事务边界4.1 车辆列表查询的 SQL 拼接与参数校验车辆列表是入口页面筛选条件通常有三个车牌号模糊查询、车辆状态下拉、购买日期区间。用 MyBatis 的动态 SQL 写法是最干净的方式select idselectVehicleList resultTypecom.example.vehicle.entity.Vehicle SELECT * FROM vehicle where if testplateNo ! null and plateNo ! AND plate_no LIKE CONCAT(%, #{plateNo}, %) /if if teststatus ! null AND status #{status} /if if testbeginDate ! null AND buy_date gt; #{beginDate} /if if testendDate ! null AND buy_date lt; #{endDate} /if /where ORDER BY id DESC /select时间区间的结束条件用的是小于号而不是小于等于表示“小于下一天”。比如查询 2024 年 1 月的数据结束日期传 2024-02-01SQL 条件就是buy_date 2024-02-01正好包含 1 月 31 日的数据。如果传 2024-01-31 再配小于等于日期格式带不带时分秒会导致结果不一致这是最常见的坑。动态 SQL 里的if teststatus ! null要配合 Java 参数的 Integer 类型页面下拉框未选择时 Controller 不把空串转成 0否则会查询出所有 status0 的数据。我的做法是 DTO 里用 Integer 接收页面空值传 null避免“清空筛选条件后列表少数据”的假 Bug。4.2 出车审批和归还登记条件更新解决并发校验很多同学的实现是三步走先查车辆状态在 Java 里判断是不是 0再执行 UPDATE 改成 1。这个写法在两套数据下没问题但两个人同时点击审批通过时后一个请求读到的是旧状态会把同一辆车批给两个人。解决方式很简单把状态校验放进 UPDATE 语句的 WHERE 条件里。Transactional(rollbackFor Exception.class) public void approveDispatch(Integer recordId, Integer vehicleId) { // 条件更新只有车辆当前是空闲(0)才允许更新为出车中(1) int rows vehicleMapper.updateStatusByOldStatus(vehicleId, 1, 0); if (rows ! 1) { throw new BusinessException(车辆已被占用审批失败); } dispatchRecordMapper.updateStatus(recordId, 1); }对应 SQL 是UPDATE vehicle SET status #{newStatus} WHERE id #{id} AND status #{oldStatus}。updateStatusByOldStatus的影响行数只有 0 或 10 说明车辆状态已经不是空闲直接抛异常回滚。这样从数据库层面保证了并发安全不用加锁课程设计的数据量下性能也足够。归还登记的流程同理更新出车单状态和归还里程、更新车辆的当前里程、把车辆状态改回 0。三步操作在一个事务里Transactional(rollbackFor Exception.class)保证中间任何一步失败都回滚。注意 rollbackFor 不能省Spring 默认只回滚 RuntimeException如果抛的是自定义 Exception 子类事务不会回滚会导致车辆状态改了但出车单没更新。4.3 常见运行时异常的三类定位手段Java Web 项目运行时异常再多归纳起来就三类掌握了这类报错的定位方式后排查速度能快不少。数据库连接类报错最常见的是java.sql.SQLException: Access denied for user和CannotGetJdbcConnectionException。前者查 yml 里的用户名密码注意 MySQL 8 默认的认证插件是 caching_sha2_password驱动版本必须是 8.0.x后者查 MySQL 服务有没有启动、端口是不是 3306、URL 里的 serverTimezone 有没有配置。日期转换类报错集中在java.time.format.DateTimeParseException页面传字符串到后台Spring 转换 LocalDateTime 失败。这是因为日期格式不匹配。前端传yyyy-MM-dd后端用 LocalDateTime 接收中间缺一个格式化配置。处理方式是前端传yyyy-MM-dd HH:mm:ss或者在后端 DTO 字段上统一加DateTimeFormat(pattern yyyy-MM-dd HH:mm:ss)。第三类是 MyBatis 特有的Invalid bound statement (not found)这个报错九成是 XML 文件没被扫描到。检查三处application.yml 里 mapper-locations 路径是否匹配 resources/mapper 目录、XML 的 namespace 是否是 Mapper 接口的全限定名、接口方法名和 XML 里的 id 是否一致。以上三类都排查完还没解决再去看控制台完整堆栈不要只看第一行。5. 提交前最后一遍SQL 日志、测试数据与答辩自查5.1 开启动态 SQL 日志验证每一条 UPDATE课程设计最后阶段最快的调试手段是把 MyBatis 执行的 SQL 打印到控制台。配置只有一行logging: level: com.example.vehicle.mapper: debug加了这条配置后每次请求都会打印对应的 SQL 语句和参数列表。重点看两类 SQL一类是出车审批时的 UPDATE 影响行数控制台会显示 Updates: 1如果这里显示 0 说明条件更新没匹配到数据Java 里抛的异常就是对的另一类是列表查询时的 WHERE 条件拼接能直观看到动态 SQL 是否拼接了空的筛选条件。做完这个验证删库重跑一遍初始化脚本再完整走一遍“登录→申请出车→审批→归还”链路。这条链路里的每一步都会产生状态变化是测试接口和页面交互最核心的业务流。5.2 项目交付压缩包前检查这三个细节压缩包命名是“Web应用开发技术课程设计-基于Java实现的车辆管理系统项目源码项目说明.zip”那“项目说明”这部分一定要写到能让人按照步骤从零跑起来。项目说明文档里必须包含三块内容环境版本JDK 8 或 17、MySQL 8、Maven 3.8、初始化步骤创建数据库、执行 SQL 脚本、修改 yml 密码、初始账号管理员和测试员工的用户名密码写清楚。缺任何一块评审第一手操作就跑不起来。最后一个建议在项目说明里专门开一节写“测试数据说明”写清楚造了哪些数据、为什么造这些数据。示例口径是“测试车辆共 8 辆覆盖空闲、出车中、维修中三种状态出车记录 20 条覆盖本月、上月、去年三个时间段方便验证默认查询、时间筛选和统计报表”。这个口径在答辩被问到“数据哪来的”时就是最好的答案。把这些细节都补齐打 zip 包这个 Java 车辆管理系统项目才算真正闭环。本文还有配套的精品资源点击获取