news 2026/9/28 5:54:42

微信小程序旅游服务后端:Java Spring Boot与数据库设计实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
微信小程序旅游服务后端:Java Spring Boot与数据库设计实战解析

简介:这份资源是基于微信小程序的旅游服务软件完整项目,后端采用Java与SSM框架开发,配套数据库脚本,面向计算机相关专业毕业设计或初学小程序全栈开发的读者。项目可在IntelliJ IDEA及微信开发者工具中直接导入运行,数据库为MySQL 8.0,环境配置已做精简,适合快速启动学习。压缩包共22个文件,包括19张界面截图、2个源码压缩包和1个SQL数据库文件,整体约61.15MB,结构清晰便于按模块查阅。功能覆盖旅游攻略、旅游资讯、景点搜索、酒店信息、论坛中心、门票与酒店预订、推荐路线、发帖互动及用户管理等,从用户端到后端管理均有体现,可帮助理解小程序与Java后端的数据交互、SSM框架分层设计和数据库建模。目前已有2604人学习下载,对有毕业设计需求或想系统掌握旅游类小程序开发流程的读者具有较高参考价值。

1. 微信小程序旅游服务软件:为什么后端Java开发才是项目源码的重头戏

做旅游类微信小程序的人,十个里有八个卡在同一步:小程序端代码拿到了,数据库文件导入了,后端却怎么都跑不起来。这个标题其实点透了毕设和课设的核心——前端只是皮相,真正的设计工作藏在Java后端和数据库表结构里。这篇文章从项目源码的组织形式出发,讲清楚后端Spring Boot项目怎么搭、数据库怎么设计、小程序怎么对接,再把最容易翻车的几个坑提前给你打上预防针。适合正在做这类项目的学生,也适合想快速上手前后端分离项目实战的初级Java开发。跟着走一遍,你会知道这套方案能不能落地、值不值得往里投入时间。

2. 搭起Spring Boot后端骨架:选型、包结构与第一个能跑的接口

2.1 为什么选Spring Boot + MyBatis-Plus,而不是SSH或Servlet

旅游服务软件这类业务,核心是用户、景点、线路、订单四组关系,业务逻辑并不算复杂,但接口数量多、表与表之间的关联也多。Spring Boot的自动配置和Starter机制能省掉大量样板代码,对拿到项目源码后想快速跑起来的场景特别友好。更重要的一点是,Spring Boot 2.x + MyBatis-Plus的组合在中小型项目和毕业设计里已经是事实标准,网上能搜到的报错和解决方案都很多,遇到问题不至于卡死。

MyBatis-Plus的价值在于把单表CRUD的代码几乎抹平了。景点表、评论表这类低频变更的单表操作,不需要为每个实体写一套insert、select、update,继承一个BaseMapper就完事。旅游项目里最高频的列表分页查询,用它的Wrapper构造条件,两行代码就能写出来。我不建议在这个阶段引入过度复杂的架构。很多人拿到源码跑不起来,往往不是因为功能多,而是因为塞了Dubbo、Redis缓存、消息队列这些重东西。旅游服务软件的核心诉求是能跑、能讲、能演示,技术栈收敛到Spring Boot + MyBatis-Plus + MySQL + 微信小程序原生开发,已经足够撑起整个项目。

2.2 包结构划分:按业务模块划,而非按技术层次划

拿到源码后第一件事是看包结构。我见过不少翻车的项目,把controller、service、mapper按技术栈堆三层,结果一个旅游项目几十个接口全塞在一个Controller里,改一个字段要翻十分钟。常见的做法是按业务模块分包,结构大概是这样:

com.example.travel ├── config // 全局配置:跨域、拦截器、文档配置 ├── controller // 对外接口:auth、spot、line、order ├── service // 业务逻辑 │ └── impl ├── mapper // MyBatis-Plus的Mapper接口 ├── entity // 数据库实体 ├── dto // 前端入参对象 ├── vo // 返回给前端的视图对象 └── common // 统一返回结构、异常处理

controller按auth、spot、line、order四个模块拆开,每个Controller只负责一类资源的接口。entity和dto分开是很多新手容易忽略的细节——如果数据库字段直接暴露给前端,一旦表结构调整,小程序端就得跟着改。用VO把返回字段裁剪一下,是前后端分离项目实战里最基本的解耦手段,答辩时也能讲出设计依据。

2.3 最小可运行配置:pom.xml与application.yml

不管项目源码长什么样,最终跑起来都靠这两个文件。先看pom.xml的核心依赖,我用Maven坐标的方式列出来:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-ui</artifactId> <version>1.7.0</version> </dependency>

spring-boot-starter-web提供MVC能力,mybatis-plus-boot-starter负责数据库操作,mysql-connector-java在运行期加载驱动。springdoc是Swagger的OpenAPI实现,毕设答辩时直接打开/swagger-ui.html就能看到一个可交互的接口文档页面,比现场翻代码讲更直观。注意MyBatis-Plus 3.5.x配Spring Boot 2.7是经过大量项目验证的搭配,版本不要随便升到4.x,否则很多配置项会变。

application.yml里最关键的三个配置是数据源、MyBatis-Plus的驼峰映射、日期格式:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/travel_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

url里必须带serverTimezone=Asia/Shanghai,否则MySQL 8.x会直接报时区错误,这是最常见的启动失败原因之一。map-underscore-to-camel-case打开后,数据库的spot_name字段能自动映射到实体的spotName属性,不用写繁琐的resultMap。日志输出打开,开发阶段每个SQL都会打印到控制台,排查问题时这是第一手线索。

到这里,一个最小后端已经具备跑起来的条件。写一个测试接口验证:

@RestController @RequestMapping("/api/health") public class HealthController { @GetMapping public Result<String> health() { return Result.success("travel backend is running"); } }

调用GET /api/health,能返回success就说明骨架通了。这里的Result是统一返回包装类,后面的所有接口都会复用这个结构。我不建议在这个阶段急着往下写业务,先把跨域配置和全局异常处理加上。小程序端本身没有CORS机制的限制,但如果你用H5方式调试页面,CORS就绕不开。在config包下加一个WebMvcConfigurer实现类,addCorsMappings里放行所有路径和本地开发端口即可。

提示:如果启动时提示8080端口被占用,先执行netstat -ano | findstr 8080找到占用进程,再决定是换端口还是清理进程。

至此,项目源码的后端部分已经从一堆文件变成了一个能响应的服务。接下来进入真正有设计含量的部分——数据库表结构,这决定了景点、线路、订单三块业务能不能在答辩时讲出逻辑闭环。

3. 旅游业务数据模型:用户、景点、订单三张核心表的设计细节

3.1 用户表与微信登录字段的设计

不含支付功能的旅游小程序,用户表不需要存密码。核心字段是openid、昵称、头像、手机号。openid是微信体系下用户的唯一标识,小程序端通过wx.login拿到code,后端用code换openid,这张表就是整个登录体系的锚点。

CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `openid` varchar(64) NOT NULL COMMENT '微信openid', `nickname` varchar(64) DEFAULT NULL COMMENT '昵称', `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `gender` tinyint(1) DEFAULT 0 COMMENT '性别 0未知 1男 2女', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `deleted` tinyint(1) DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

openid字段一定要加唯一索引。同一用户重复调用登录接口时,后端应该按openid去更新用户信息,而不是重复插入。deleted字段配合MyBatis-Plus的逻辑删除,用户注销时不会物理删除数据,历史订单还能关联上。create_time和update_time直接由数据库维护,不需要在实体里手动赋值,MyBatis-Plus的insert和update语句会自动跳过这两个字段。

3.2 景点与线路:别把两张表合成一张

旅游项目的核心资源是景点和线路。景点是静态资源,线路是动态组合。新手常见的设计错误是把线路直接做成一个字段存景点ID列表,比如line_detail字段存"1,2,3,4",看起来方便,但要查某个景点被哪些线路包含时,必须用LIKE '%2%'去模糊匹配——不仅慢,还会匹配错误。正确的做法是线路主表加一张线路-景点关联表:

CREATE TABLE `line_spot` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `line_id` bigint(20) NOT NULL COMMENT '线路ID', `spot_id` bigint(20) NOT NULL COMMENT '景点ID', `sort_order` int(11) DEFAULT 0 COMMENT '游览顺序', PRIMARY KEY (`id`), KEY `idx_line_id` (`line_id`), KEY `idx_spot_id` (`spot_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='线路景点关联表';

sort_order字段记录景点的游览顺序,这是旅游线路区别于一般商品列表的地方。查询线路详情时,按line_id过滤、sort_order排序,就能还原出一条完整的游览动线。关联表是数据库多对多关系的标准拆法,答辩时能讲清楚这一点,比堆技术名词更让老师信服。

景点表本身相对简单,但要注意几个字段的类型取舍。景点描述用TEXT,不要用VARCHAR(255),因为真实景点的介绍文案动辄几百字。封面图URL用VARCHAR(512),现在云存储的URL普遍带签名参数,长度经常超255。景点经纬度用DECIMAL(10,6)而不是FLOAT,避免浮点精度导致地图定位偏移。

3.3 订单表:价格快照与状态机的设计价值

订单表是整个项目里数据设计含金量最高的部分。旅游订单的特点是下单时的价格和出行时的价格可能不同,因此必须做价格快照。如果订单表只存line_id,再去关联查询当前价格,一旦后台改了线路价格,历史订单就失真了。

CREATE TABLE `order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL COMMENT '订单号', `user_id` bigint(20) NOT NULL COMMENT '用户ID', `line_id` bigint(20) DEFAULT NULL COMMENT '线路ID', `spot_id` bigint(20) DEFAULT NULL COMMENT '景点ID,单景点门票时使用', `title` varchar(100) NOT NULL COMMENT '订单展示名称快照', `cover` varchar(512) DEFAULT NULL COMMENT '封面图快照', `price` decimal(10,2) NOT NULL COMMENT '成交单价快照', `quantity` int(11) DEFAULT 1 COMMENT '数量', `total_amount` decimal(10,2) NOT NULL COMMENT '成交总价', `contact_name` varchar(20) NOT NULL COMMENT '联系人', `contact_phone` varchar(20) NOT NULL COMMENT '联系电话', `travel_date` date DEFAULT NULL COMMENT '出行日期', `status` tinyint(2) DEFAULT 0 COMMENT '0待支付 1已支付 2已完成 3已取消', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `deleted` tinyint(1) DEFAULT 0, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_user_id` (`user_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='旅游订单表';

title、cover、price这三个快照字段是重点。下单那一刻的线路名称、封面、价格被复制到订单表里,之后后台修改线路信息,订单数据不受影响。这种做法在电商系统里叫快照模式,是答辩时实打实的设计亮点。status字段用tinyint存数字状态值,比用字符串更节省空间,也便于后期扩展状态机。建议在Java代码里用枚举类维护这些状态值,而不是在业务代码里写0、1、2这种魔法数字。

3.4 数据库文件交付:SQL脚本的四个细节

项目源码里带的数据库文件,通常是.sql格式。交付时要注意四个细节。第一,导出要包含CREATE DATABASE语句,很多初学者拿到库文件不知道要先建库。第二,初始化数据要覆盖三个层级:一个测试用户、至少10条景点记录、5条线路记录,这样小程序端一打开就有内容可看。第三,字符集统一用utf8mb4,否则用户昵称里的emoji表情会插入失败。第四,SQL文件里不要包含本地绝对路径或数据库密码明文,保护基本信息。

导入数据库时最常见的报错是排序规则不兼容。utf8mb4_0900_ai_ci是MySQL 8.0的默认排序规则,如果导出用的8.0,而本地跑的是5.7,会直接导入失败。解决办法是导出时在Navicat或命令行里指定排序规则为utf8mb4_general_ci,或者导入前用文本编辑器批量替换文件里的规则串。

注意:数据库文件和你自己的后端代码一样,是项目源码的重要组成部分。答辩前导出一份干净的初始数据备份,比任何讲解都更有说服力。

4. 小程序端与Java后端对接:登录、列表、详情页的最小闭环

4.1 wx.login换openid:会话token的处理流程

微信小程序的登录跟传统网页完全不同,没有cookie机制,也不适合每次请求都拿着openid去查用户表。标准做法是:小程序wx.login拿到code,请求后端/auth/login接口,后端拿着code向微信服务器换openid,再用openid查询或创建用户,最后生成一个自定义token返回给小程序端。小程序端把token存进storage,后续所有请求都在header里带上token。

后端登录接口的Controller层代码大致是这样:

@PostMapping("/login") public Result<LoginVO> login(@RequestBody LoginDTO dto) { String code = dto.getCode(); String openid = wechatService.code2openid(code); User user = userService.findOrCreate(openid); String token = jwtUtil.generateToken(user.getId()); return Result.success(new LoginVO(token, user)); }

code2openid方法封装了向微信服务器发请求的逻辑,用Spring的RestTemplate即可,不需要额外引入HTTP库。微信接口返回的是JSON,其中openid和session_key是核心字段。session_key不要返回给小程序端,它是后续解密手机号等敏感操作的密钥,暴露出去会有安全风险。token生成用JWT而不是UUID,因为JWT自包含,后端不需要把token存在内存里,验证时用密钥解析即可,适合小程序这种无状态请求模型。JWT载荷里只放用户ID和过期时间,过期时间建议2小时,旅游类应用用户使用频率不高,过期后重登一次成本很低。

小程序端的请求封装也很关键。wx.request每写一次就要重复设置header、处理错误码,所以常见做法是封装一个request.js统一管理:

const request = (url, method = 'GET', data = {}) => { return new Promise((resolve, reject) => { wx.request({ url: getApp().globalData.baseUrl + url, method: method, data: data, header: { 'Authorization': 'Bearer ' + wx.getStorageSync('token') }, success: (res) => { if (res.statusCode === 401) { wx.navigateTo({ url: '/pages/login/login' }); reject(res); } else if (res.data.code === 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); reject(res.data); } }, fail: reject }); }); };

这段代码的逻辑是:所有请求自动带上Authorization头,后端拦截器对未携带token或token过期的请求返回401,小程序端统一捕获401并跳转登录页。业务状态用code=0表示成功,非0时用toast直接展示后端返回的msg。这样前端不需要为每个接口单独写错误处理,这个模式在前后端分离项目里是通用做法,能把接口对接的代码量减少三分之一。

4.2 景点列表分页:后端参数与小程序端渲染的配合

旅游小程序首页通常是景点列表,涉及分页、搜索、排序三个参数。后端接口设计时,分页参数我习惯用一个统一的PageDTO作为入参:

public class PageDTO { private Integer pageNum = 1; private Integer pageSize = 10; private String keyword; private String sort; }

pageNum从1开始,pageSize上限设置20,防止有人一次性拉全量数据把数据库拖垮。keyword用于按名称模糊搜索景点,sort支持default和price两种排序模式。后端用MyBatis-Plus的Page对象做分页查询:

public Page<SpotVO> getSpotPage(PageDTO dto) { LambdaQueryWrapper<Spot> wrapper = new LambdaQueryWrapper<>(); if (StringUtils.hasText(dto.getKeyword())) { wrapper.like(Spot::getName, dto.getKeyword()); } Page<Spot> page = spotMapper.selectPage( new Page<>(dto.getPageNum(), dto.getPageSize()), wrapper); Page<SpotVO> result = new Page<>(dto.getPageNum(), dto.getPageSize()); result.setTotal(page.getTotal()); result.setRecords(spotConverter.toVOList(page.getRecords())); return result; }

LambdaQueryWrapper是MyBatis-Plus的条件构造器,spotMapper.selectPage第一个参数是分页对象,第二个是查询条件。这里把实体转成VO再返回,避免数据库字段直接暴露给前端。total总数对小程序端的滚动加载特别重要——当已加载条数大于等于total时,就该停止上拉分页请求。小程序端用onReachBottom实现触底加载,维护pageNum和pageSize两个data字段,每次请求返回后累加列表数据而不是覆盖。这里有一个新手常犯的错误:pageNum放在data里,但请求回调里忘记在成功后再+1,导致同一页数据被重复加载。

4.3 图片资源与后端静态资源映射

旅游项目的图片量很大,景点封面、线路详情图、用户头像,每张图都走后端转发不现实。常见做法是后端只存图片URL,图片本身放云存储。开发阶段,图片放到后端项目的static目录下,通过Spring Boot的静态资源映射访问。Spring Boot默认把classpath:/static/映射为根路径,所以把upload文件夹放在resources/static/upload下,访问http://localhost:8080/upload/spot1.jpg就能直接拿到图片。

图片上传接口是另一个容易踩坑的点。小程序端用wx.uploadFile上传,后端接收MultipartFile。Spring Boot的单个文件大小默认限制是1MB,景点高清图动辄3MB以上,必须提前调大限制:

spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB

max-file-size限制单个文件,max-request-size限制整个请求体。文件上传成功后,返回给前端的应该是可访问的完整URL,而不是一个相对路径。我踩过这个坑:接口返回了"upload/20240601.jpg",小程序端直接往域名后面拼接却404,原因就是没有正确拼接请求根路径。

注意:上线前图片资源必须迁移到对象存储并开启CDN加速,本地static目录方案只适合开发和演示环境。

5. 微信小程序旅游后端避坑指南:5个让我翻车的真实案例

5.1 真机预览请求全部失败,但开发工具里一切正常

现象:小程序在开发者工具里调后端接口全部成功,一换成真机预览,所有请求全部超时报错。

原因:微信小程序的网络请求有严格的域名限制。开发者工具里可以勾选"不校验合法域名"来跳过限制,但真机上这个开关不起作用。后端localhost地址在真机上根本找不到,而且小程序要求所有请求域名必须是HTTPS且在后台配置过白名单。

解决:开发阶段用手机连电脑的局域网IP访问后端,把后端启动host改成0.0.0.0,前端baseUrl改成http://局域网IP:8080。要真机调试HTTPS效果,就用映射工具把本地的8080端口映射成一个公网HTTPS地址,再把域名加到小程序后台的request合法域名里。最终上线前必须配好备案域名和SSL证书,云厂商一般都有免费证书申请入口,在Nginx里配置HTTPS是固定套路,这部分不是玄学,照着官方文档配一遍就能跑通。

5.2 订单时间总是差了8小时

现象:后端返回的create_time字段,小程序端显示比本地时间慢了8小时。数据库里存的时间是正确的,响应JSON里却变成了带T的UTC格式。

原因:Jackson在序列化LocalDateTime时默认使用UTC时区。后端服务器的系统时区、MySQL连接串里的serverTimezone、Jackson的时区配置三者不一致,就会造成时间偏移。

解决:保持三重配置一致。第一,application.yml里加spring.jackson.time-zone=GMT+8和date-format=yyyy-MM-dd HH:mm:ss。第二,MySQL连接串保持serverTimezone=Asia/Shanghai。第三,实体里的时间字段加@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="GMT+8")注解兜底。改完这三处,重启后端再测一遍时间就对齐了。

5.3 数据库连接池耗尽,页面随机卡死

现象:小程序端高频请求后,后端日志出现"Connection is not available, request timed out",部分接口偶发性超时,重启后才恢复。

原因:这是典型的数据库连接池被打满。某个Service方法内加了事务注解,事务内又调用了外部HTTP接口,HTTP接口响应慢,数据库连接被事务长时间占用。默认连接池大小只有10,10个连接都被慢请求占满后,新请求全部排队等连接。

解决:第一,排查所有@Transactional方法,事务内不要做远程HTTP调用,先完成数据库操作再调外部接口,或者用编程式事务精确控制边界。第二,把连接池调大并加监控,Spring Boot默认使用HikariCP:

spring: datasource: hikari: maximum-pool-size: 30 minimum-idle: 10 connection-timeout: 30000

把maximum-pool-size调到30只是缓解症状,根因还是事务内不要做远程调用。可以用一个AOP切面把执行超过1秒的事务方法打印到日志里,快速定位慢事务,才能真正解决问题。

5.4 用户连续点击下单,生成了两条重复订单

现象:用户在小程序端双击"立即预订"按钮,后端收到两个几乎同时到达的下单请求,数据库里出现了两条相同的订单记录。

原因:前端只做了按钮loading状态,但loading生效前的瞬间,两个请求已经发出。后端没有做任何幂等校验,insert语句被重复执行。

解决:订单表上已经建了order_no唯一索引,但如果订单号生成逻辑放在事务内用时间戳生成,同一毫秒的两次请求仍可能撞号。常见的做法是前端在点击下单时生成一个requestId传给后端,后端判断requestId是否处理过,处理过就直接返回已有订单。更稳的方案是在订单表上建(user_id, line_id, travel_date)的联合唯一索引,让重复插入直接抛异常,再在Service层捕获异常返回"请勿重复操作"。这个方案不依赖前端配合,数据库层直接兜底。

5.5 图片加载白屏,控制台报错"图片链接未授权"

现象:小程序端打开景点详情页,轮播图区域白屏,控制台提示下载图片失败。

原因:图片URL是后端拼接的,域名可能是localhost或IP地址,微信小程序对网络图片有缓存和域名校验机制。更隐蔽的原因是图片URL中包含了&符号,被解析成多个参数,实际访问的URL被截断了。

解决:第一,所有图片URL统一返回完整路径,即"https://域名/" + 相对路径,不要返回相对路径让前端自己拼。第二,后端生成URL时对查询参数做encodeURIComponent编码,前端拿到后用decodeURIComponent还原,避免特殊字符被截断。第三,如果用了云存储,图片域名必须加入小程序后台的downloadFile合法域名,否则真机上永远加载不出来。

6. 项目验收前的三个验证技巧:让代码在答辩时不出丑

到这里,项目已经能跑起来了。但"能跑"和"能演示"之间还有一段距离。我总结三个自己验收前必做的验证动作,每个都能提前暴露问题。

第一个是接口全量冒烟测试。用Postman或Apifox把项目里的所有接口按业务链路串起来跑一遍,从登录拿到token,到创建订单、查询订单、取消订单,覆盖整条主流程。重点观察返回状态码和响应耗时。我习惯在Postman里设一个全局变量token,登录接口里用一段test脚本自动提取token,后续请求全部引用{{token}},这样能真实模拟前端调用顺序。

第二个是事务回滚验证。找一个修改类接口,故意让SQL执行失败,比如给一个不存在的ID传参,看接口是否正确返回错误码,以及数据库里是否发生了部分更新。这个验证直接对应答辩时老师最常问的问题:"如果下单流程里第二步失败了,第一步的订单会留下来吗?"在Service层加上@Transactional并验证回滚后,你就能有底气地回答。

第三个是日志检查。把后端控制台日志打开,跑一遍核心流程,看有没有MyBatis打印出select *全量查询,有没有重复执行的相同SQL。全量查询在景点列表这种接口里非常常见,原因通常是实体关联字段没做懒加载。把慢SQL日志打开,你会发现很多接口的耗时集中在某几张表的关联上,这时候在JOIN字段上补索引,比加Redis缓存更直接有效。

最后分享一个我的习惯:答辩前一周,把数据库导出一份干净的备份,放到项目源码的database目录下。这样不管演示时数据被改成什么样,随时能一键还原回初始状态。这个习惯救过我很多次,也希望帮到你。

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

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

STM32嵌入式设备实现阿拉伯语LCD动态显示的完整方案

1. 项目拆解&#xff1a;阿拉伯语显示到底难在哪里1.1 阿拉伯语书写的三个“反常识”规则如果你在嵌入式设备上显示过中文&#xff0c;大概率会觉得“显示阿拉伯语不就是再做一套字库嘛”。真做起来你会发现&#xff0c;事情远没有这么简单。阿拉伯语有三个完全不同于英文和中文…

作者头像 李华
网站建设 2026/9/28 5:54:34

响应式网站技术新手入门:3个档位报价单拆解,避开改需求拖一周的坑

响应式网站技术新手入门:3个档位报价单拆解,避开改需求拖一周的坑 上周刚跟一家做建材出口的公司谈完,对方老板拿着另一家公司的报价单,指着上面那行“响应式适配费:3000元”问我:“为什么我明明只要改个手机端按钮颜色,对方说技术架构要重构,拖了一周还没动静?这钱到底花在哪了?”…

作者头像 李华
网站建设 2026/9/28 5:54:28

5年老兵揭秘:推广策划书模板从零搭建避坑指南

5年老兵揭秘:推广策划书模板从零搭建避坑指南 域名服务器搞不懂?别慌,这行十年,我见过太多老板被这一关卡住。 其实核心就两点:服务器买错配置,域名没绑解析。 今天把推广策划书模板从零搭建的账算清楚,让你心里有底。 方案类型与适用场景…

作者头像 李华
网站建设 2026/9/28 5:54:21

Keyboard Shortcuts 配置 TaoToken:settings.json 骨架与验证动作

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 5:54:23

网站怎样做301跳转图解步骤避坑指南

网站怎样做301跳转图解步骤避坑指南 找建站公司怕被坑高价?别慌,其实很多“高深”的技术问题,拆开看就是几行代码的事。比如 网站怎样做301跳转 ,这不仅是SEO的基础,更是保护你品牌权重的关键动作。很多小白一听到“301”就头大,觉得需要找专家花大价钱处理,结果交出去一堆费用,最后发现就是改个配置…

作者头像 李华
网站建设 2026/9/28 5:53:29

南宁网站开发价格全解析:备案图解步骤避坑指南

南宁网站开发价格全解析:备案图解步骤避坑指南 很多老板盯着南宁网站开发价格表,心里却慌得一批,不是怕贵,是怕备案流程一头雾水。明明预算定了,域名服务器也买了,结果卡在工信部备案环节,网站迟迟打不开,客户流量全跑隔壁去了。别急,今天不整虚的,直接上 图解步骤…

作者头像 李华