首页 / 资讯中心 / 文章详情

前后端分离物流管理系统实战:SpringBoot+Vue3+MyBatis+MySQL

前后端分离物流管理系统实战:SpringBoot+Vue3+MyBatis+MySQL ★ FEATURED ARTICLE
前阵子帮一个做城配物流的朋友整理内部业务聊着聊着就提到了他们那套还在用Excel和微信报单的调度流程客户打电话下单、调度手写单子、司机跑完回来再人工录系统数据一乱就靠人肉对账。当时我就建议这种场景其实特别适合用一套轻量级的物流信息管理系统来收口——订单、车辆、司机、客户、结算全部放同一个平台里跑。正好手上有个用 Java SpringBoot Vue3 MyBatis MySQL 做的前后端分离物流信息管理系统从需求梳理到落地部署大概两个月时间今天把它拆开聊聊希望给正在做类似项目或准备入行物流系统的同学一点参考。这套系统解决的核心问题很直接把物流业务里散落的角色和单据全部线上化。管理员在后台维护客户和订单调度员看单派车司机在手机上接任务、上报在途状态财务和老板看统计报表。整个过程全部围绕订单的状态流转展开配合车辆、司机、仓库、客户几个基础档案整体并不复杂但麻雀虽小五脏俱全很适合用来理解前后端分离架构在真实业务里的落地方式。如果你正在学 SpringBoot 和 Vue3 整合或者想找一个既有技术含量又不至于太虚的练手项目这篇文章应该能帮到你。1. 整体设计思路为什么是这套技术组合1.1 物流管理系统的核心业务闭环做系统之前先把业务跑一遍这是最重要的一步。物流信息管理系统的核心不是“录入信息”而是“管住状态”。一个订单从客户下进来到调度派车、司机提货、干线运输、到达派送、客户签收中间每一步都要留痕任何人点开系统都能看到当前单子在哪个环节、卡没卡住、谁在处理。围绕这个闭环我把系统拆成五个主要模块订单管理、调度派车运输任务、车辆与司机档案、客户档案、统计报表。订单管理是主流程其他模块都是为订单服务的支撑数据。比如调度模块它的操作对象是“运输任务”任务关联了订单、车辆、司机三个维度派单的瞬间同时影响三张表的状态——订单变成已接单、车辆变成占用、司机变成出发状态。如果不先把业务关系理清楚后面写代码会反复改表结构非常痛苦。前端部分我用的 Vue3配合 Element Plus 做后台管理界面。物流系统这类内部管理系统的页面形态很固定——表格、筛选、表单、详情、统计卡片很少有大面积复杂交互Vue3 的组合式 API 在这种场景下写起来很顺手。后端就是 SpringBoot 标准三层架构MyBatis 做持久层MySQL 存数据。整套技术选型没有任何炫技成分恰恰是这种“稳妥的组合”在实际开发和后期维护中不容易出幺蛾子。1.2 前后端分离的收益与成本这个项目一开始就定了前后端分离没有用 SpringBoot 直接渲染模板页。分离带来两个直接好处。第一是开发节奏快前端后端可以并行推进只要把接口约定好两边各写各的联调时再对齐细节。第二是部署灵活后端可以扔到一台服务器上跑 jar 包前端打包成静态文件用 Nginx 托管后续要加小程序端或者司机 App直接复用后端 API 就行不用动 Web 端。但分离也有代价最典型的就是跨域问题开发阶段前端跑在 5173 端口后端跑在 8080 端口两边端口不一致浏览器默认会拦。这个后面我会专门讲。另外就是权限控制不能只靠页面路由后端接口必须自己做鉴权因为请求是直接打到 API 上的拦截器 JWT 是标配这也是我为什么在项目里坚持做 token 认证而不是只靠 session。技术选型上SpringBoot 的优势不用多说自动配置和生态成熟度摆在那里搭建一个项目基本不用操心环境问题。MyBatis 相比 JPA 更贴合国内中小团队的开发习惯SQL 自己掌控复杂统计查询好写也方便 DBA 后期优化。MySQL 作为业务数据库完全够用物流数据量再大也不至于一上来就上分布式数据库单库单表配合索引和分页足够支撑中小型物流公司的业务。1.3 模块边界与数据流设计我在设计模块时坚持一个原则一个模块只做一类事模块之间通过状态字段和关联 ID 通信不跨表直接操作。比如订单模块不直接改车辆表状态而是提供“订单已派车”的事件由调度模块去联动更新车辆和司机状态。代码里通过 Service 层互相调用实现而不是在 Controller 里写一堆操作这样后期维护时每个类的职责都很清晰。整个系统的数据流大概是这样的客户档案、车辆档案、司机档案这类基础数据由管理员维护它们是订单和任务的“字典数据”。订单创建后状态为“待接单”调度员在待接单列表里看到订单创建运输任务并关联车辆、司机订单状态变成“已接单”。司机执行任务过程中更新任务节点提货、发车、到达订单状态同步推进为“在途”“已送达”最终客户签收后订单变成“已签收”任务关闭车辆和司机释放回可用池。报表模块把自己挂在订单表后面用 group by 语句按时间、状态、客户维度汇总数据。整条链路下来其实没有一个多余的模块每个都对业务有实打实的价值。2. 数据库表设计与订单状态机2.1 核心表结构一览数据库设计是整个项目的地基表结构不对后面业务代码写起来全是别扭。下面是这套系统中实际用到的核心表我按业务模块逐个说。用户表sys_user存的是登录账号字段包括 username、password、real_name、role、phone、status。密码一定不能存明文项目里用的是 BCrypt 加密存储登录时比对加密后的哈希值。角色字段我用的是简单字符串ADMIN、DISPATCHER、DRIVER系统小没必要上 RBAC 那一整套一个角色字段加一个全局拦截器就够用了。客户表customer记录客户公司或个人档案字段有 customer_no、customer_name、contact_person、contact_phone、address、credit_level。物流行业客户就是货主credit_level 是信用评级用于决定要不要接受月结运费这个字段在后续做应收账款统计时会经常用到。物流订单表logistics_order是核心中的核心字段最多包括 order_no、customer_id、order_type零担/整车/仓储、pickup_address、delivery_address、pickup_time、expected_delivery_time、goods_name、goods_weight、goods_volume、goods_quantity、freight_amount、status、remark、create_time。每个字段背后都有业务含义比如 goods_weight 和 goods_volume 直接决定了派什么样的车整车和零担的计费逻辑完全不同。运输任务表transport_task记录一次派车行为字段有 task_no、order_id、vehicle_id、driver_id、plan_start_time、plan_end_time、actual_start_time、actual_end_time、task_status、remark。这张表把订单和车辆、司机关联起来属于典型的关联表查询订单详情时通过它 join 出车辆和司机的信息。车辆表vehicle存的是车辆档案plate_no、vehicle_type、max_load、max_volume、status、driver_id。注意 vehicle 表和 driver 之间有归属关系一辆车有一个常驻司机但调度时也可以临时指派别的司机所以订单关联的是 transport_task 自带的 vehicle_id 和 driver_id而不是通过 vehicle 去反查这样更灵活。最后是操作日志表operation_log记录关键操作的用户、时间、操作内容、IP。物流公司对接客户时经常需要举证“某个单子谁在什么时候操作过”这张表就是用来应付这种场景的成本很低价值不小。2.2 订单状态机设计物流订单的状态流转是整个系统的业务骨架。我定义了一套状态机状态字段 status 用整数表示分别对应1待接单、2已接单、3在途、4已送达、5已签收、6已取消。状态机的设计原则是不是任何一个状态都能跳到另一个状态非法跳转直接拦截报错。合法流转路径是1 → 2 → 3 → 4 → 5以及 1/2/3 → 6取消。比如一个订单还在“待接单”状态时司机不可能上报签收一个“已签收”的订单也不可能重新变成“在途”。这套校验我封装在一个 StatusTransition 工具类里传入当前状态和目标状态返回是否允许。状态机的校验只放在 Service 层没有放在数据库层面。原因是状态字段本身就是一个整数数据库 check 约束写起来麻烦而且报错信息不友好业务代码里校验可以给出“当前订单状态不允许此操作已签收订单不可取消”这类明确提示。不过更新时必须用条件更新防止并发问题后面我会展开讲。2.3 索引设计与查询优化物流系统的数据查询有两个特点一是按订单号、客户名、状态、时间范围筛选是常态二是列表页天然分页很少出现全表扫描的情况。基于这两个特点我建索引时主要考虑了三个方向。第一个是订单号 order_no通常直接精确查询建唯一索引。第二个是 status 和 create_time 的组合索引因为列表页最常见的场景是“按状态过滤 按时间倒序”这个联合索引能大幅减少回表。第三个是 order_id 在 transport_task 表上的外键索引因为订单详情页要 join 任务表查车辆司机信息没有索引的话 join 性能会很差。索引不是建得越多越好物流系统里写操作也不少订单状态频繁变动、任务表持续插入索引过多会拖慢写入。我实际保留的就上面三组外加 customer_id 这个外键索引其他字段查询频率不高直接靠 MySQL 的 where 过滤就行。数据量到几十万条级别时这套索引方案的表现依然稳定。3. 后端核心实现SpringBoot 与 MyBatis 实践3.1 工程结构与统一返回结果后端工程我使用标准的 Maven 多模块可选结构但实际项目里因为业务量不大我直接用了单模块分层controller、service、mapper、entity、common、config每个包职责清晰。启动类配置好 MapperScan 扫描 mapper 接口数据库连接信息放在 application.yml 里用 profile 区分 dev 和 prod 环境避免开发库和正式库打架。所有 Controller 返回的数据结构统一走 Result 包装类包含 code、message、data 三个字段。code 为 0 表示成功非 0 表示业务异常或系统异常。这么做的好处是前端封装 Axios 拦截器时可以统一判断 code不用每个接口各写一套成功失败逻辑。全局异常处理我用了 RestControllerAdvice 加 ExceptionHandler捕获业务异常、参数校验异常、数据库异常返回对应的 Result 包装。这里的核心经验是异常信息不要直接堆给用户参数校验错误提示具体字段数据库异常统一提示“系统繁忙请稍后重试”日志里记录完整堆栈方便排查。3.2 MyBatis 动态 SQL 的实用写法MyBatis 在这个项目里承担了所有持久层操作。XML mapper 与接口一一对应我重点说一下动态 SQL 的几种高频写法因为订单查询条件组合极多订单号、客户名、状态、时间范围、货物类型每次可能只填其中两三个条件。第一种是条件过滤。查询订单分页列表时用一个where标签包裹多个if判断用户传了什么条件就拼什么条件。这里要注意字符串判空用! null and ! 数值类型只判 null。第二种是批量插入。派单时一个订单可能对应多辆车大件货物拆分transport_task 表需要批量插入用foreach标签实现 batch insert配合事务一次性提交。第三种是关联查询。订单列表页需要显示客户名称和车辆车牌用 LEFT JOIN 关联 customer 和 transport_taskresultType 映射到一个 VO 类而不是直接映射实体避免把关联字段硬塞进 entity 里。还有一类常用的是统计查询报表模块的 SQL 基本都是 group by 聚合函数。比如查“每月订单量”就是SELECT DATE_FORMAT(create_time, %Y-%m) AS month, COUNT(*) FROM logistics_order GROUP BY monthMyBatis 里把结果映射到一个 MonthStatVO。这类 SQL 在 JPA 里反而不好写动态 SQL 和原生 SQL 的灵活性是我坚持用 MyBatis 的主要原因。启动时 MyBatis 会扫描 XML 文件并校验 SQL 语法如果 XML 里写错标签或者 SQL 语句本身有问题项目启动阶段就会报错而不是等到接口被调用时才炸出来。这个特性很实用相当于把一部分错误拦截在开发早期。3.3 JWT 鉴权与接口权限控制鉴权方案我用的 JWTJSON Web Token没有引入 Spring Security原因是系统角色少、接口不算多一个拦截器加注解足够。登录接口校验用户名密码成功后生成 token包含 userId、username、role 三个信息设置 24 小时有效期返回给前端。前端拿到 token 后存到 localStorage每次请求在拦截器里把 token 塞进 Authorization 请求头。后端写一个 HandlerInterceptor拦截除了登录接口外的所有请求解析 token 合法性。解析失败返回 401前端收到 401 就跳回登录页。角色权限上我用了自定义注解 RequireRole标注在 Controller 方法上值是允许访问的角色数组。拦截器解析完 token 后把 token 里的角色和注解要求的角色比对不匹配返回 403。比如派单接口标注 RequireRole(DISPATCHER)司机角色即使拿到 token 也调不了这个接口。这套方案简单直接避免了引入 Spring Security 的学习成本接口权限控制也够用了。派单这个操作我强调一下它牵涉多个表的状态变更必须在方法上标注 Transactional。先更新订单状态为已接单再插入运输任务再更新车辆状态为占用再更新司机状态为出车中任何一个环节失败整个事务回滚保证数据一致性。事务里我用的传播行为是默认的 REQUIRED多个 Service 方法调用会自动合并到同一事务中。并发场景下防止重复派单我用的是乐观更新UPDATE logistics_order SET status 2 WHERE id ? AND status 1如果影响行数为 0说明订单已经被别人操作过了直接抛出业务异常提示“订单已被派车”。这个写法比先 select 再 update 安全得多推荐有类似场景的同学直接采用。4. 前端 Vue3 实现与接口联调细节4.1 项目初始化与目录组织前端用 Vite 创建 Vue3 项目UI 组件库选的 Element Plus状态管理用的 Pinia路由用的 Vue Router 4。这几样都是当前 Vue3 生态最主流的组合社区资料多遇到问题好搜解决方式。初始化完成后我把目录做了下面几个文件夹划分api接口定义与请求封装、views页面组件、components公共组件、router路由配置、storesPinia 状态、utils工具函数。路由配置上用了懒加载模式每个页面组件按需引入首屏加载速度会明显好过把所有页面打包进一个 bundle。路由守卫做了一层登录判断没有 token 一律重定向到登录页。菜单结构是固定的静态数据没有做后端动态路由因为系统角色少菜单无非是管理员和调度员看到的东西略有不同用角色字段在前端 v-if 控制就够。这套系统的页面全部围绕“列表 弹窗表单”的形态展开。几个关键页面包括登录页、订单管理列表页、订单创建/编辑弹窗、调度派单页订单详情 车辆司机选择、车辆列表、司机列表、客户列表、统计看板。每一类页面我都尽量复用组件比如订单状态标签、车辆选择器避免到处复制粘贴代码。4.2 Axios 请求封装与拦截器Axios 封装是整个前端联调的基础。我在 utils/request.js 里创建了一个 axios 实例配置 baseURL 指向后端的接口前缀同时设置了超时时间。请求拦截器统一在 header 里注入 token响应拦截器统一处理返回结果code 为 0 时直接返回 data 数据给页面用code 非 0 时弹出错误提示HTTP 状态 401 时清空登录态并跳转登录页网络异常时提示“网络连接失败请检查服务是否启动”。文件上传在物流系统里也常见主要是回传运单照片或签收凭证。axios 上传二进制文件时不能手动设置 Content-Type 为 application/json要让浏览器自动生成带 boundary 的 multipart/form-data 格式这个坑我踩过手动指定了反而会报错。Vue3 里我统一用 Composition API 写业务逻辑。列表页的状态用 ref 和 reactive 管理订单列表是const orderList ref([])查询参数用const queryParams reactive({ pageNum: 1, pageSize: 10, status: undefined, orderNo: })。特别注意reactive 对象在传参给子组件时不要整体解构再传入因为解构会丢失响应性。要用 toRefs 或者直接传整个 reactive 对象。这个坑后面我还要单独讲。调用接口的写法基本是const fetchOrderList async () { loading.value true try { const data await listOrder({ ...queryParams }) orderList.value data.records total.value data.total } finally { loading.value false } }配合分页组件页码或页大小变化时重新调用 fetchOrderList这套模板在几乎每个列表页里都在用所以我把列表拉取逻辑抽出了一个 usePagination 组合式函数减少重复代码。4.3 订单看板页面的完整实现链路订单管理页是整个前端最典型的页面我拿它举例说明前后端如何协作。页面左侧是筛选区包含订单号输入框、状态下拉框、时间范围选择器右侧是表格区和分页器。用户输入筛选条件后点击查询queryParams 更新触发 fetchOrderList 请求后端接口。后端接收 pageNum、pageSize、orderNo、status、beginTime、endTime 这些参数MyBatis 动态 SQL 拼接查询条件PageHelper 自动生成 limit 语句返回分页对象records total。前端拿到 records 渲染表格total 渲染到分页器。状态字段在后端返回的是数字前端在页面里用一个全局映射函数把 1 转成“待接单”并渲染对应颜色的标签——处理这种“状态码转文案”的需求不要在接口层处理放前端统一映射更灵活后端只管返回数字。派单操作弹窗是这个页面的核心交互。点击订单行的“派单”按钮打开弹窗弹窗里展示订单基本信息不可编辑下方是车辆选择器和司机选择器。车辆选择器数据来自后端“可用车辆”接口只返回 status1空闲的车司机只返回 status1 的司机。选择后点击确认调用派单接口后端一整套事务操作完成后刷新列表弹窗关闭。整个过程前端代码并不复杂大部分复杂度都在后端的业务一致性上。统计看板页面用到了 ECharts。后端提供三个统计接口每月订单量趋势、订单状态分布、客户发货量排行。前端在 onMounted 里并行调用三个接口拿到数据后用 ECharts 初始化三个图表。图表响应式处理上加了一个 window resize 监听窗口大小变化时调用 chart.resize()这个细节不处理的话浏览器缩放后图表会变形。5. 踩坑记录与排查思路5.1 前后端联调的跨域问题前后端分离开发时跨域十有八九会遇到。我开发时前端跑在 http://localhost:5173后端是 http://localhost:8080前端请求后端接口浏览器的同源策略直接拦截。解决办法有三种我在项目里都试过。第一种是后端加 CORS 配置注册一个 CorsFilter允许指定来源跨域。适合开发环境但部署后如果前端域名变了要改配置后端重启。第二种是前端 Vite 配置 proxy 代理请求统一走 /api 前缀Vite 开发服务器把请求转发到后端前端代码里看起来就是同源请求。开发环境我用这个舒服且不用动后端。第三种是部署后用 Nginx 反向代理把后端接口和前端静态文件放到同一个域名下彻底避免跨域生产环境用这个。我的建议是开发用 Vite proxy生产用 Nginx后端不要开放全来源 CORS安全又省心。5.2 MyBatis 字段映射与 XML 报错排查场景查询列表返回的 JSON 里customer_id、pickup_address 这些字段有值但对应实体类里驼峰命名的属性却是 null。原因很简单MySQL 字段是下划线命名customer_idJava 属性是驼峰命名customerIdMyBatis 默认不会自动映射。两种解法在 application.yml 里设置 mapUnderscoreToCamelCasetrue 开启驼峰自动映射或者在 XML 的 resultMap 里显式配置 column 和 property 的对应关系。我选的是第一种全局配置一处搞定。XML 文件报错是另一个高频问题。MyBatis 启动时解析 XML 并做语法检查经常遇到的错误是符号在 XML 里被当成标签开头。比较运算里写create_time #{endTime}会直接报 XML 解析失败。解决方案是转义成lt;或用![CDATA[ create_time #{endTime} ]]包裹。另外封装通用查询条件时三个以上相同判断建议抽成 SQL 片段用sql标签定义include引入。还有一个隐藏问题resultType 写的是实体类但查询列别名和实体属性对不上返回的对象某些属性拿不到值。排查这种问题时建议先把 SQL 复制到 Navicat 里执行一遍确认列名是什么再回来看 XML 的映射基本能快速定位是列名错误还是映射配置缺失。5.3 数据库连接、时区与编码问题MySQL 8.x 的连接字符串有一堆参数需要注意。项目刚启动时连接报错 “The server time zone value Öйú±ê׼ʱ¼ä is unrecognized”这就是时区问题因为 MySQL 8 默认使用系统时区而 Java 驱动要求识别标准时区。解决办法是在 JDBC URL 里加上 serverTimezoneAsia/Shanghai。还有一个常见的 SSL 警告或报错MySQL 8 默认开启 SSL但本地开发证书不匹配会报错连接参数里加 useSSLfalse 解决。allowPublicKeyRetrievaltrue 这个参数如果没有加用 caching_sha2_password 认证方式时也会报错建议一并加上。中文乱码问题数据库、表、连接三层的字符集要统一成 utf8mb4。建库时用CREATE DATABASE xxx DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci连接 URL 里加 characterEncodingutf8SpringBoot 配置里加 spring.datasource.connection-init-sqls 不是必须但确保连接参数正确是必须的。如果只是某个字段乱码先检查这个字段的 collation 是否和表一致再检查代码里写入前是否做了编码转换。数据库连接池我用的 HikariCPSpringBoot 2.x 默认集成。配置里我调整了 maximum-pool-size 为 15minimum-idle 为 5这个值根据并发量估算。物流系统并发不高15 个连接足够开太多反而浪费数据库资源出现过数据库连接耗尽报错的基本都是池子配置太大加连接未正常关闭导致的。5.4 Vue3 响应式丢失与表单数据坑Vue3 的响应式虽然比 Vue2 强不少但使用不当还是会丢响应性。我踩过最典型的一个坑是从 reactive 对象里解构属性赋值给一个新变量然后修改这个新变量页面完全不动。比如const form reactive({ name: })然后let name form.name再name 张三这里 name 已经不是响应式的了。正确做法是用 toRefs 解构或者干脆直接操作 form.name。另一个坑是 reactive 对象的属性新增和删除。Vue3 中 reactive 已经可以拦截新增属性了但如果你用 ref 定义了一个对象数组直接refArr.value[0].newField 1这种深层新增在部分场景下不会触发视图更新。解决方案提前在对象里把所有可能出现的字段都声明出来或者用arr.value.splice(0, 1, newObj)整体替换。表单数据坑和组件库相关。Element Plus 的表单校验是异步的提交前校验没通过时按钮还是要禁用。我在项目里写了一个封装提交按钮的 loading 状态在表单校验通过后才会置为 true避免用户连续点击产生重复提交。还有一个常见问题是日期选择器的值格式绑定值是 Date 对象传给后端时要先格式化我用 dayjs 统一转成YYYY-MM-DD HH:mm:ss字符串后端 LocalDateTime 也按这个格式解析不容易出乱子。轮询也是物流系统常用的功能司机端页面上报位置和状态时要定时刷接口。我在订单详情页做了一个简单的轮询每 30 秒调一次查询订单最新状态的接口页面关闭前在 onUnmounted 里清理定时器。如果不清定时器会继续跑接口疯狂请求后端日志一会儿就刷屏。5.5 性能优化与分页查询的坑分页查询第一个坑是 PageHelper 的使用位置。PageHelper 的 startPage 方法必须紧接着下面的第一条 SQL 查询才有分页效果如果中间隔着别的查询语句分页参数会应用到错误的 SQL 上导致数据错乱。正确写法是 startPage 之后马上调用 mapper 方法不能有任何其他查询插队。第二个坑是分页 count 查询的性能。PageHelper 默认会执行一条 count 语句如果主查询 join 了多张表count 也会 join一旦数据量大count 查询可能比列表查询还慢。解决办法是手写独立的 count SQL只统计主表的 id 数量不要 join。或者如果列表查询字段少也可以考虑直接返回全部数据让前端分页但这只适用于数据量很小的场景比如车辆表、司机表订单这种持续增长的表绝对不行。第三个坑是大数据量下的深分页。用户翻到第 100 页limit 1000, 10MySQL 要扫描前 1000 行才能拿到结果越往后越慢。物流订单这种按时间线增长的数据我建议列表页限制最大可查范围或者提供一个“只看最近三个月”的默认筛选既符合业务需求又避免深分页问题。如果确实要深查可以查 id 列表再回表但这属于进阶优化物流中小场景一般用不到。还有一个细节列表查询不要SELECT *只 select 页面需要的字段。订单表字段多含大字段描述全查出来网络传输慢而且转 JSON 也浪费内存。我在 XML 里显式列字段列表实体类完整字段但列表 VO 只映射必要的十几列实测接口响应速度提升明显。6. 一点个人体会这套系统做下来我个人最大的感受是技术栈本身没有什么门槛真正的难度在把物流业务的状态流转理清楚。订单什么时候能派车、什么时候能签收、异常单怎么处理这些规则不明朗代码写得再漂亮都是空中楼阁。所以想动手做类似项目的朋友我的建议是先花时间画清楚业务流程图再开始建表写代码。另外前后端分离项目里接口规范的约定一定要前置。我在项目初期就定了统一返回结构、分页参数名、日期格式、错误码范围前后端照着这个约定开发联调阶段的返工次数会少很多。如果一开始没约定好前端传的字段名和后端接收的字段名对不上两边反复改效率极低。最后分享一个实用的小技巧在本地开发时后端开启 SQL 日志打印mybatis.configuration.log-implStdOutImpl能看到每条 SQL 的执行情况和参数值配合打印出的 SQL 观察数据库实际执行计划基本能解决九成的数据查询问题。等项目上线后关闭这个日志避免日志文件暴增。这套系统的代码量不算大但五脏俱全适合用来完整走一遍从需求分析、数据库设计、后端开发、前端联调到部署上线的全流程。如果你正在学 SpringBoot 和 Vue3拿来练手比单纯刷教程有效得多。
阅读完成 · 觉得有帮助?
咨询建站