资讯动态

SpringBoot+Thymeleaf旅游网站毕设实战:前后端分离架构与核心模块实现

发布时间:2026/9/2 8:21:22 来源:尧图企业网站定制
简介这是一套面向计算机专业本科生的高质量毕业设计项目源码基于SpringBoot后端框架与Thymeleaf模板引擎构建旅游网站系统采用前后端分离架构完整覆盖用户注册登录、景点浏览、线路预订、订单管理、后台数据统计等核心业务模块适用于Java Web课程设计、毕设开发与企业级项目入门实践。压缩包共183个文件包含44个Java业务逻辑类、28个HTML页面模板、18个CSS样式文件含bootstrap.min.css、animate.css、slider-pro.css等主流UI组件、16个JavaScript交互脚本及37张JPG素材图整体体积9.57MB结构清晰、注释规范便于快速理解MVC分层设计与Thymeleaf动态渲染机制。目前已有595人学习下载资源附带可直接运行的SQL数据库脚本与application.yml配置说明开箱即用显著降低环境搭建与调试门槛是掌握SpringBoot实战开发的优质参考范例。1. 项目概述与核心价值最近几年无论是计算机专业的同学做毕业设计还是初级开发者想找个项目练手“旅游网站”都是一个经久不衰的热门选题。它业务场景清晰功能模块典型从用户端到管理端都能覆盖非常适合用来串联起Web开发的全栈技能。今天我想分享的就是一个基于SpringBoot Thymeleaf技术栈实现的旅游网站系统并且采用了前后端分离的架构思想。这个项目不仅功能完整代码结构清晰更重要的是它避开了许多新手在搭建第一个“像样”项目时容易踩的坑比如混乱的包结构、硬编码的前后端耦合、难以维护的静态页面等。如果你正在为毕设发愁或者想找一个能写进简历的实战项目这个系统的设计思路和实现细节或许能给你带来不少启发。这个系统的核心是构建一个模拟的在线旅游服务平台。用户可以在前端浏览旅游线路、查看景点详情、进行预订下单管理员则在后台管理产品、订单和用户信息。技术选型上后端采用SpringBoot快速搭建极大简化了传统SSM框架繁琐的配置前端视图层则使用了Thymeleaf模板引擎来渲染动态页面。这里需要特别强调的是“前后端分离”的实践我们并不是使用Vue或React这种彻底的前后端分离而是在服务端渲染SSR的框架下通过清晰的职责划分Controller只返回数据或视图名Service处理业务Thymeleaf负责渲染来实现逻辑上的分离。这种模式对于Java后端学习者来说更加友好既能理解MVC的精髓又能为将来过渡到纯API接口的前后端分离打下坚实基础。2. 技术选型背后的深度考量为什么是SpringBoot Thymeleaf这个组合而不是更火的SpringBoot Vue或者传统的JSP这背后有一系列针对学习成本、项目复杂度和毕设需求的权衡。2.1 为什么选择SpringBoot作为后端基石SpringBoot的核心优势在于“约定大于配置”和自动装配。对于毕设或入门项目来说这意味着你可以把精力几乎全部投入到业务逻辑的开发上而不是浪费在无尽的XML或注解配置里。我见过很多同学的毕设还在手动配置DispatcherServlet、视图解析器、数据库连接池一个简单的项目配置文件比代码还多这完全背离了快速开发的初衷。使用SpringBoot你只需要在pom.xml中引入spring-boot-starter-web、spring-boot-starter-thymeleaf和spring-boot-starter-data-jpa或mybatis-spring-boot-starter等Starter依赖一个具备Web MVC、模板渲染和数据库访问能力的项目骨架就瞬间搭好了。内嵌的Tomcat服务器让你可以一键启动应用无需额外部署。这对于需要快速迭代、演示的毕设项目来说效率提升是巨大的。此外SpringBoot丰富的Starter生态和统一的配置管理application.yml或application.properties也让集成缓存如Redis、安全框架如Spring Security、接口文档如Swagger/OpenAPI变得异常简单。2.2 Thymeleaf vs. JSP vs. FreeMarker视图层的抉择在服务端渲染的领域Java生态主要有JSP、FreeMarker和Thymeleaf三个选项。JSP虽然是“上古”技术但在学校教学和遗留系统中依然常见。它的主要问题是需要依赖Servlet容器并且可以在页面中嵌入大量的Java代码Scriptlet这极易导致业务逻辑和表现层高度耦合代码难以维护和测试不符合现代Web开发的最佳实践。FreeMarker一个功能强大的模板引擎语法简洁性能不错。但它是一个纯粹的模板引擎不直接与HTML标准绑定需要学习其特定的标签语法。Thymeleaf这是我们选择的方案。它的最大特点是自然模板。Thymeleaf模板本身就是标准的、语法正确的HTML文件可以直接在浏览器中打开并显示静态原型。当应用运行时Thymeleaf会用动态数据替换掉其中的特殊属性如th:text,th:each。这意味着前端设计师和后端开发者可以更好地协作设计师给的HTML原型几乎可以直接拿来用只需添加Thymeleaf属性即可。对于毕设项目Thymeleaf的优势非常明显学习曲线平缓如果你熟悉HTML那么Thymeleaf的th:*属性非常直观易于上手。与Spring生态无缝集成Spring官方推荐和支持与SpringBoot的整合几乎是零配置。功能完备它内置了强大的表达式语言SpringEL、布局方言用于复用页面头尾、国际化和片段表达式等功能足以应对毕设的复杂度。利于理解MVC在Thymeleaf模式下Controller返回的是视图名如index由视图解析器定位到/templates/index.html这个过程完美诠释了MVC中“控制器调度视图渲染”的思想。注意这里说的“前后端分离”更准确地说是“前后端职责分离”。后端通过Controller提供数据模型ModelThymeleaf在服务器端将模型与模板结合生成最终的HTML。这与基于Vue/React、后端只提供JSON API的“物理分离”不同但同样是清晰架构的体现。2.3 “前后端分离”在服务端渲染项目中的实践在这个项目中我们实践的“分离”主要体现在以下几个方面数据与表现分离Controller方法负责准备数据从Service层获取并将其放入Model对象不关心这些数据最终如何被渲染成HTML。逻辑与视图分离所有业务逻辑、数据校验、事务管理都在Service层完成。Thymeleaf模板中只包含简单的表达式和循环判断不包含复杂的业务代码。清晰的接口契约虽然不像REST API那样有明确的Swagger文档但我们可以通过约定让每个页面对应一个Controller方法该方法需要向模板提供哪些模型属性是明确的。这为后续如果真要改造成纯API后端提供了清晰的改造路径。这种架构确保了项目具有良好的可维护性和可测试性。你可以独立测试Service层的业务逻辑也可以相对独立地修改前端页面样式只要数据模型的契约不变两者就不会相互影响。3. 系统核心模块设计与实现拆解一个典型的旅游网站系统可以划分为两大端用户端前台和管理端后台。下面我们来详细拆解每个核心模块的设计思路和实现要点。3.1 用户端核心功能模块用户端是面向游客的窗口需要注重用户体验和功能的流畅性。3.1.1 旅游产品展示与搜索模块这是系统的门面。核心实体是TourRoute旅游线路属性包括线路名称、价格、封面图、行程天数、出发地、目的地、特色标签等。实现要点数据库设计除了基础字段建议设计tags字段可存为JSON或另建关系表来存储“亲子游”、“蜜月行”、“徒步探险”等标签便于多维筛选。Thymeleaf渲染列表在Controller中查询线路列表可分页并放入模型在HTML中使用th:each进行循环渲染。分页组件可以使用Thymeleaf的片段th:fragment来复用。搜索与筛选这是一个重点。后端接收查询参数关键词、目的地、价格区间、标签在Service层构造动态的JPA Specification或MyBatis动态SQL进行查询。筛选条件应通过表单GET方式提交这样用户可以将筛选后的页面地址加入收藏夹或分享。详情页详情页需要展示更丰富的信息如详细行程安排Itinerary、费用说明、用户评价Comment等。这里通常会涉及一对多关系查询。在Thymeleaf中可以使用th:each嵌套来展示行程和评价列表。3.1.2 用户登录注册与个人中心使用Spring Security进行安全管理是专业的选择但对于毕设项目如果时间紧张实现一个简单的基于Session的登录验证也是可行的。简化实现方案用户提交登录表单POST /login。Controller调用Service验证用户名密码。验证通过后将用户对象或关键信息如ID、姓名存入HttpSessionsession.setAttribute(loginUser, user)。后续请求中通过拦截器或直接在Controller方法参数中获取HttpSession来判断用户状态。在Thymeleaf模板中可以使用th:if${session.loginUser ! null}来动态显示“登录/退出”按钮。个人中心包含订单管理Order、收藏夹Favorite、个人信息修改等功能。关键点在于数据隔离查询订单或收藏时必须在SQL或查询条件中加上user_id :currentUserId确保用户只能看到自己的数据。这是最基本的安全要求。3.1.3 购物车与订单流程这是系统的核心交易链路涉及状态流转和数据一致性。购物车设计由于旅游产品线路库存概念相对灵活通常取决于航班、酒店余位购物车可以设计得简单一些。可以将购物车信息直接存储在用户的Session中结构为一个MapLong, Integerkey是线路IDvalue是购买数量通常为1。这样无需持久化到数据库用户退出即清空。也可以通过Redis来存储实现跨会话的持久化购物车。下单流程确认订单页从购物车跳转展示选中的线路、总价、填写旅客信息姓名、证件号等。提交订单这是一个事务性操作。Controller接收到下单请求后调用一个加了Transactional注解的Service方法。该方法内应依次校验参数如库存虽然旅游产品可能不严格锁库存。生成订单号使用时间戳随机数确保唯一。创建Order主记录状态为“待支付”。创建OrderItem子记录关联线路。清空用户购物车Session或Redis。支付模拟毕设项目中可以模拟支付流程。创建一个“模拟支付”页面输入任意卡号密码点击支付后调用Service方法将订单状态更新为“已支付”。真实的支付集成支付宝、微信涉及签名、回调等复杂逻辑可作为加分项但非必需。3.2 管理端核心功能模块管理端是后台运营人员的操作面板侧重数据的增删改查CRUD和业务状态管理。3.2.1 通用CRUD架构设计管理端绝大部分功能都是对某个实体如TourRoute,Order,User的CRUD操作。我们可以设计一套通用的模式来提高开发效率。Controller层为每个管理实体创建对应的XxxAdminController。定义标准的请求映射GET /admin/route/list- 显示列表页GET /admin/route/add- 显示新增页POST /admin/route/save- 处理新增/修改提交GET /admin/route/edit/{id}- 显示编辑页POST /admin/route/delete- 处理删除通常用POST防止CSRFService层包含基础的save,findById,findAll,delete方法。视图层利用Thymeleaf的布局功能如th:replace或th:insert来复用后台管理的公共框架侧边栏、头部。列表页使用th:each渲染表格并配合th:href生成编辑和删除链接。3.2.2 旅游线路管理这是后台最复杂的模块之一因为线路信息丰富。富文本编辑线路的“详细描述”字段可能需要富文本编辑器。可以集成开源的编辑器如WangEditor或TinyMCE。前端将编辑器的HTML内容通过表单提交后端直接以HTML格式存入数据库的TEXT类型字段。在Thymeleaf详情页渲染时使用th:utextutext表示不转义HTML来正确显示富文本内容。安全警告直接渲染用户提交的HTML是极度危险的会引发XSS攻击。在管理后台由于是管理员操作风险相对可控但仍建议对富文本内容进行过滤如使用Jsoup库或严格限制编辑器的功能只允许安全的标签和属性。图片上传封面图和详情图需要上传功能。不建议将图片直接以二进制形式存数据库而是将文件保存到服务器磁盘或云存储如OSS在数据库中只存储文件的访问路径如/uploads/2023/10/abc.jpg。SpringBoot中可以使用MultipartFile接口轻松处理文件上传。3.2.3 订单与用户管理订单管理后台需要能看到所有用户的订单并支持按状态待支付、已支付、已完成、已取消筛选。关键操作是“状态变更”例如管理员可以手动将订单标记为“已完成”。这通常通过一个单独的“操作”接口实现。用户管理除了基本的查看可能还需要封禁/启用用户的功能。在User实体中添加一个status字段如 0-正常1-禁用。在用户登录校验时需要额外检查此状态。4. 项目实战从零到一的构建流程理论说再多不如动手做一遍。下面我以一个简化的“旅游线路列表与详情”功能为例展示从数据库设计到页面渲染的完整流程。4.1 环境准备与项目初始化使用Spring Initializr创建项目访问 start.spring.io 选择Project: Maven ProjectLanguage: JavaSpring Boot: 选择稳定的版本如3.x.xDependencies: 添加Spring Web,Thymeleaf,Spring Data JPA,MySQL Driver,Lombok导入IDE使用IntelliJ IDEA或Eclipse导入生成的项目。配置数据库在application.yml中配置数据源和JPA。spring: datasource: url: jdbc:mysql://localhost:3306/travel_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # 开发环境可用update生产环境用validate或none show-sql: true # 控制台显示SQL便于调试 properties: hibernate: dialect: org.hibernate.dialect.MySQL8Dialect format_sql: true4.2 实体类与数据持久层开发创建旅游线路实体package com.example.travel.entity; import lombok.Data; import javax.persistence.*; import java.math.BigDecimal; import java.time.LocalDateTime; Entity Data Table(name tour_route) public class TourRoute { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String title; // 线路标题 private String coverImage; // 封面图路径 private String destination; // 目的地 private Integer days; // 行程天数 private BigDecimal price; // 价格 Column(columnDefinition TEXT) private String description; // 简要描述 Column(columnDefinition LONGTEXT) private String detail; // 富文本详情 private Integer stock; // 库存余位 private Integer status; // 状态 0-上架 1-下架 private LocalDateTime createTime; private LocalDateTime updateTime; // 省略 getter/setter (由Lombok Data注解生成) }创建Repository接口Spring Data JPA会让基础CRUD变得非常简单。package com.example.travel.repository; import com.example.travel.entity.TourRoute; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import java.util.List; public interface TourRouteRepository extends JpaRepositoryTourRoute, Long, JpaSpecificationExecutorTourRoute { // 可以根据方法名自动推导查询例如 ListTourRoute findByDestinationAndStatusOrderByCreateTimeDesc(String destination, Integer status); }继承JpaSpecificationExecutor是为了支持复杂的动态查询这在实现多条件搜索时非常有用。4.3 业务逻辑与控制器实现创建Service层package com.example.travel.service; import com.example.travel.entity.TourRoute; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import java.util.List; public interface TourRouteService { PageTourRoute findRoutes(String keyword, String destination, Pageable pageable); TourRoute findById(Long id); // 其他CRUD方法... }package com.example.travel.service.impl; Service Slf4j public class TourRouteServiceImpl implements TourRouteService { Autowired private TourRouteRepository tourRouteRepository; Override public PageTourRoute findRoutes(String keyword, String destination, Pageable pageable) { // 使用Specification构建动态查询条件 SpecificationTourRoute spec Specification.where(null); if (StringUtils.hasText(keyword)) { spec spec.and((root, query, cb) - cb.or( cb.like(root.get(title), % keyword %), cb.like(root.get(description), % keyword %) )); } if (StringUtils.hasText(destination)) { spec spec.and((root, query, cb) - cb.equal(root.get(destination), destination)); } // 默认只查询上架的商品 spec spec.and((root, query, cb) - cb.equal(root.get(status), 0)); return tourRouteRepository.findAll(spec, pageable); } Override public TourRoute findById(Long id) { return tourRouteRepository.findById(id).orElseThrow(() - new RuntimeException(线路不存在)); } }创建Controllerpackage com.example.travel.controller; Controller RequestMapping(/route) public class RouteController { Autowired private TourRouteService tourRouteService; GetMapping(/list) public String list(RequestParam(value page, defaultValue 1) Integer page, RequestParam(value keyword, required false) String keyword, RequestParam(value destination, required false) String destination, Model model) { // Spring Data JPA的页码从0开始 Pageable pageable PageRequest.of(page - 1, 10, Sort.by(Sort.Direction.DESC, createTime)); PageTourRoute routePage tourRouteService.findRoutes(keyword, destination, pageable); model.addAttribute(routePage, routePage); model.addAttribute(keyword, keyword); model.addAttribute(destination, destination); return route/list; // 对应 /templates/route/list.html } GetMapping(/detail/{id}) public String detail(PathVariable Long id, Model model) { TourRoute route tourRouteService.findById(id); model.addAttribute(route, route); return route/detail; } }4.4 Thymeleaf前端页面渲染列表页 (/templates/route/list.html)!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title旅游线路列表/title link relstylesheet hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.1.3/dist/css/bootstrap.min.css /head body div classcontainer mt-4 !-- 搜索表单 -- form classrow g-3 mb-4 th:action{/route/list} methodget div classcol-auto input typetext classform-control namekeyword th:value${keyword} placeholder输入关键词... /div div classcol-auto select classform-select namedestination option value全部目的地/option option th:eachd : ${destinations} th:value${d} th:text${d} th:selected${d destination}/option /select /div div classcol-auto button typesubmit classbtn btn-primary搜索/button /div /form !-- 线路列表 -- div classrow div th:eachroute : ${routePage.content} classcol-md-4 mb-4 div classcard h-100 img th:src{${route.coverImage}} classcard-img-top alt封面图 styleheight: 200px; object-fit: cover; div classcard-body h5 classcard-title th:text${route.title}/h5 p classcard-text text-muted th:text目的地 ${route.destination}/p p classcard-text th:text${#strings.abbreviate(route.description, 100)}/p div classd-flex justify-content-between align-items-center strong classtext-danger th:text¥ ${#numbers.formatDecimal(route.price, 1, 2)}/strong a th:href{/route/detail/} ${route.id} classbtn btn-sm btn-outline-primary查看详情/a /div /div /div /div /div !-- 分页组件 -- nav th:if${routePage.totalPages 1} ul classpagination justify-content-center li classpage-item th:classappend${routePage.first} ? disabled : a classpage-link th:href{/route/list(page${routePage.number}, keyword${keyword}, destination${destination})}首页/a /li li classpage-item th:classappend${routePage.first} ? disabled : a classpage-link th:href{/route/list(page${routePage.number}, keyword${keyword}, destination${destination})}上一页/a /li !-- 页码循环这里简化处理 -- li classpage-item th:eachi : ${#numbers.sequence(1, routePage.totalPages)} th:classappend${i routePage.number 1} ? active : a classpage-link th:href{/route/list(page${i}, keyword${keyword}, destination${destination})} th:text${i}/a /li li classpage-item th:classappend${routePage.last} ? disabled : a classpage-link th:href{/route/list(page${routePage.number 2}, keyword${keyword}, destination${destination})}下一页/a /li li classpage-item th:classappend${routePage.last} ? disabled : a classpage-link th:href{/route/list(page${routePage.totalPages}, keyword${keyword}, destination${destination})}末页/a /li /ul /nav /div /body /html这个列表页使用了Bootstrap进行快速布局。关键点在于th:each循环渲染线路卡片。th:href动态生成详情页链接。th:if控制分页组件的显示。搜索表单使用GET方法参数会体现在URL中便于分享和刷新。分页逻辑是Thymeleaf结合Spring DataPage对象的一个典型应用需要注意页码的转换前端从1开始后端从0开始。详情页 (/templates/route/detail.html)div classcontainer mt-4 th:object${route} h1 th:text*{title}/h1 img th:src{*{coverImage}} classimg-fluid rounded mb-3 alt封面 div classrow div classcol-md-8 !-- 使用th:utext渲染富文本详情注意XSS风险 -- div th:utext*{detail}/div /div div classcol-md-4 div classcard div classcard-body h5 classcard-title行程信息/h5 p th:text目的地 *{destination}/p p th:text天数 *{days} 天/p p th:text价格¥ ${#numbers.formatDecimal(price, 1, 2)}/p p th:text余位 *{stock}/p a th:href{/order/create(routeId*{id})} classbtn btn-success btn-lg w-100立即预订/a /div /div /div /div /div详情页的关键是使用th:object绑定整个route对象子元素可以用*{...}语法简化表达。对于富文本详情必须使用th:utext来正确显示HTML格式。5. 开发中的常见“坑”与解决之道在实际开发这个系统的过程中我遇到了不少典型问题。这里总结一下希望能帮你提前避坑。5.1 Thymeleaf模板渲染问题静态资源CSS, JS, 图片无法加载现象页面样式错乱浏览器控制台报404错误找不到/css/style.css等文件。原因Spring Boot默认将静态资源放在classpath:/static/、/public/等目录下。Thymeleaf中引用这些资源时需要使用Thymeleaf的链接表达式{...}并且路径不能以/开头除非你配置了上下文路径。正确写法link th:href{/css/bootstrap.min.css} relstylesheet。注意这里的{}会帮你处理应用上下文Context Path。如果你把CSS文件放在src/main/resources/static/css/下这样写就能正确映射。排查技巧在浏览器中右键“检查”查看网络Network标签页看资源请求的完整URL是什么与你的预期是否一致。表达式不生效页面直接显示${...}或*{...}现象页面原样输出span th:text${user.name}这样的代码。原因最常见的原因是HTML文件没有引入Thymeleaf的命名空间。或者你访问的路径没有被Spring MVC的控制器处理而是直接作为静态文件访问了比如直接双击打开HTML文件。解决确保HTML文件html标签有xmlns:thhttp://www.thymeleaf.org。并且一定要通过启动SpringBoot应用在服务器环境下访问如http://localhost:8080/route/list。表单提交后数据绑定到对象失败现象Controller方法中接收到的对象属性全是null。原因表单中input标签的name属性必须与后台Java对象的属性名严格一致包括大小写。例如对象有userName属性表单中必须是nameuserName。Thymeleaf的优雅写法使用th:field*{userName}。它会同时生成id,name,value属性并与绑定的对象模型同步是首选方式。input typetext th:field*{userName} classform-control !-- 等价于生成 -- input typetext iduserName nameuserName value classform-control5.2 数据库与JPA相关陷阱ddl-auto: update的误用现象在开发初期修改实体类后表结构自动更新了但已有数据可能丢失或出现字段映射错误。建议在开发初期可以设为update方便迭代。但在进行重要数据操作前或实体结构稳定后应改为validate。永远不要在生产环境使用create或create-drop。正式的数据库变更应通过数据库迁移工具如Flyway或Liquibase来管理。N1查询问题现象在列表页显示线路及其关联的订单数量时控制台打印出大量SQL1条查询线路N条分别查询每条线路的订单数。原因在Thymeleaf中遍历routes并在循环内调用route.getOrders().size()时如果orders关系是懒加载FetchType.LAZY就会触发N次额外的查询。解决使用JOIN FETCH在Repository的查询方法上使用Query(SELECT r FROM TourRoute r LEFT JOIN FETCH r.orders WHERE ...)一次性抓取关联数据。使用实体图EntityGraph注解方式定义需要一次性加载的关联属性。在Service层处理如果关联数据复杂可以先查询出主实体列表再根据ID批量查询关联数据在内存中组装减少SQL次数。日期时间处理问题Java 8的LocalDateTime存入MySQL后再读出来时间可能不对。解决确保数据库连接URL中设置了正确的时区如serverTimezoneAsia/Shanghai。实体类字段可以使用JsonFormat用于JSON序列化和DateTimeFormat用于表单绑定来格式化。5.3 事务管理与并发控制订单超卖问题场景多个用户同时抢购最后一个旅游名额。简陋方案在Service方法中先select stock判断0然后update stock stock - 1。在高并发下多个请求可能同时读到stock1都通过判断导致超卖。解决方案数据库悲观锁select ... for update。但性能损耗大不推荐用于高并发。数据库乐观锁在TourRoute表增加一个版本号字段version。更新时带上版本条件update tour_route set stockstock-1, versionversion1 where id? and version?。如果更新行数为0说明版本已变库存已被其他请求修改本次下单失败。这是推荐的做法。在Service方法上添加Transactional确保查询库存和更新库存在一个数据库事务中至少能保证一致性。事务不生效现象方法上加了Transactional但抛出异常后数据还是被保存了。常见原因异常类型非RuntimeException或Error。默认只回滚这些异常。可以配置Transactional(rollbackFor Exception.class)。方法不是public的。Spring AOP代理基于接口或CGLIB对非public方法的事务注解可能失效。在同一个类中一个非事务方法A调用了同一个类中的事务方法B事务不会生效。因为这是通过this调用而不是代理对象调用。5.4 部署与性能调优初步打包与运行使用Maven命令mvn clean package打包会在target目录生成一个可执行的jar文件内嵌Tomcat。运行java -jar your-project.jar。可以通过--server.port8081指定端口。生产环境建议虽然内嵌Tomcat方便但对于资源敏感的生产环境更常见的做法是打成war包部署到外部的Tomcat服务器以便于统一管理、监控和资源隔离。Thymeleaf模板缓存开发时在application.yml中设置spring.thymeleaf.cachefalse这样修改HTML后无需重启应用刷新页面即可生效。生产环境务必设置为true以提升性能。静态资源优化Spring Boot默认映射了静态资源。对于图片、CSS、JS等可以考虑使用CDN内容分发网络来加速访问减轻应用服务器压力。只需将模板中的资源链接改为CDN地址即可。这个基于SpringBoot和Thymeleaf的旅游网站项目从技术选型到模块设计再到具体实现和问题排查覆盖了一个典型Java Web毕设所需的核心技能点。它不仅仅是一个可以运行的代码集合更体现了一种清晰、分层、易于维护的架构思想。在实现过程中你会深刻体会到从数据库设计、业务逻辑编写到前端交互的完整链路这对于巩固Java Web知识体系非常有帮助。如果你能在此基础上进一步集成Spring Security实现更安全的权限控制或者尝试将部分页面改用Vue.js来实现体验真正的基于REST API的前后端分离那你的技术视野和项目深度又会再上一个台阶。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价