news 2026/8/31 2:57:07

基于Spring Boot + MyBatis的机票预订系统项目实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Spring Boot + MyBatis的机票预订系统项目实战解析

简介:这是一套面向Java初学者与高校数据库课程设计学生的实战型机票预订系统项目,聚焦Java桌面应用开发与MySQL数据库协同实践,完整覆盖用户注册登录、航班查询、座位选择、订单生成等核心业务流程。资源包共37个文件,含7个Java源码文件(如Care.java、Ticket.java等体现MVC分层逻辑)、7个编译后class文件、15个XML配置文件(支撑界面布局与数据绑定)、1份Word版项目说明书及1个SQL建表脚本,整体压缩后仅208KB,轻量易部署。已有8302人学习下载,适合用于课程设计答辩、期末实训或Java+MySQL综合能力提升。读者可直接运行调试,深入理解JDBC连接机制、GUI界面交互逻辑、第三范式数据库设计及常见并发订座控制策略,配套文档还详细说明了各模块职责与测试要点。 最近总有刚学完 Java 基础的朋友问我,说想找一个能写进简历、又不用太大工程量的练手项目。我基本回答都是同一个:做一个基于 Java + MySQL 的机票预订系统。为什么是它?因为一个成熟的机票预订系统,功能足够清晰,流程足够完整,又不会像电商秒杀那样把难度直接拉满。注册登录、航班查询、下单购票、订单管理、后台管理,这些链路几乎覆盖了 JavaWeb 开发里最常见的一整套知识点。

这篇文章我就用 Spring Boot + MyBatis + MySQL 这套组合,把整个项目的设计思路、数据库建模、后端实现、前端页面、部署步骤和踩坑记录完整走一遍。不管你是准备课程设计,还是想给简历加一个项目,都可以直接对照着做。我把每一步为什么这样做也讲清楚,方便你面试的时候能讲明白,而不是简历写了项目、一问细节就露馅。

1. 项目整体设计与技术选型思路

1.1 为什么我推荐拿机票预订系统练手

先说说为什么选机票预订系统,而不是图书管理系统、员工管理系统这类烂大街的项目。图书管理这类系统最大的问题是业务逻辑太薄,本质就是单表增删改查,做完之后对“业务设计”几乎没有感知。面试官问一句“你这个系统有什么难点”,你大概率只能回答“CRUD 比较多”,没有任何亮点。

机票预订系统不一样,它涉及库存、状态流转、乘客实名信息、并发购票这些真实业务点。你亲手写一次库存扣减和订单状态管理,再去看别的业务系统,会发现很多东西是相通的。比如下单时余票不够怎么办、两个人同时买最后一张票怎么办、订单支付超时要不要处理,这些问题都是可以在简历上写的真实业务难点。项目含金量一下就上来了。

还有一个很现实的原因:机票预订系统的数据关系非常典型,用户、订单、航班三张表之间是多对多、一对多的关系,练的就是关联查询和事务处理。这种表关联设计在真实项目中到处都是,你把这个项目吃透了,以后做其他管理系统的数据库设计,基本都能顺手很多。

1.2 技术栈怎么定:Spring Boot + MyBatis + MySQL

这次项目我用的组合是 JDK 8 + Spring Boot 2.7 + MyBatis + MySQL 5.7 + Bootstrap + JSP + Maven。先解释一下这套选型,免得你只照着敲代码却不知道为什么是这个组合。

技术版本建议选型理由
JDK1.8稳定、生态资料最多,大部分公司还在用
Spring Boot2.7.x内置 Tomcat,配置少,适合快速开发
MyBatis3.xSQL 完全可控,面试高频,跟 MySQL 配合好
MySQL5.7 或 8.0经典稳定,社区资料多,Navicat 可视化方便
前端JSP + Bootstrap不用额外搭 Node 环境,适合快速交付
Maven3.6+依赖管理和项目打包,行业标配

你可能会问,为什么不用现在很火的 Vue 做前后端分离?我的理由是:对于第一次做项目实战的人来说,前后端分离要同时处理跨域、Token 认证、接口文档、前端构建这些额外问题,这些内容会大幅冲淡你对核心业务逻辑的关注。用 JSP 页面加 Controller 直出,你可以把精力集中在后端的业务代码和 SQL 上。项目跑通了,再按我文末的方案改造成前后端分离,反而事半功倍。

至于 Spring Boot 而不是 SSM 手动配置,理由更直接。Spring Boot 把大量繁琐的 XML 和 Bean 配置自动化了,你可以用很少的代码把项目跑起来,这对新手特别友好。而且现在中小厂后端基本也都是 Spring Boot,学了不亏。MyBatis 则是让你保留对 SQL 的完全控制,像库存扣减这种需要精确 SQL 的操作,用 MyBatis 写起来比 JPA 更直观。MySQL 选 5.7 已经够用,如果你装了 8.0 也没关系,后面第 5 章我会专门讲版本差异带来的坑。

1.3 项目功能模块与包结构划分

功能上,我把它拆成两个端:前台用户端和后台管理端。用户端有注册登录、航班查询、下单购票、个人订单管理;管理端有航班管理、订单管理、出票退票处理。听起来模块不多,但每个模块都有值得讲的细节。比如航班查询通常要支持“出发城市、到达城市、出发日期”三个条件,日期为空时要能查全部;订单模块要有状态字段,不同状态下显示不同操作按钮,这些细节才是真实项目的样子。

代码结构我按常见的分层来建,方便你以后扩展。controller 层负责接收请求和参数校验,service 层负责业务逻辑,比如下单时库存扣减和订单生成必须在一个事务里,mapper 层负责数据库操作,entity 就是实体类。我直接把包结构给你,照着建就行:

com.airline ├── controller // 控制层:UserController、FlightController、OrderController ├── service // 业务层:UserService、FlightService、OrderService ├── mapper // 数据访问层:UserMapper、FlightMapper、OrderMapper ├── entity // 实体类:User、Flight、Order ├── interceptor // 拦截器:LoginInterceptor ├── common // 通用类:Result 返回结果封装 └── config // 配置类:WebConfig 注册拦截器

每层之间单向依赖,controller 调 service,service 调 mapper,不要越层调用。我刚带项目时见过有人直接在 controller 里写 JDBC,代码全揉在一起,后期想改业务逻辑,牵一发动全身。分层清晰是项目可维护性的第一步。

2. 数据库设计:先想清楚表再动手写代码

2.1 核心表结构拆解:用户、航班、订单

数据库设计是这类项目里最不该跳过的部分。很多新手一上来就写代码,写到关联查询才发现表结构不合理,返工特别痛苦。我建议你先把表想清楚再动手,建表顺序也固定:先建用户表,再建航班表,最后建订单表。

用户表存的是登录账号和乘客信息。用户名和密码是必须的,真实姓名、手机号、身份证号属于购票实名信息,虽然可以直接让用户在下单时填,但提前存在用户表里会让用户下单更顺畅,也是实际航空系统的做法。

航班表是核心业务表,一条记录就是一个航班。航班号、航空公司、出发城市、到达城市、出发时间、到达时间、票价、余票数都是重点。这里余票数就是库存,是整个系统最需要小心处理的字段。

订单表把用户和航班关联起来,同时记录乘客信息和成交价格。订单表是业务真正的核心,查询最频繁,设计要尤其注意冗余字段,具体原因我在 2.2 节详细说。

2.2 字段类型与约束为什么这样选

先看用户表。用户名要加唯一约束,这是登录查询的依据,否则注册时可能出现重复账号。密码字段长度给到 255,不要只给 32,因为后面可能升级加密算法,MD5 是 32 位,BCrypt 是 60 位,留足余量是习惯问题。角色字段 role 用 tinyint 表示,0 是管理员,1 是普通用户,比用字符串更省空间,判断也方便。

再看航班表。价格字段必须用 decimal(10,2),千万别用 float 或 double。二进制浮点数在计算金额时有精度误差,哪怕账面上只差 0.01,累积起来也是麻烦事。余票字段 stock 用 int,默认值 0,后面扣减库存时要用它做条件判断。航班号要加唯一索引,因为这是业务上天然的唯一标识。

订单表的设计是这套系统里含金量最高的地方。我把乘客姓名和身份证号直接冗余在订单表里,不通过 user_id 去用户表反查,原因是机票支持帮别人买,乘客不一定是注册用户。既然订单要求实名,那就必须把乘客信息快照到订单里。类似地,订单里还冗余了 price,记录购买时的成交价,因为航班价格会变,订单表要保留历史快照。这个“冗余快照”的思路,面试官非常爱问,你答出来就是加分项。

订单状态 status 用 tinyint,0 待支付、1 已支付、2 已出票、3 已取消、4 已退票。不要直接存中文,不好扩展也不好比较。实际开发中一般会在代码里用常量类或者枚举统一管理这些状态,JSP 页面再根据状态值显示对应按钮。

另外,三张表之间我故意没用数据库外键约束,只保留逻辑关联字段 user_id、flight_id。外键虽然能保证数据一致性,但会带来额外的锁开销和耦合,线上项目普遍不用外键,靠业务代码控制。这个点你也可以在面试时说,显得你了解实际工程做法。

2.3 建表 SQL 与初始化数据

下面是完整的建表 SQL,我按实际项目标准写好注释,你直接用 Navicat 或命令行执行即可:

CREATE DATABASE IF NOT EXISTS airline DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE airline; -- 用户表 CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(255) NOT NULL COMMENT '密码,演示用MD5加密', `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `id_card` varchar(18) DEFAULT NULL COMMENT '身份证号', `role` tinyint NOT NULL DEFAULT '1' COMMENT '角色:0管理员,1普通用户', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 航班表 CREATE TABLE `flight` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `flight_no` varchar(20) NOT NULL COMMENT '航班号', `airline` varchar(50) DEFAULT NULL COMMENT '航空公司', `departure_city` varchar(50) NOT NULL COMMENT '出发城市', `arrival_city` varchar(50) NOT NULL COMMENT '到达城市', `departure_time` datetime NOT NULL COMMENT '起飞时间', `arrival_time` datetime NOT NULL COMMENT '到达时间', `price` decimal(10,2) NOT NULL COMMENT '票价', `stock` int NOT NULL DEFAULT '0' COMMENT '余票数', `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1正常,0停售', PRIMARY KEY (`id`), UNIQUE KEY `uk_flight_no` (`flight_no`), KEY `idx_city_time` (`departure_city`, `arrival_city`, `departure_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='航班表'; -- 订单表 CREATE TABLE `orders` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `order_no` varchar(32) NOT NULL COMMENT '订单号', `user_id` int NOT NULL COMMENT '下单用户ID', `flight_id` bigint NOT NULL COMMENT '航班ID', `passenger_name` varchar(50) NOT NULL COMMENT '乘客姓名', `passenger_id_card` varchar(18) NOT NULL COMMENT '乘客身份证号', `seat_class` varchar(20) DEFAULT '经济舱' COMMENT '舱位', `price` decimal(10,2) NOT NULL COMMENT '成交价快照', `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0待支付 1已支付 2已出票 3已取消 4已退票', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

初始化数据也顺手给几条,方便测试查询和下单流程。管理员账号 admin,普通用户 zhangsan,航班加两个热门航线,余票库存给几个不同值方便测试:

INSERT INTO `user` (`username`, `password`, `real_name`, `phone`, `id_card`, `role`) VALUES ('admin', 'e10adc3949ba59abbe56e057f20f883e', '系统管理员', '13800000000', '110101199001011234', 0), ('zhangsan', 'e10adc3949ba59abbe56e057f20f883e', '张三', '13912345678', '110101199505052345', 1); INSERT INTO `flight` (`flight_no`, `airline`, `departure_city`, `arrival_city`, `departure_time`, `arrival_time`, `price`, `stock`, `status`) VALUES ('CA1234', '中国国航', '北京', '上海', '2025-07-01 08:00:00', '2025-07-01 10:15:00', 1280.00, 150, 1), ('MU5678', '东方航空', '上海', '广州', '2025-07-01 14:30:00', '2025-07-01 17:00:00', 980.00, 0, 1), ('CZ9012', '南方航空', '广州', '成都', '2025-07-02 09:20:00', '2025-07-02 11:45:00', 860.00, 80, 1);

这里的密码 e10adc3949ba59abbe56e057f20f883e 就是 123456 的 MD5 值,演示项目直接这样写,方便测试时统一用 123456 登录。

2.4 索引设计:查询快慢的差别在这

索引设计不是建完表就完事,要结合业务查询场景来定。我最开始的版本只建了主键,结果航班查询一多,数据量上来以后查询明显变慢,后来加了索引才解决。

用户表上 username 唯一索引,既保证账号唯一,也加速登录查询。这个索引是必加的,登录接口基本每次都按 username 查。

航班表上我建了联合索引idx_city_time,字段顺序是departure_city, arrival_city, departure_time。因为系统里最常见的查询是“从北京到上海,7月1日有没有票”,这种组合条件走联合索引效率最高。这里注意索引字段顺序很重要:等值条件的城市放前面,范围条件的日期放后面,否则索引利用率会变差。

订单表上 user_id 加普通索引,因为用户订单列表是高频查询,按 user_id 查订单是必然操作。另外,订单号 order_no 要加唯一索引,同时还能兜底防止订单号重复插入。

说到底,索引不是越多越好,每个索引都有写入开销,你要针对真实查询场景来设计。新手最容易犯的错就是给所有字段都加索引,结果数据一多写入反而变慢。一般一张表 3~5 个索引足够。

3. 后端核心业务实现

3.1 注册登录与权限控制

登录这块我讲讲思路,代码给你核心片段。密码我用了 MD5 加盐处理。严格来说 MD5 不算安全加密,演示项目够用,真实生产建议换成 BCrypt。关键代码逻辑是这样:

// 登录 public User login(String username, String password) { String md5Pwd = DigestUtils.md5DigestAsHex(password.getBytes()); User user = userMapper.findByUsername(username); if (user != null && user.getPassword().equals(md5Pwd)) { return user; } return null; }

Controller 层校验通过后,把用户对象放到 Session 里:

@PostMapping("/login") public String doLogin(String username, String password, HttpSession session, Model model) { User user = userService.login(username, password); if (user == null) { model.addAttribute("error", "用户名或密码错误"); return "login"; } session.setAttribute("loginUser", user); return "redirect:/flight/list"; }

光登录还不够,下单和订单查询接口必须做拦截,不然用户不登录也能调接口,这就是严重的越权问题。我用 Spring MVC 拦截器实现登录校验,没登录就跳回登录页:

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); if (session.getAttribute("loginUser") != null) { return true; } response.sendRedirect(request.getContextPath() + "/login"); return false; } }

配置拦截器时注意,静态资源要放行,不然后台页面 CSS、JS 全被拦了:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/login", "/register", "/css/**", "/js/**", "/images/**"); } }

3.2 航班多条件查询

航班查询接口是用户用得最多的,条件包含出发城市、到达城市、出发日期,三个条件都不固定。MyBatis 里用动态 SQL 拼装条件,比 JPA 更直观:

<select id="searchFlights" resultType="com.airline.entity.Flight"> SELECT * FROM flight <where> <if test="departureCity != null and departureCity != ''"> AND departure_city = #{departureCity} </if> <if test="arrivalCity != null and arrivalCity != ''"> AND arrival_city = #{arrivalCity} </if> <if test="departureDate != null"> AND DATE(departure_time) = #{departureDate} </if> AND status = 1 </where> ORDER BY departure_time </select>

这里有个关键点:前端传过来的日期是字符串2025-07-01,后端接收时要转成LocalDate,并且日期类型要用@DateTimeFormat注解指定格式,否则 Spring 解析不了:

@GetMapping("/flight/search") public String search(@RequestParam(required = false) String departureCity, @RequestParam(required = false) String arrivalCity, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate departureDate, Model model) { List<Flight> flights = flightService.search(departureCity, arrivalCity, departureDate); model.addAttribute("flights", flights); return "flight_list"; }

所有搜索条件我都设置成 required = false,用户只填一个条件也能查,这样体验更好。查询出来之后,余票为 0 的航班直接在前端禁用下单按钮,避免用户点进去才知道没票。

3.3 下单与库存扣减的事务处理

下单是整个项目最核心的业务,也是最容易出 bug 的地方。我先说最终方案,再解释为什么。

@Transactional(rollbackFor = Exception.class) public Order createOrder(Integer userId, Long flightId, String passengerName, String passengerIdCard) { // 1. 查询航班是否存在且正常 Flight flight = flightMapper.findById(flightId); if (flight == null || flight.getStatus() != 1) { throw new RuntimeException("航班不存在或已停售"); } // 2. 扣减库存,通过 stock > 0 条件防止超卖 int rows = flightMapper.deductStock(flightId); if (rows == 0) { throw new RuntimeException("余票不足,下单失败"); } // 3. 生成订单并保存 Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setFlightId(flightId); order.setPassengerName(passengerName); order.setPassengerIdCard(passengerIdCard); order.setPrice(flight.getPrice()); order.setStatus(0); orderMapper.insert(order); return order; }

扣减库存对应这句 SQL,是整个防超卖的关键:

<update id="deductStock"> UPDATE flight SET stock = stock - 1 WHERE id = #{flightId} AND stock > 0 </update>

先解释@Transactional为什么必须加。如果库存扣了,订单插入却失败,没有事务的话数据就脏了。加了事务注解,两步要么都成功,要么都回滚。这里我用的是 Spring 声明式事务,注意默认只回滚运行时异常,所以 rollbackFor 指定了 Exception.class,保证任何异常都回滚。

再解释防超卖。如果你先SELECT stock FROM flight WHERE id = ...,在代码里判断 stock > 0 再 UPDATE,两个用户同时查询都看到 stock = 1,就会出现两个人同时下单买同一张票,最终库存变成 -1。我把判断条件直接写进 UPDATE 的 WHERE 子句里,这条 SQL 执行时会对该行加行锁,第二个事务必须等第一个提交后才能执行,此时 stock 已经变成 0,影响行数为 0,抛异常回滚。这种写法叫乐观锁或条件更新,比 synchronized 锁在应用层更靠谱,因为它是数据库层面保证的。

订单号生成我用的是时间戳加随机数:yyyyMMddHHmmss加四位随机数,同时数据库有唯一索引兜底,万一极端情况撞了唯一索引,插入报错,事务回滚。实际项目里订单号还会加入用户 ID、业务类型等,我这里保持简单。

3.4 订单状态管理与后台功能

订单状态我用 0 到 4 五个数字表示,状态的流转必须清晰。0 待支付可以主动取消变成 3,可以支付变成 1;1 已支付可以由管理员出票变成 2,也可以申请退票变成 4;已出票的状态基本就锁定了。这里我把状态流转控制放在 Service 层,前端只展示当前状态和可操作按钮,不直接修改状态值。这样状态流转移到一个地方管理,不会出现在 JSP 里到处写死状态逻辑的的情况。

后台管理端我做了两个功能:航班管理、订单管理。航班管理支持新增航班、修改航班信息、上下架航班,对应普通 CRUD,但要注意删除航班前要检查有没有关联订单,有订单的航班不能直接删,建议用下架代替删除。订单管理主要展示所有订单列表,管理员可以对已支付订单执行出票操作,状态从 1 变成 2。

这里有个细节很容易被忽略:用户点击“支付”和“确认出票”时,接口要判断当前状态是否符合预期。比如订单已经是已支付状态,用户重复点支付,后端应该返回“订单状态异常”而不是重复扣款。这也是面试喜欢问的场景。

4. 前端页面与交互细节

4.1 页面方案选择:JSP + Bootstrap

前端我选了 JSP + Bootstrap 4。这一层没有太复杂的逻辑,但页面的交互细节直接决定项目能不能顺利演示。JSP 可以直接用 JSTL 标签在后端渲染数据,不需要额外写 Ajax 请求,对初学者最友好。Bootstrap 负责把页面做得像样,不至于看起来像 2005 年的网页。

页面文件放在src/main/webapp/WEB-INF/jsp/目录下,需要配置视图解析器,Spring Boot 在 application.properties 里这样写:

spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp

4.2 页面模块与功能对照

页面文件对应功能核心操作
login.jsp用户登录表单校验,错误提示
register.jsp用户注册用户名是否重复
flight_search.jsp首页航班搜索三个条件查询
flight_list.jsp航班列表余票展示,下单入口
order_confirm.jsp订单确认乘客信息填写
order_list.jsp用户订单中心支付、取消、退票
admin_flight.jsp后台航班管理增删改查
admin_order.jsp后台订单管理出票操作

页面数量不多,但每个页面都有值得注意的交互细节。比如航班列表页,余票为 0 时下单按钮要禁用并显示“售罄”,不能让用户点了再报错;订单列表页,只有待支付状态显示“去支付”和“取消订单”,已支付状态才显示“申请退票”。这些状态控制你可以用 JSTL 的<c:if>标签判断,非常方便。

4.3 表单提交、数据回显与状态控制

航班搜索表单是一个 GET 请求,携带出发城市、到达城市、出发日期三个参数。这里用 GET 而不是 POST,原因是查询操作是幂等的,GET 还方便分享链接,而且刷新页面不会弹出“确认重新提交表单”的提示:

<form action="/flight/search" method="get"> <input type="text" name="departureCity" placeholder="出发城市"> <input type="text" name="arrivalCity" placeholder="到达城市"> <input type="date" name="departureDate"> <button type="submit">搜索航班</button> </form>

搜索完成后,页面要把用户提交的条件回显到表单里,不然用户想改一个城市重新搜索,发现输入框全空了,体验极差。回显方式很简单,用 JSTL 的${param.departureCity}把参数值填回 value 属性。

订单状态按钮的显示逻辑,我用一个例子展示。订单列表页,每行订单根据状态显示不同按钮:

<c:choose> <c:when test="${order.status == 0}"> <a href="/order/pay/${order.id}">去支付</a> <a href="/order/cancel/${order.id}">取消订单</a> </c:when> <c:when test="${order.status == 1}"> <a href="/order/refund/${order.id}">申请退票</a> </c:when> <c:when test="${order.status == 2}"> <span class="text-success">已出票</span> </c:when> <c:otherwise> <span class="text-muted">已取消/已退票</span> </c:otherwise> </c:choose>

这里的除法要做一次后端校验,不能只靠前端藏按钮保证安全。用户手动构造请求直接访问/order/cancel/1完全可能,后端 Service 里必须再次判断订单状态是否符合操作条件,否则就是业务漏洞。很多新手项目功能演示没问题,一被深挖就垮,往往就是这种状态校验漏了。

5. 本地部署、启动与常见错误排查

5.1 开发环境准备清单

先把工具列表摆出来,没装好的先装好,这一步绕不开:

工具版本用途
JDK1.8+Java 运行环境,装完记得配 JAVA_HOME 和 PATH
MySQL5.7 或 8.0数据存储
IDEA2022+开发工具
Maven3.6+依赖管理与构建
Navicat任意版本可视化操作数据库

JDK 和 MySQL 的安装教程网上很多,这里不展开了,就提醒两个高频坑:JDK 装完在命令行敲 java -version 验证一下,没反应基本都是环境变量忘了配;MySQL 安装过程中如果让你选密码强度,建议选普通强度,便于测试时记住密码。

5.2 从零启动项目的完整步骤

新建 Spring Boot 项目时勾选 Spring Web、MyBatis Framework、MySQL Driver 三个依赖,然后把上一章的建表 SQL 导入新建的 airline 数据库,再写配置文件,最后运行启动类。启动成功后浏览器访问http://localhost:8080/flight/list,能看到航班列表页就说明项目骨架没问题。

我把关键的 application.yml 配置放在这里,注意 MySQL 8.0 和 5.7 的配置有差异,一会儿单独讲:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/airline?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.airline.entity

如果要用 Maven 打包部署,命令行执行mvn clean package -Dmaven.test.skip=true,打出来的 jar 包直接java -jar target/airline-0.0.1-SNAPSHOT.jar就能跑。打包部署这一步建议至少做一次,很多面试官会问“你项目怎么部署的”,能答出 jar 包运行比只会 IDEA 点运行强不少。

5.3 常见问题与解决方案速查表

现象可能原因解决方案
数据库连接失败,报 Access denied用户名密码错误或账号权限不足检查 yml 里的账号密码,手动连一次 Navicat 确认
报 Communications link failureMySQL 服务没启动启动 MySQL 服务,或检查端口是否 3306
页面中文乱码数据库连接串缺少编码参数url 加 useUnicode=true&characterEncoding=utf8
Mapper 绑定的 SQL 找不到mapper-locations 路径配置错误检查 mapper xml 是否在 classpath 指定目录下
8080 端口被占用其他进程占用端口换端口server.port=8081或杀掉占用进程
前台页面样式全丢拦截器拦截了静态资源放行 /css/、/js/等路径
LocalDate 类型格式解析失败前端日期格式后端不认参数加 @DateTimeFormat(pattern = "yyyy-MM-dd")
Maven 依赖下载失败网络问题或镜像没配配置阿里云 mirror,重新导入
项目启动报 Servlet 初始化异常JSP 依赖没加全添加 tomcat-embed-jasper 依赖
下单后库存没扣或扣了没订单事务没生效Service 实现类加 @Transactional,异常回滚检查

5.4 MySQL 5.7 和 8.0 的版本差异坑

如果装的是 MySQL 8.0,有两个地方要改:驱动类名从com.mysql.jdbc.Driver改成com.mysql.cj.jdbc.Driver,连接 URL 必须加serverTimezone=Asia/Shanghai,否则会报时区错误。Maven 依赖里 mysql-connector-java 的版本也会自动跟随 Spring Boot 版本,8.0 的 MySQL 对应连接器 8.x,没问题。

还有个老问题就是 Navicat 连不上 MySQL 8.0,报 1251 错误。原因是 MySQL 8.0 默认缓存认证插件是 caching_sha2_password,Navicat 老版本不支持,最简单的解决方案是执行这条 SQL,把 root 用户的认证方式改回 mysql_native_password:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;

另外,5.7 和 8.0 的 SQL 基本兼容,这个项目在两个版本下都能跑,核心逻辑不受影响。如果你跟着教程装的早期版本连接器,记得在 IDEA 的 Maven 窗口里刷新一下,让依赖版本对齐。

6. 做完这个项目后,我的一些体会

这个项目我前前后后带人做过好几次,每次做完都会发现新的东西。第一版的时候,我只把流程跑通就没有继续优化,后来复盘才发现少了拦截器,用户不登录直接访问下单接口也能下单,这个漏洞在面试中一问一个准。所以我后来每一版都会把权限控制、状态校验、事务完整性这些从代码里抠出来,单独检查一遍。

我个人最深的体会是,别小看这个项目,它虽然表只有三张,但包含的业务设计思想并不少。库存扣减的并发处理、订单状态机的流转、乘客信息冗余快照、索引设计,每一个都是真实项目里的高频考点。你把这个项目里这些问题真正想明白,远比照着视频敲一个看起来高大上的商城系统更有收获。

如果你已经把这个版本跑通了,想再往深做,我建议三条路:第一,把航班查询结果用 Redis 做缓存,减少数据库压力;第二,前端换成 Vue 3 + Axios,后端改成纯接口返回 JSON,练前后端分离;第三,给支付模块接入支付宝沙箱或者微信支付沙箱,让订单状态流转有真实支付回调。这三条路按自己精力选一条就够,项目质感和面试可聊的东西都会明显提升。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 2:54:33

STM32N6部署U-Net:Neural-ART Oauto编译失败排查与解决指南

1. 问题现场&#xff1a;训练好的 U-Net&#xff0c;Neural-ART 量化成功但优化失败先说个我最近反复遇到、也帮几个朋友远程看过的问题。模型用的是非常典型的 U-Net 结构&#xff0c;输入是单通道灰度图&#xff0c;输出是同样尺寸的分割掩码&#xff0c;训练完在 PC 上验证精…

作者头像 李华
网站建设 2026/8/31 2:54:02

掌静脉识别毕设实战:从光学原理到轻量模型部署

简介&#xff1a;本资源是一套面向本科毕业设计、课程设计及期末大作业的高分深度学习实战项目&#xff0c;聚焦非接触式掌静脉识别这一生物特征认证前沿方向&#xff0c;适用于计算机、人工智能、信息安全等专业学生快速开展毕设开发与答辩准备。压缩包共2000个文件&#xff0…

作者头像 李华
网站建设 2026/8/31 2:50:16

网约车抽成比例下降背后的计费系统改造:从硬编码到规则配置化

最近网约车行业里讨论度很高的一句话是“网约车平台抽成比例下降了”。对司机来说&#xff0c;这直接关系到每单到手收入&#xff1b;对产品团队来说&#xff0c;这是一个需要快速落地的业务策略&#xff1b;但对技术团队而言&#xff0c;这句话背后往往藏着一个更现实的问题&a…

作者头像 李华
网站建设 2026/8/31 2:49:13

MATLAB缺少工具包?从报错定位、许可检查到加载路径的完整排查指南

有过这么一次经历&#xff1a;我在整理一份潮汐观测数据时&#xff0c;想调用某个信号处理工具箱里的函数做分潮调和分析&#xff0c;结果 MATLAB 直接弹出一行红色报错&#xff0c;大意是“未定义函数或变量”。当时第一反应是去网上搜这个工具箱的下载包&#xff0c;折腾了半…

作者头像 李华
网站建设 2026/8/31 2:43:32

从源码到实战:手把手搭建WebRTC视频会议系统

简介&#xff1a;这是一套基于WebRTC技术实现的轻量级视频会议系统源码&#xff0c;面向计算机、电子信息、软件工程等专业的本科生与初学者&#xff0c;适用于课程设计、期末大作业及毕业设计参考&#xff0c;帮助学习者掌握实时音视频通信的核心原理与工程落地方法。资源共10…

作者头像 李华
网站建设 2026/8/31 2:43:20

基于S型曲线与过渡圆弧的多段连续插补平滑算法解析

简介&#xff1a;本资源是一套面向本科及硕士阶段机器人运动控制与轨迹规划教学的Matlab实践算法包&#xff0c;聚焦于多段路径间基于S型加减速曲线的连续插补与平滑过渡问题&#xff0c;适用于数控系统、工业机器人轨迹优化等典型应用场景。压缩包共66个文件&#xff0c;含59个…

作者头像 李华