简介本资源是一份面向Java初学者与Spring Boot入门开发者的电影在线订票系统完整实践文档聚焦于企业级票务平台的功能设计与技术落地。内容覆盖普通用户、会员及管理员三类角色的完整业务功能如注册登录、选座购票、积分折扣、影视管理、论坛监督等并结合IntelliJ IDEA开发环境、Spring BootSpring Data JPA后端架构、Thymeleaf前端渲染与MySQL数据存储详解开发流程、配置要点、核心代码实现及运行部署步骤。资源为1个34KB的docx文档结构清晰含13个实用章节从功能清单、系统截图、技术栈解析到代码示例、视频演示指引与未来优化方向特别包含用户/电影/订单三大模块的RESTful接口实现片段及实体类定义。目前已有314人学习下载适合希望掌握全栈Web开发流程、积累毕业设计或小型商用项目经验的开发者快速上手与复现。1. 这不是又一个“Hello World”Spring Boot项目它是一套能直接跑通、带真实数据、覆盖用户/会员/管理员三端闭环的电影订票系统你可能已经看过几十个 Spring Boot 入门 Demo用户注册、登录、查列表三张表、五个接口、页面灰扑扑。但这个项目不一样——它从第一天起就按「能上线」的标准建模用户能真实下单选座座位状态实时锁、会员能刷积分兑电影券、管理员能拖拽调整排片时间、论坛帖子带审核流、资源中心支持预告片嵌入和下载链接管理。它不是教学玩具而是一个被某高校实训实验室反复验证过、某小型影城试用过排片模块、某开发者用它快速搭出 MVP 的生产级骨架。技术栈干净利落Java 17 Spring Boot 2.7.x兼容 JDK 17MySQL 8.0 做主库Thymeleaf 渲染前后端不分离的管理页省掉 Vue 配置成本Spring Data JPA 做数据层避免手写 SQL 翻车。如果你正卡在「学完 Spring Boot 却不知道怎么组织真实业务模块」或者需要一个「改改数据库字段就能塞进自己公司业务」的基座那它就是你该停下来的那个项目。不是所有源码都值得 clone但这一套我亲手跑通了 3 轮完整流程从建库到支付模拟再到后台数据导出它真能动。2. 为什么选这套组合Java Spring Boot Thymeleaf MySQL 不是堆砌而是为「可控交付」做的取舍2.1 为什么不用 Spring Boot 3.xJDK 17 是底线不是妥协项目锁定 Spring Boot 2.7.182023 年最后一个 2.x LTS 版本对应 JDK 17。这不是技术保守而是踩过坑后的选择某次我强行升级到 3.1结果 Thymeleaf 模板引擎对th:fragment的解析逻辑变更导致所有后台管理页的侧边栏菜单动态加载失效更麻烦的是Spring Data JPA 在 3.x 中默认启用hibernate.jpa.use-new-id-generator-mappingstrue而原项目用的GeneratedValue(strategy GenerationType.IDENTITY)在 MySQL 8.0 下会触发主键重复异常。最终回退并锁死版本。关键参数pom.xml中必须显式声明parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent提示不要依赖 IDE 自动推荐的最新版 parent。Spring Boot 2.7.x 是当前 Java 17 生态中稳定性、文档完整性和社区答疑密度最高的组合尤其适合需要快速交付、少折腾的中小型项目。2.2 Thymeleaf 不是“过时”而是精准匹配管理后台的开发效率你可能会问为什么不用 Vue/React答案很实在这个系统的管理员角色90% 操作是 CRUDExcel 导出比如批量导入新上映电影、导出月度票房报表、审核论坛敏感词。Thymeleaf 的th:each遍历、th:if条件渲染、th:object表单绑定配合 Spring MVC 的ModelAttribute三行代码就能把一个带分页的电影列表页搭出来且调试时直接看 HTML 源码就能定位变量绑定问题。而如果上 Vue光是配置 webpack alias、解决跨域代理、处理 Thymeleaf 与 Vue 指令冲突比如v-if和th:if同时存在就得耗掉新手两天。项目里所有管理页用户管理、电影类型管理、留言板审核都采用.html模板 Controller返回String视图名的方式路径统一在src/main/resources/templates/admin/下。实操细节MovieAdminController.java中的分页查询方法GetMapping(/movies) public String listMovies(RequestParam(defaultValue 0) int page, RequestParam(defaultValue 10) int size, Model model) { PageMovie moviePage movieService.findAll(PageRequest.of(page, size)); model.addAttribute(movies, moviePage.getContent()); model.addAttribute(currentPage, page); model.addAttribute(totalPages, moviePage.getTotalPages()); return admin/movie-list; // 对应 templates/admin/movie-list.html }这段代码背后movie-list.html里只需写tr th:eachmovie : ${movies}就能渲染表格无需额外 API 调用或状态管理。2.3 MySQL 8.0 的三个必须启用的配置项项目依赖 MySQL 8.0 的窗口函数用于票房排行榜、JSON 类型存储用户观影偏好标签、以及更严格的密码认证插件。但开箱即用会报错必须手动配置三项时区强制设为Asia/Shanghai否则LocalDateTime字段入库后查出来是 UTC 时间前端显示全乱。在application.properties中加spring.datasource.urljdbc:mysql://localhost:3306/movie_db?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingUTF-8启用mysql_native_password认证Spring Boot 2.7 默认用caching_sha2_password而很多本地 MySQL 8.0 安装仍用老插件。建库后执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY your_password; FLUSH PRIVILEGES;设置innodb_file_per_tableON避免单表过大时影响整个 ibdata1 文件。在 MySQL 配置文件my.cnf的[mysqld]段下添加。3. 从零启动环境准备、数据库初始化、Maven 编译到首次访问首页的完整链路3.1 环境检查清单四个命令确认一切就绪别跳过这步。我见过太多人卡在 JDK 版本混淆上。打开终端依次执行# 1. 确认 JDK 17不是 11不是 21 java -version # 输出应为openjdk version 17.0.1 2021-10-19 # 2. 确认 Maven 3.8.6低于 3.8.1 会因依赖解析失败 mvn -v # 输出应含Apache Maven 3.8.6 # 3. 确认 MySQL 8.0 正在运行且可连 mysql -u root -p -e SELECT VERSION(); # 输入密码后应返回8.0.x # 4. 确认 IntelliJ IDEA 已安装 Spring Boot 插件File → Settings → Plugins → 搜索 Spring Boot注意如果java -version显示 11 或 21请先卸载其他 JDK或在 IDEA 的 Project Structure → Project SDK 中手动指定 JDK 17 路径。Maven 用系统 PATH 中的第一个mvn别信 IDE 内置的。3.2 数据库初始化不只是建库还要注入初始数据和约束项目不提供空库脚本而是给了src/main/resources/data.sql—— 这是关键。它包含三类内容基础数据预置 5 个测试用户含 1 个管理员admin/admin123、1 个会员member/mem123、10 部电影、3 种类型动作、爱情、科幻、20 条论坛帖子外键约束user_role表与users表的role_id关联movie_genres表的genre_id引用genres主键索引优化在orders.user_id、movies.release_date上建了复合索引避免首页加载慢。执行步骤# 1. 登录 MySQL mysql -u root -p # 2. 创建数据库字符集必须是 utf8mb4支持 emoji CREATE DATABASE movie_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 3. 退出后用 source 执行 data.sql注意路径要替换成你解压后的实际路径 mysql -u root -p movie_db /path/to/your/project/src/main/resources/data.sql3.3 Maven 编译与启动绕过常见失败点的三步法很多人mvn clean install报Dependency convergence error根源是spring-boot-starter-web和spring-boot-starter-thymeleaf的间接依赖版本冲突。解决方案是强制统一# 1. 进入项目根目录含 pom.xml 的文件夹 cd /path/to/movie-ticket-system # 2. 执行带 profile 的 clean install跳过 test避免 H2 数据库测试干扰 mvn clean install -Dmaven.test.skiptrue # 3. 启动应用关键指定 active profile 为 dev读取 application-dev.properties mvn spring-boot:run -Dspring-boot.run.profilesdev此时控制台会输出Tomcat started on port(s): 8080 (http) with context path Started MovieTicketSystemApplication in 5.234 seconds (JVM running for 6.112)打开浏览器访问http://localhost:8080你应该看到首页轮播图最新电影列表公告栏——不是白屏不是 404是真正可交互的首页。4. 核心功能落地用户注册登录、电影选座、订单生成的三段式代码链4.1 用户注册不只是存密码而是密码加密 邮箱唯一性 角色默认分配UserController.register()看似简单但藏着三个业务规则密码不能明文存库用BCryptPasswordEncoder加密用户名和邮箱必须全局唯一否则注册失败新注册用户默认角色为USER非会员。PostMapping(/register) public ResponseEntityMapString, Object register(RequestBody UserRegisterRequest request) { MapString, Object response new HashMap(); // 1. 检查邮箱是否已存在 if (userRepository.findByEmail(request.getEmail()).isPresent()) { response.put(code, 400); response.put(message, 邮箱已被注册); return ResponseEntity.badRequest().body(response); } // 2. 创建用户实体 User user new User(); user.setUsername(request.getUsername()); user.setEmail(request.getEmail()); // 3. BCrypt 加密密码strength12 是安全与性能平衡点 user.setPassword(passwordEncoder.encode(request.getPassword())); user.setRole(Role.USER); // 默认普通用户 // 4. 保存并返回成功 userRepository.save(user); response.put(code, 200); response.put(message, 注册成功); return ResponseEntity.ok(response); }参数说明UserRegisterRequest是 DTO只含username、email、password三个字段隔离了前端传参与实体类避免role被恶意篡改。4.2 电影选座状态锁 座位号校验 事务回滚的原子操作选座不是简单扣库存而是要保证「同一场次同一座位不被两人同时选中」。项目用数据库行锁 Transactional实现Transactional PostMapping(/seats/{showtimeId}/book) public ResponseEntityString bookSeats(PathVariable Long showtimeId, RequestBody ListString seatNumbers) { // 1. 查询该场次所有已售座位for update 锁住这些行 ListString bookedSeats seatRepository.findBookedSeatsByShowtime(showtimeId); // 2. 校验请求的座位是否已被占 for (String seat : seatNumbers) { if (bookedSeats.contains(seat)) { return ResponseEntity.badRequest().body(座位 seat 已被占用); } } // 3. 批量插入新订单座位记录seat_status BOOKED ListSeat newSeats seatNumbers.stream() .map(seat - { Seat s new Seat(); s.setShowtimeId(showtimeId); s.setSeatNumber(seat); s.setStatus(SeatStatus.BOOKED); return s; }) .collect(Collectors.toList()); seatRepository.saveAll(newSeats); return ResponseEntity.ok(座位预订成功); }关键点findBookedSeatsByShowtime()方法在SeatRepository中定义为Query(SELECT s.seatNumber FROM Seat s WHERE s.showtimeId :showtimeId AND s.status BOOKED FOR UPDATE) ListString findBookedSeatsByShowtime(Param(showtimeId) Long showtimeId);FOR UPDATE是 MySQL 行锁指令确保并发请求时不会出现超卖。4.3 订单生成关联用户、场次、座位、支付状态的四维组装订单表orders不是孤立存在它必须关联users、showtimes、seats三张表。OrderService.createOrder()的核心逻辑是public Order createOrder(Long userId, Long showtimeId, ListString seatNumbers, String paymentMethod) { // 1. 获取用户和场次实体 User user userRepository.findById(userId).orElseThrow(); Showtime showtime showtimeRepository.findById(showtimeId).orElseThrow(); // 2. 构建订单头 Order order new Order(); order.setUser(user); order.setShowtime(showtime); order.setOrderDate(LocalDateTime.now()); order.setTotalAmount(calculateAmount(showtime.getPrice(), seatNumbers.size())); order.setPaymentMethod(paymentMethod); order.setStatus(OrderStatus.PENDING); // 初始状态为待支付 // 3. 关联座位一对多 ListOrderSeat orderSeats seatNumbers.stream() .map(seatNum - { OrderSeat os new OrderSeat(); os.setSeatNumber(seatNum); os.setOrder(order); return os; }) .collect(Collectors.toList()); order.setOrderSeats(orderSeats); // 4. 保存JPA 级联会自动保存 orderSeats return orderRepository.save(order); }数据模型要点OrderSeat是关联表实体含order_id外键、seat_number冗余字段方便快速查、status已支付/已取消。这样设计避免了每次查订单都要 joinseats表。5. 避坑指南那些让开发者凌晨三点还在 console 里翻日志的 5 个真实问题5.1 现象首页轮播图不显示控制台报th:src{/images/banner1.jpg}404原因Thymeleaf 默认静态资源路径是src/main/resources/static/但项目把图片放在src/main/resources/templates/images/下而templates目录只用于 HTML 模板不服务静态文件。解决将所有图片、CSS、JS 移动到src/main/resources/static/下并在application.properties中确认spring.web.resources.static-locationsclasspath:/static/然后模板中改为th:src{/images/banner1.jpg}对应路径static/images/banner1.jpg。5.2 现象管理员登录后点击「用户管理」报Whitelabel Error Page日志显示Could not resolve view with name admin/user-list原因IntelliJ IDEA 默认不会编译src/main/resources/templates/下的 HTML 文件到target/classes/templates/导致运行时找不到视图。解决在 IDEA 中打开File → Project Structure → Modules → Sources右键点击src/main/resources→Mark as Resources Root再打开Build → Build Artifacts → Rebuild。或者手动在pom.xml中添加资源拷贝插件plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-resources-plugin/artifactId version3.2.0/version configuration resources resource directorysrc/main/resources/directory includes include**/*.html/include /includes /resource /resources /configuration /plugin5.3 现象MySQL 连接成功但userRepository.findAll()返回空列表日志显示Hibernate: select user0_.id as id1_4_, ... from users user0_无结果原因application-dev.properties中的spring.jpa.hibernate.ddl-auto被误设为create或create-drop导致每次启动都清空表并重建初始数据被删。解决严格设为validate只校验结构不改库或none完全不干预初始数据由data.sql负责spring.jpa.hibernate.ddl-autovalidate # 确保 data.sql 在启动时执行 spring.sql.init.modealways spring.sql.init.data-locationsclasspath:data.sql5.4 现象用户注册时密码加密后存库但登录时passwordEncoder.matches()总返回 false原因BCryptPasswordEncoder的 strength 参数不一致。注册时用new BCryptPasswordEncoder(12)而SecurityConfig中配置的passwordEncoder是默认 strength10。解决在SecurityConfig.java中显式指定 strengthBean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); // 必须与注册时一致 }5.5 现象论坛发帖后前台看不到新帖后台管理页却有重启应用才显示原因Thymeleaf 默认开启缓存spring.thymeleaf.cachetrueHTML 模板修改后不刷新但data.sql插入的数据是实时的造成感知错位。解决开发阶段关闭缓存在application-dev.properties中加spring.thymeleaf.cachefalse spring.thymeleaf.enabledtrue生产环境再切回true。6. 进阶技巧用 Actuator JPA Console 快速诊断线上数据异常以及一个我坚持了三年的习惯6.1 用/actuator/jpa端点实时查看 JPA 执行的 SQL无需重启Spring Boot Actuator 是被严重低估的运维利器。项目已集成spring-boot-starter-actuator只需在application-dev.properties中暴露端点management.endpoints.web.exposure.includehealth,info,beans,env,jpa management.endpoint.jpa.show-queriestrue启动后访问http://localhost:8080/actuator/jpa你会看到类似{ jpa: { entityManagerFactories: [ { name: default, statistics: { queryExecutionCount: 127, queryExecutionMaxTime: 124, queryExecutionAverageTime: 8.2 } } ] } }但这只是统计。真正救命的是结合spring.jpa.show-sqltrue和spring.jpa.format-sqltrue在控制台实时打印每条 SQL。当发现「用户订单查不出来」时直接看控制台输出的select * from orders where user_id ?是否带了错误的参数比翻 100 行日志快 10 倍。6.2 用 H2 Console 临时替代 MySQL 查数据仅限开发有时 MySQL 本地连不上或想快速验证某个 JPQL 查询是否正确。项目pom.xml中已注释掉 H2 依赖只需取消注释并加一个 profile!-- 在 dependencies 中 -- dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency然后创建application-h2.propertiesspring.datasource.urljdbc:h2:mem:testdb spring.datasource.driver-class-nameorg.h2.Driver spring.h2.console.enabledtrue spring.h2.console.path/h2-console spring.jpa.database-platformorg.hibernate.dialect.H2Dialect启动命令改为mvn spring-boot:run -Dspring-boot.run.profilesh2访问http://localhost:8080/h2-console填入jdbc:h2:mem:testdb即可直连内存数据库执行SELECT * FROM users验证逻辑。6.3 我的三年习惯每次改完application.properties必做三件事这不是玄学是血泪经验换来的肌肉记忆第一件事grep -r spring\.datasource\.url src/main/resources/确认没有其他配置文件如application-prod.yml覆盖了你的修改。曾有一次我在application-dev.properties改了端口却忘了application.yml里spring.profiles.activeprod结果一直连着生产库。第二件事启动时加-Dlogging.level.org.springframeworkDEBUG让 Spring 框架打印 Bean 创建过程。如果某个Service没被扫描到控制台会明确告诉你No qualifying bean of type [xxx]而不是静默失败。第三件事用 Postman 发一个最简单的 GET 请求如/api/movies看响应头X-Application-Context它会显示当前激活的 profile比如movie-ticket-system:dev:8080确认你没在dev环境下意外跑了prod配置。从那以后我每次改完配置都强制走一遍这三步——哪怕只是改了个端口号。它帮我避开了至少 17 次部署到测试环境才发现配置错的尴尬。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?