ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Vue+MyBatis+MySQL政务管理系统实战:从数据库设计到前后端联调

SpringBoot+Vue+MyBatis+MySQL政务管理系统实战:从数据库设计到前后端联调 做了这么多年Java后端大大小小的管理系统写过不少SpringBootVue这套前后端分离的组合也早就从“新潮玩法”变成了“行业标配”。这次接到的这个在线政务服务中心管理系统项目代号_nrlwabo看着像是内部工程的命名习惯是2025年最新整理的完整源码技术栈落在SpringBoot、Vue、MyBatis、MySQL上正好是现在大多数Java课程设计、毕业设计和中小型公司内部项目最常用的一套组合。我把整个项目从数据库设计到前后端联调的过程完整过了一遍把里面涉及的核心技术点和容易踩坑的地方都整理出来这篇就当是给大家的实战参考。这个管理系统说白了就是把线下的政务办事大厅搬到线上常见的模块包括用户注册登录、办事指南查看、在线预约、事项申报、进度查询、后台管理员审核、通知公告发布这些。它解决的核心问题是让老百姓少跑腿让审批流程透明化。适合谁看如果你正在用SpringBoot做毕业设计或者在公司里接手一个前后端分离的管理类项目又或者想系统梳理一遍MyBatisMySQL在实际项目里的用法这篇文章应该能帮你省不少时间。1. 项目背景与整体架构思路1.1 这个管理系统到底在做什么政务服务中心系统的业务逻辑其实比普通的企业后台管理系统要复杂一些因为它既要有面向普通用户的前台办事入口又要有面向工作人员的审批后台。前台用户能看公告、查办事指南、在线填表申报、预约窗口办理、随时查看审批进度后台管理员能做用户管理、事项分类管理、审批流转、统计报表之类的操作。拿项目里的核心流程举例一个用户想要办理某项业务常规的路径是注册登录账号 - 在办事指南里找到对应事项 - 查看所需材料和办理条件 - 在线填写申报表单并上传附件 - 提交等待审核 - 在个人中心查看审批进度 - 审核通过后按通知去线下窗口领证或打印结果。这套流程如果做成纸质化需要用户来回跑好几趟而且审批到哪个环节了用户完全不知道。做成系统之后所有状态变化都能实时同步到前端页面用户体验好了工作人员审批效率也高了。这也是这个项目的核心价值所在——用技术手段把线下的繁琐流程数字化、透明化。其实不要被“政务”两个字吓到剥开业务外壳它本质上就是一个典型的RBAC权限管理系统加业务流程审批系统。用户表、角色表、权限表、菜单表、业务申报表、审批记录表这些核心表结构一出来整个系统的骨架就立住了。理解了这一点你会发现很多管理类项目的设计思路是相通的。1.2 为什么选择SpringBootVueMyBatisMySQL这套组合先说后端。SpringBoot现在的地位不用多讲它把Spring那套复杂的XML配置全部简化成了自动配置和Starter依赖开发效率比传统SSM不知道高到哪里去了。哪怕Spring Boot 3.x已经出了很久2025年很多企业内部项目仍然在用Spring Boot 2.7.x原因就是稳定、资料多、排坑容易。持久层选了MyBatis而不是Spring Data JPA我和这个项目的选择是一致的。政务类项目的SQL往往比较复杂特别是审批流查询、多表关联统计这种场景用MyBatis写动态SQL能精确控制每一条语句的执行行为排查问题也更直观。MyBatis的缓存机制和分页插件PageHelper是高频使用点后面我会专门展开讲。前端选Vue的原因更简单组件化开发模式适合后台管理系统这种大量复用表格、表单、弹窗的场景而且Vue的生态在中文社区非常成熟。Vue 2和Vue 3目前在项目里都有应用这个源码用的是Vue 2 Element UI的组合虽然是上一代技术栈但胜在稳定跑起来不折腾。数据库用MySQL也是顺理成章的选择。MySQL的InnoDB引擎支持事务、行级锁对于政务系统这种读多写少、偶尔有并发提交的业务来说完全够用。配合索引优化和慢查询日志单表千万级数据量也能扛得住。2. 数据库设计与MyBatis实战要点2.1 数据表设计思路与建表经验拿到项目第一步是看数据库脚本。这个系统的表结构大概分三块第一块是用户权限域包括用户表、角色表、菜单权限表、用户角色关联表、角色菜单关联表第二块是业务数据域包括办事事项分类表、事项信息表、申报单表、申报材料表、审批记录表、预约记录表第三块是系统支撑域包括通知公告表、操作日志表、文件上传记录表。这里有个设计细节我觉得很好申报单表和审批记录表是分开的。一开始如果不仔细想很容易把审批状态直接做成申报单的一个字段审核通过就改状态不通过也改状态。但这么做有两个问题审批历史丢失用户想知道这个单子谁审的、审了几次、每次意见是什么完全没有记录。状态字段被反复覆盖一旦出现并发操作或者误操作很难追溯责任人。把审批记录单独建表之后每一条审批操作都是insert不是update天然保留完整操作链路主表里的当前状态字段只是冗余存储最新状态方便列表查询时直接过滤。这种“主表存状态流水表存轨迹”的设计在很多业务系统里都是通用套路。MySQL建表时的几个关键点也顺便说一下所有表必须有主键推荐用BIGINT自增或者雪花ID不要用UUID做主键字符串主键在数据量大时索引性能明显差。业务字段要设置合适的长度和默认值比如状态字段用TINYINT存数字枚举不要用VARCHAR存中文查询和扩展都麻烦。创建时间created_at、更新时间updated_at这种审计字段全表统一后面排查数据问题的时候能救命。给高频查询字段加索引比如申报单表里的user_id、status、create_time但索引不是越多越好每个索引都会拖慢写入速度。2.2 MyBatis配置、缓存与分页插件用法MyBatis在这个项目里要处理的核心问题有三个参数映射、动态SQL、结果集映射。先讲配置。Spring Boot整合MyBatis需要引入mybatis-spring-boot-starter然后在application.yml里做基础配置mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.nrlwabo.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case这个配置我建议新项目一律打开。数据库字段名下划线命名是DBA的偏好Java实体类属性名驼峰命名是开发者的习惯打开这个开关之后select返回的结果集就能自动把create_time映射成createTime少写一大堆resultMap。log-impl配置成StdOutImpl可以在控制台直接打印SQL语句和参数开发阶段排查问题非常好用。但生产环境一定要关掉不然日志量太大还可能泄露SQL结构信息。再讲缓存。MyBatis自带一级缓存和二级缓存。一级缓存默认开启作用域是SqlSession同一个SqlSession内执行相同SQL直接从缓存取不查数据库。听起来很美好但有个坑如果项目里用了Spring事务或者ORM整合SqlSession的生命周期可能超出你的预期。更常见的问题是你的SQL只要执行了update插入删除操作一级缓存就会清空所以很多开发者根本没感觉到缓存的存在。二级缓存是跨SqlSession的默认不开启需要实体类实现Serializable接口并在Mapper XML里加cache/标签。但我个人建议不要随便开MyBatis二级缓存尤其在业务复杂的政务系统里。一旦开了缓存多表联查的数据一致性很难保证表数据更新了但缓存没失效用户看到旧数据就是事故。真要提速优先优化SQL本身和加索引缓存放到Redis层做可控性更强。分页插件PageHelper是这个项目里必然用到的组件基本用法PageHelper.startPage(pageNum, pageSize); ListOrderInfo list orderInfoMapper.selectOrderList(param); PageInfoOrderInfo pageInfo new PageInfo(list);PageHelper的原理是拦截器它在MyBatis执行查询前拦截Executor把当前线程上下文里保存的分页参数拼接到SQL上自动生成limit语句然后查询完成后还会执行一条count语句统计总数。这里有个经典坑PageHelper.startPage()必须紧跟要分页的Mapper查询语句中间不能插入其他查询因为PageHelper是基于ThreadLocal存储分页参数的只要中间执行了别的SQL那个SQL就会被莫名其妙地加上limit线上出过好几次这种诡异问题。还有一个要注意的是PageHelper虽然自动做了count查询但如果你的SQL本身就带了group by或者distinctcount的结果是错的需要自己写countSql来覆盖这个后面排查章节我再细说。2.3 动态SQL与一对多查询的坑政务审批系统的查询条件非常多用户ID、事项分类、申报状态、时间段、关键字搜索这些条件组合起来能列出几十种可能性。如果每种组合写一条SQLMapper文件会膨胀到无法维护。MyBatis的动态SQL标签就是解决这个问题的select idselectApplyList resultTypecom.nrlwabo.entity.ApplyInfo SELECT * FROM apply_info where if testuserId ! null AND user_id #{userId} /if if testcategoryId ! null AND category_id #{categoryId} /if if teststatus ! null AND status #{status} /if if testkeyword ! null and keyword ! AND (apply_no LIKE CONCAT(%, #{keyword}, %) OR applicant_name LIKE CONCAT(%, #{keyword}, %)) /if if teststartTime ! null AND create_time gt; #{startTime} /if if testendTime ! null AND create_time lt; #{endTime} /if /where ORDER BY create_time DESC /selectwhere标签会自动处理掉第一个AND前缀省去写where 11的丑陋做法。这个写法几乎是后台管理列表查询的标配模板建议大家直接背下来。一对多查询是另一个高频场景比如查询申报单时需要带出该单下所有附件材料。常见做法有两种一种是先在Mapper里查主表列表然后循环去查附表这就是传说中的N1查询问题数据量一大直接卡死另一种是用collection嵌套结果集映射一次SQL把关联数据查出来resultMap idApplyDetailMap typecom.nrlwabo.entity.ApplyInfo id columnid propertyid/ result columnapply_no propertyapplyNo/ collection propertyfileList ofTypecom.nrlwabo.entity.ApplyFile id columnfile_id propertyfileId/ result columnfile_name propertyfileName/ /collection /resultMap这种查询需要主表字段和附表字段别名不能冲突否则映射会错乱常见解决办法是附表字段起别名比如af.id AS file_id。嵌套结果映射虽然高效但当数据量大时会产生重复的主表记录MyBatis底层会根据id去重前提是你必须把主表的id正确映射到resultMap的id上否则所有行的对象都是新建的分页总数和去重逻辑都会出错。3. SpringBoot后端核心功能实现3.1 项目初始化与配置文件的几个坑这个项目既然叫SpringBootVue后端就是标准的SpringBoot工程。我在还原项目结构的时候pom.xml里看到核心依赖有spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java、lombok、druid连接池这些。有同学问为什么用Druid不用默认的HikariCPDruid在国内项目里的优势是监控功能丰富自带SQL监控面板能看到每一条SQL的执行时间、并发数、慢查询统计这在政务项目上线初期排查性能问题时太有用了。HikariCP性能更好但监控弱二选一我一般推荐Druid图个省心。SpringBoot版本的选择这里必须多说一句。如果你下载的源码是Spring Boot 3.x要特别注意JDK版本要求Spring Boot 3要求JDK 17以上而很多课程设计项目环境还是JDK 8。这个版本不匹配的问题我见过无数次启动直接报UnsupportedClassVersionError。遇到这种问题就两个解决办法要么换JDK版本要么把Spring Boot降级到2.7.x同时把javax.*包统一换成jakarta.*包。在2025年这个时间点如果只是为了跑通项目我一律建议用Spring Boot 2.7.18 JDK 8兼容性最好。配置文件里数据库连接这块要注意时区问题spring: datasource: url: jdbc:mysql://localhost:3306/nrlwabo_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.DriverserverTimezoneAsia/Shanghai这个参数必须加MySQL 8.x默认时区是UTC不加的话查出来的时间和实际时间相差8小时用户看到“提交时间”不对直接投诉。characterEncodingutf8也必须加否则中文乱码问题能折腾你一下午。3.2 登录鉴权与权限控制的实现方式政务系统的安全要求比一般系统高登录鉴权和操作日志是刚需。这个项目里的权限控制走的是经典的Shiro或者JWT 拦截器方案我先说JWT这套因为更贴合SpringBoot项目的简单无状态设计。用户登录成功之后后端生成一个JWT token返回给前端前端存到localStorage之后每次请求在header里带上Authorization: Bearer xxx。后端定义一个拦截器继承HandlerInterceptorpublic class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { response.setStatus(401); return false; } // 解析token校验签名和过期时间 Claims claims JwtUtil.parseToken(token.replace(Bearer , )); if (claims null) { response.setStatus(401); return false; } request.setAttribute(userId, claims.get(userId)); return true; } }拦截器注册时要设置放行路径登录接口、注册接口、静态资源、前端页面预览接口这些都要排除在拦截范围之外。这里有个常见错误只拦截了后端接口没配置静态资源放行结果前端部署在同域下时JS、CSS全部被拦截页面光秃秃的。更细的权限控制比如管理员才能访问用户管理接口可以在拦截器里根据当前用户角色做判断也可以配合Shiro/Spring Security做注解式权限校验。我个人实际项目的习惯是小项目用拦截器自定义注解就够了不引入Security那套重框架避免配置复杂度反噬业务开发效率。3.3 文件上传与审批流程接口的设计细节政务申报里用户要上传身份证照片、营业执照扫描件、申请表附件等所以文件上传功能躲不开。SpringBoot处理multipart文件上传其实很简单PostMapping(/upload) public Result upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(文件不能为空); } String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName UUID.randomUUID().toString().replace(-, ) ext; LocalDate today LocalDate.now(); String relativePath /uploads/ today.format(DateTimeFormatter.ofPattern(yyyyMMdd)) / newFileName; // 实际保存到本地磁盘生产环境一般存OSS或FastDFS file.transferTo(new File(System.getProperty(user.dir) relativePath)); return Result.success(relativePath); }这里有几个容易踩的细节文件名必须重命名不能直接用用户原始文件名防上传目录路径穿越攻击。按日期建目录方便后续定期归档清理。上传大小限制要配置SpringBoot默认单文件最大只有1MB得多媒体文件一般需要调大到10MB、50MB不等spring: servlet: multipart: max-file-size: 50MB max-request-size: 50MB审批流程接口的核心逻辑虽然不复杂但状态流转必须严格校验比如只有待审核状态才能执行通过、驳回操作已通过状态不能重复提交。推荐做法是在Service层用枚举定义状态机每次更新前先查当前状态比对后再执行更新更新语句带上WHERE status #{expectStatus}做乐观锁保护。这样就算两个管理员同时点了审核按钮也只有一个人能成功不会出现状态覆盖。4. 前端Vue实现细节4.1 Vue环境配置与项目初始化注意事项前端部分用的是Vue 2 Element UI Vue Router Vuex Axios这套经典组合。2025年用Vue 2确实不算新项目的最佳选择但对这种需要快速交付、稳定运行的源码项目来说Vue 2的生命周期虽然已停止维护但Element UI的成熟度和中文文档对于初学者仍然友好很多高校课程设计还在用它。环境配置是老生常谈但必须讲Node.js版本不能太新Vue 2的项目在Node 17以上版本编译时经常报Error: error:0308010C:digital envelope routines::unsupported。这个报错的根源是Node.js 17之后OpenSSL升级导致哈希算法不兼容。如果你遇到了两个解决办法降级Node.js版本到16.x这是最推荐的。在package.json的scripts里加SET NODE_OPTIONS--openssl-legacy-provider vue-cli-service serveWindows下亲测有效但只能算临时绕过。安装依赖建议用npm或者pnpm跑npm install的时候如果下载特别慢可以配置镜像源。国内环境大家都知道直接用registry.npmmirror.com就行这个不属于任何敏感问题只是常规加速手段。Vue项目初始化的目录结构里核心关注四块api目录统一放接口请求函数router目录管理路由表store目录管理全局状态views目录放页面组件。很多新手通病是把接口请求直接写在页面里导致同一个接口在多个页面重复代码。规范做法是每个模块一个api文件例如api/apply.js里统一导出所有申报相关接口页面里只负责调用并处理返回结果。4.2 路由参数传递与axios请求封装的实战用法Vue Router路由参数传递有两种主流方式这个项目里都用到了路径参数/apply/detail/:id跳转时this.$router.push({ path: /apply/detail/ id })页面通过this.$route.params.id取值。适合详情页这种需要收藏或分享链接的场景。查询参数/apply/list?status1跳转时this.$router.push({ path: /apply/list, query: { status: 1 } })页面通过this.$route.query.status取值。适合列表筛选条件这种临时状态。这里有坑当路由从/apply/list?status1跳转到/apply/list?status2时Vue会复用组件实例不会重新执行created生命周期。如果你是在created里发请求拉数据会发现问题页面URL变了但数据没刷新。解决办法要么在监听$route对象变化时重新拉数据要么在router-view上加:key$route.fullPath强制组件重建。axios请求封装在这个项目里属于必做的基础设施。因为后端接口返回格式是统一的{ code: 200, data: ..., msg: 成功 }所以axios实例需要做响应拦截器统一处理业务码和错误提示service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.msg || 请求失败) if (res.code 401) { router.push(/login) } return Promise.reject(new Error(res.msg)) } return res }, error { Message.error(error.message || 网络异常) return Promise.reject(error) } )请求拦截器里要做的事是每次请求自动带上tokenservice.interceptors.request.use( config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config } )这样前端开发时就不用每个接口手动写header了逻辑统一收口到一个文件里后续要加签名、加密都方便。4.3 网页播放m3u8视频流的处理方式政务中心项目里有一个上传办事辅导视频、播放宣传视频的需求前端面临一个问题视频文件以.m3u8索引文件分段存储浏览器原生video标签不支持直接播放m3u8格式Safari除外。这个热搜词“vue播放m3u8”被很多人搜索确实是一道常见关卡。解决方案是用hls.js库它在浏览器端能通过Media Source Extensions把m3u8协议的视频流转成浏览器可播放的格式。安装npm install hls.js。在Vue组件里的用法import Hls from hls.js export default { mounted() { if (Hls.isSupported()) { const hls new Hls() hls.loadSource(this.videoUrl) hls.attachMedia(this.$refs.video) hls.on(Hls.Events.MANIFEST_PARSED, () { this.$refs.video.play() }) } else if (this.$refs.video.canPlayType(application/vnd.apple.mpegurl)) { // Safari走原生播放 this.$refs.video.src this.videoUrl } } }核心代码就这几行但有两个容易忽略的点视频源必须支持跨域访问服务器响应头要带Access-Control-Allow-Origin否则分片请求会被拦截。播放器容器要有明确的宽高m3u8流自适应分辨率时如果容器高度为0画面会黑屏。如果不想引入额外依赖还有一个偏后端思路把m3u8视频转码成mp4格式再输出给前端。但视频转码非常耗时不适合实时播放场景一般只在视频发布时做离线转码。所以hls.js方案是这类需求的最优选。5. 常见问题与排查技巧实录5.1 MyBatis日志打印与慢SQL排查政务系统上线后最常被吐槽的就是慢慢在哪十有八九是SQL问题。排查SQL问题第一步是能看到SQL。MyBatis通过StdOutImpl打印的日志格式像这样 Preparing: SELECT * FROM apply_info WHERE status ? Parameters: 1(Integer) Columns: id, apply_no, applicant_name, status, create_time Row: 1, A202501010001, 张三, 1, 2025-01-01 10:00:00 Total: 1Preparing后面是预编译后的SQLParameters是对应参数Total是返回记录数。看到这个日志你就能确认SQL是什么、参数是什么、返回了多少条。慢SQL的排查日志建议用MySQL自带的慢查询日志[mysqld] slow_query_log ON slow_query_log_file /var/log/mysql/slow.log long_query_time 1配置后执行时间超过1秒的SQL都会被记录到slow.log然后用EXPLAIN分析执行计划重点看type字段如果出现ALL说明是全表扫描必须加索引优化。还有一类常见问题是MyBatis的Update执行很慢这是热搜词“mybatis update执行慢”对应的场景。我之前遇到过一条更新语句执行了5秒EXPLAIN一看update的where条件字段没有索引导致每次更新都要全表扫。解决方式就是给where条件字段建索引。另外一个更隐蔽的坑是如果表数据量特别大同时更新影响的记录数很多MySQL行锁升级为表锁或者锁竞争激烈更新也会变慢这个可以从SHOW ENGINE INNODB STATUS里看到锁等待信息。5.2 分页插件数据不准确的几个典型场景PageHelper分页是高频功能出问题的概率也最高。我整理了这个项目里遇到的几个典型问题第一个是传参导致的SQL解析错误。当你分页查询的SQL里带有多表join和order by时PageHelper会在原SQL外面包一层count语句有时候这个count语句会因为order by子句或distinct出现语法错误报Unknown column xxx in order clause。解决办法是给PageHelper指定countSql手写一条精确的count语句。第二个是统计总数不准确。PageHelper默认count是SELECT COUNT(0)的形式但如果你的查询SQL里有group by实际返回的物理行数小于逻辑记录数导致总数偏大或偏小前端分页按钮的数量对不上。同样需要手写countSql来修正。第三个是线程池串参数。前面说过PageHelper用了ThreadLocal存分页参数如果你在controller方法里调用了多个Service方法第二个方法里的SQL可能被误分页。排查方法是在关键位置打印日志确认startPage和查询语句之间没有执行其他Mapper。5.3 前端构建部署与接口跨域问题前后端分离项目最后一步是部署。前端代码npm run build之后默认生成到dist目录把dist目录里的文件扔到Nginx配一个站点server { listen 80; server_name yourdomain.com; root /opt/nrlwabo/dist; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里特别注意try_files $uri $uri/ /index.html;Vue是history模式路由刷新前端页面时Nginx要能回退到index.html否则刷新就直接404。路由模式如果用hash模式默认带#则不需要这个配置但URL不好看大部分线上项目都用history。接口跨域问题有两种解法一种是在后端配CORS全局跨域另一种是像上面这样用Nginx将/api/前缀的请求反向代理到后端服务前端请求地址写/api/xxx避开了跨域。实际生产我更推荐Nginx反代方案因为后端不用开放跨域更安全而且可以统一做负载均衡和HTTPS证书配置。开发模式下的跨域在Vue.config.js里配置devServer.proxyconst { defineConfig } require(vue/cli-service) module.exports defineConfig({ transpileDependencies: true, devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } } })pathRewrite的作用是去掉/api前缀因为后端Controller的RequestMapping并没有加/api这个统一前缀如果不去掉请求路径会多一层导致404。这个细节很多新手都要卡一阵子。写在最后的一点实际体会把整个系统从数据库脚本一直跑到前后端部署上线我对这套SpringBootVueMyBatisMySQL的组合又加深了几分认同。它不是性能最强、架构最潮的方案但它是学习曲线最平缓、团队协作成本最低、遇到问题能最快搜到解决方法的组合。政务服务中心这类管理系统的开发重点不在技术炫技而在流程设计的严谨性和大量CRUD操作背后的工程化沉淀用户表、角色表、菜单表的互相绑定审批流的状态机控制文件上传的安全处理以及前端列表页面的筛选重置、分页条、详情弹窗这些重复但必须做好的细节。如果你正打算拿这套源码做二次开发或者写自己的课程设计我的建议是不要停留在“能跑起来”就结束。可以试着做这几个扩展一是给文件上传换成MinIO对象存储体验一下海量文件的存储管理二是把审批流程换成Flowable工作流引擎看看真正的流程自动化怎么设计三是给前端加上数据可视化大屏用ECharts做办事量趋势、分类占比这些图表。每改一块你对整个业务系统的理解就能深一层。这套技术栈在未来几年内依旧会是Java就业市场的常青树面试被问MyBatis缓存、分页插件原理、SpringBoot自动配置的几率依然很高。把这些代码一行行读懂踩过的坑一个个记下来比单纯收藏十个项目都管用。
返回列表