ARTICLE DETAIL

资讯详情

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

SpringBoot+MyBatis+MySQL实战:游戏介绍系统开发与部署全解析

SpringBoot+MyBatis+MySQL实战:游戏介绍系统开发与部署全解析 这个项目是我帮学生做的一个课程设计名字叫《逃跑吧少年》介绍系统。说白了就是一个游戏官网式的信息展示平台把角色图鉴、地图玩法、攻略资讯这些内容做成一个能看能管的完整站点。技术栈选了SpringBootMyBatisMySQLSpringBoot负责把整个Web应用的骨架搭起来MyBatis负责和数据库打交道前端用Thymeleaf做服务端渲染一套典型的单体应用结构。这篇文章会从源码结构、代码讲解、部署文档三条线把整个项目过一遍适合正在做Java课设或毕设的同学也适合想搞懂SpringBoot完整开发流程的初学者。1. 项目简介与技术选型从零认识这个系统1.1 逃跑吧少年介绍系统的功能画像先把这个系统到底要做到什么程度说清楚。逃跑吧少年是一款非对称对抗类的休闲手游玩家分逃生者和追捕者两个阵营。我们做的介绍系统定位是游戏内容展示与信息管理主要拆成前台和后台两块。前台面向普通访客必须包含几个核心模块首页轮播图用来展示游戏主视觉图角色图鉴把逃生者、追捕者的角色属性、技能说明做成卡片列表地图玩法对游戏内几张地图做图文介绍攻略资讯编辑可以发布游戏攻略和活动公告。这些模块的共性是只读展示加上列表-详情的信息流模式非常适合用服务端渲染来做。后台面向运营人员功能就是登录验证、角色信息的增删改查、攻略文章发布、轮播图管理。后台管理是这类系统的核心价值所在因为纯静态页面没法维护内容必须有一个内容管理系统来支撑数据的持续更新。整条业务链路就是管理员在后台录入数据数据落到MySQL前台页面通过后端接口读取数据并渲染到页面上。数据库层面我设计了四五张表核心的包括角色表、资讯表、管理员表角色表存姓名、阵营、定位、技能描述、图片路径这些字段资讯表存标题、正文、封面图、发布时间、状态。字段设计尽量简单避免冗余因为课程设计的时间有限复杂外键和联表查询能省则省。1.2 为什么选SpringBoot做这类系统技术选型没有高大上的理由就是够用、好上手、生态成熟。SpringBoot最大的价值在于自动配置以前的Spring项目要写一堆XML配置SpringBoot把这些全部封装好了引入web依赖就能直接跑一个内嵌Tomcat。这对快速交付一个课程设计来说太重要了时间应该花在业务代码上而不是跟配置死磕。MyBatis的选择则是出于对SQL控制的偏好。JPA虽然写起来更省事但在多表关联和复杂查询时反而容易失控。MyBatis把SQL明明白白写在XML里出问题时一眼就能看出来哪里错了。对一个包含列表搜索、条件过滤、排序分页场景的系统来说手写SQL的效率并不低而且可读性强。索引优化和慢查询定位也更直观。前端为什么用Thymeleaf而不是Vue这里多说一句。如果做前后端分离意味着要搭建Vue工程、处理后端跨域、还要考虑打包后的静态资源部署对单人课程设计来说链条太长。Thymeleaf的好处是SpringBoot默认支持HTML页面里直接写th:each、th:text这些标签就能循环渲染数据部署时和jar包一起打包带走省掉一套前端工程。想改成前后端分离也容易项目结构里Controller层返回JSON和返回页面的写法天然兼容后续要扩展随时可以拆。2. 源码结构硬核拆解一个请求从页面到数据库的完整旅程2.1 项目目录与基础结构源码结构这一块先看整体目录再逐层拆开讲。src/main/java/com/example/escapedboy ├── controller │ ├── CharacterController.java │ ├── NewsController.java │ └── GuideController.java ├── service │ ├── CharacterService.java │ └── impl │ └── CharacterServiceImpl.java ├── mapper │ ├── CharacterMapper.java │ └── NewsMapper.java ├── entity │ ├── Character.java │ └── News.java ├── common │ ├── Result.java │ └── GlobalExceptionHandler.java └── EscapedBoyApplication.java src/main/resources ├── application.yml ├── mapper │ ├── CharacterMapper.xml │ └── NewsMapper.xml ├── static │ ├── css │ ├── js │ └── images └── templates ├── index.html ├── character │ └── list.html ├── news │ ├── list.html │ └── detail.html └── admin └── login.html这个结构是标准的Controller-Service-Mapper三层架构。Controller负责接HTTP请求Service处理业务逻辑Mapper操作数据库。很多初学者觉得分层是多余的直接把SQL写Controller里不就行了。实际经历过就明白分层最大的优势是让改动可控。比如后面要给角色查询加缓存只需要改Service层Controller和Mapper都不动这才是三层架构存在的意义。实体类的核心写法也要注意数据库字段名是下划线风格Java属性是驼峰风格MyBatis的mapUnderscoreToCamelCase配置打开后就能自动映射。日期字段用LocalDateTime配合注解格式化输出比老的Date类型好用得多。2.2 Controller层入口设计Controller是请求进系统的第一道门。我习惯把页面跳转和数据接口分开写返回页面用Controller返回JSON数据用RestController这样逻辑清晰。角色图鉴列表控制器的写法大概是这样的Controller RequestMapping(/character) public class CharacterController { Autowired private CharacterService characterService; GetMapping(/list) public String list(Model model) { ListCharacter characters characterService.listAll(); model.addAttribute(characters, characters); return character/list; } GetMapping(/detail/{id}) public String detail(PathVariable(id) Integer id, Model model) { Character character characterService.getById(id); model.addAttribute(character, character); return character/detail; } GetMapping(/api/list) ResponseBody public Result apiList() { return Result.success(characterService.listAll()); } }注意路径的设计原则/list和/detail/{id}是页面跳转返回视图名给Thymeleaf/api/list是数据接口返回JSON给后续可能要做的移动端或管理系统用。这样的设计让一个Controller同时兼顾页面渲染和API输出后面的部署和二次开发都会顺手很多。Model传参是Thymeleaf渲染的基础model.addAttribute(characters, characters)就相当于把数据放到请求作用域里HTML里可以用${characters}直接取。这里有个初学者经常踩的坑返回视图名时千万别加RestController否则返回的字符串会被当作JSON正文输出页面直接白屏显示一行路径名。2.3 Service层与事务边界Service层是业务逻辑的核心接口加实现类的写法是我一贯坚持的做法。接口定义方法签名实现类写具体逻辑好处是后续做单元测试时可以轻松mock实现类做扩展时也能在不改动调用方的前提下替换实现。角色查询的Service实现大概这样Service public class CharacterServiceImpl implements CharacterService { Autowired private CharacterMapper characterMapper; Override public ListCharacter listAll() { return characterMapper.selectAll(); } Override Transactional public boolean addCharacter(Character character) { return characterMapper.insert(character) 0; } }事务注解需要重点提一下。Transactional不是随便加的只有涉及写操作的方法才考虑加。比如新增角色时要同时更新角色表和操作日志表两步必须同时成功或同时回滚这时事务才有意义。如果只是查询方法加上事务反而白白增加开销。事务边界放在Service层是最合适的Controller层不用管事务Mapper层的操作也不应该单独标事务因为一个业务方法可能调用多个Mapper方法只在Service层统一控制才能保证原子性。Service层还有一个容易忽视的点就是参数校验。数据从前端传过来不能直接放心用空值、超长、类型不对都要拦在Controller或Service入口。规范的做法是Controller做基础的不为空、格式校验Service做业务规则校验。比如新增角色时检查角色名是否重复这种校验依赖数据库查询必须放在Service层。2.4 Mapper层与SQL管理Mapper层负责数据库操作接口定义方法XML写SQL。接口用Mapper标注或者启动类加MapperScan都可以我习惯在启动类加MapperScan一次扫完省得每个接口都写注解。一个常见的角色查询Mapper接口public interface CharacterMapper { ListCharacter selectAll(); Character selectById(Param(id) Integer id); int insert(Character character); }对应的XMLselect idselectAll resultTypecom.example.escapedboy.entity.Character select * from t_character where status 1 order by sort_order asc /select select idselectById resultTypecom.example.escapedboy.entity.Character select * from t_character where id #{id} /select#{}和${}的区别要说清楚这是个高频面试点也是安全点。#{}是预编译占位符最终会生成?参数MyBatis自动做类型转换和防注入处理${}是字符串拼接直接替换进SQL容易产生SQL注入漏洞。写动态排序字段时确实可能用到${}但要确保字段名称是自己程序拼接的绝不能直接把用户输入传进去。XML文件的位置也值得留意。我在resources目录下建了mapper文件夹application.yml里配置mybatis.mapper-locations: classpath:mapper/*.xml这样MyBatis启动时会自动加载这些SQL映射文件。如果XML文件的namespace写错或者id不匹配启动时会直接报BindingException属于典型的低级错误。2.5 前端页面的组织方式前端用Thymeleaf模板引擎页面放在templates目录静态资源放在static目录。templates下的HTML文件不能直接浏览器访问必须通过Controller路由跳转static下的css、js、图片可以静态访问。这个目录约定很重要新手最容易把两者混在一起。首页的一个循环片段div th:eachitem : ${characters} classcard img th:src${item.imageUrl} classcard-img-top alt角色图片/ div classcard-body h5 classcard-title th:text${item.name}角色名/h5 p classcard-text th:text${item.position}角色定位/p a th:href{/character/detail/{id}(id${item.id})} classbtn btn-primary查看详情/a /div /divth:each类似循环遍历th:text输出文本{/path}是URL表达式会自动拼接上下文路径。这种服务端渲染页面的好处是搜索引擎可以直接抓取内容无需额外做SEO处理。对介绍系统来说内容能被搜索引擎收录本身就是一种推广。如果后面想要前端列表搜索或者轮播切换效果页面里引入Vue或jQuery也是可以的。Thymeleaf服务端渲染和前端交互并不冲突静态资源里放好js文件模板底部引入就行。3. 部署文档全流程从环境准备到线上运行3.1 环境准备清单部署这套系统环境很常规整理出来就是下面这张清单组件版本建议说明JDK1.8 或 11SpringBoot 2.x 搭配 JDK8 最稳Maven3.6 或以上负责项目依赖管理和打包MySQL5.7 或 8.0存储业务数据8.0注意驱动版本服务器CentOS 7 / Ubuntu 20.04生产环境建议LinuxWindows也能跑Navicat推荐付费版用免费的也够数据库可视化管理工具JDK版本这个坑我要专门提醒。现在很多新教程直接教SpringBoot 3.x JDK17但是3.x版本把javax包迁移到了jakarta包很多旧代码习惯全要改对新手不友好。我的建议是个人开发老老实实用SpringBoot 2.7.x JDK8资料最多、兼容性最好、遇到问题搜一下到处都是解决方案。MySQL 8.0的用户注意数据库连接驱动需要是com.mysql.cj.jdbc.Driver老版本驱动是com.mysql.jdbc.Driver配置反了会报ClassNotFoundException。8.0还要在JDBC连接串里带上serverTimezoneAsia/Shanghai否则时区错误会导致日期处理出问题这也是一个典型的新手报错点。3.2 application.yml核心配置配置文件是整个系统能否跑起来的关键我把核心配置拆开逐项说明。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/escapedboy?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root123456 thymeleaf: cache: false prefix: classpath:/templates/ suffix: .html mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.escapedboy.entity configuration: map-underscore-to-camel-case: true logging: level: com.example.escapedboy.mapper: debugserver.port决定应用监听端口一台服务器上跑多个服务时记得改端口。数据库URL这一类配置要注意useUnicodetrue和characterEncodingutf8是中文不乱码的必备参数serverTimezone是MySQL 8.0的必填项。用户名密码用正确的就行生产环境千万别把密码硬编码在配置文件里可以用环境变量或配置中心管理课程设计阶段用明文勾选默认密码也不算大问题。Thymeleaf的cache: false对开发阶段调试特别重要。模板文件修改后马上生效不用重启服务。生产环境建议改回true模板会被缓存减少磁盘IO提升访问速度。MyBatis配置中map-underscore-to-camel-case设为true后数据库的user_name字段就能自动映射到Java的userName属性不用在XML里写一大堆resultMap代码量能少很多。logging.level设为debug可以打印SQL语句排查问题时视图更清晰。3.3 Maven构建与打包发布构建打包是部署前的最后一步操作本身很简单但里面的细节不少人踩过坑。在IDEA右侧的Maven面板中先执行clean清理target目录再执行package进行打包。命令行方式等效写法是mvn clean package -DskipTests-DskipTests跳过单元测试避免因测试环境数据库没配好导致打包失败。如果只是不想执行测试但想编译测试代码可以用-Dmaven.test.skiptrue效果更彻底。打包完成后target目录下会生成两个jar文件一个是以-SNAPSHOT.jar结尾的完整可执行包另一个是.jar.original这个原始包是SpringBoot重新打包之前的文件没有依赖直接删掉或忽略即可。要确认打出来的是可执行包可以用java -jar试运行一下。打包前还要检查Maven的镜像源国内使用阿里云镜像可以让依赖下载速度提升一个量级。在Maven的settings.xml里配置mirror节点指向https://maven.aliyun.com/repository/public亲测有效。依赖下载速度对开发体验影响很大没有配镜像的人等一次全量依赖下载泡杯咖啡回来可能还没好。3.4 服务器部署细节服务器部署我推荐直接用jar包运行不需要额外装Tomcat。SpringBoot内置了Tomcatjava -jar就能直接跑起来这是相比传统war包部署的巨大优势。前台运行一条命令java -jar escapedboy-0.0.1-SNAPSHOT.jar但这种方式CtrlC退出服务就停了。生产环境要后台运行用nohup命令nohup java -jar escapedboy-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod app.log 21 nohup让进程忽略终端挂断信号 app.log 21把标准输出和错误日志都写进app.log文件末尾的表示后台运行。查看日志用tail -f app.log排查启动失败和运行时异常看这里就够了。内存有限的话可以限制JVM参数nohup java -Xms128m -Xmx256m -jar escapedboy-0.0.1-SNAPSHOT.jar app.log 21 -Xms是初始堆内存-Xmx是最大堆内存。个人项目256m以内基本够用别给太小否则会频繁GC导致卡顿。服务器还需要开放防火墙端口。CentOS上用firewalld命令是firewall-cmd --zonepublic --add-port8080/tcp --permanent加firewall-cmd --reload。等这些步骤都完成浏览器访问http://服务器IP:8080就能看到首页了。数据库的3306端口不建议对公网开放只允许本机访问安全别忽略。4. 高频踩坑实录这些问题我全都替你趟过4.1 端口冲突与启动失败最常见的启动失败报错是Port 8080 was already in use症状很直接应用还没能起步就被操作系统拒绝了。解决方式就是先把占用端口的进程找出来杀掉。Linux下排查lsof -i:8080 kill -9 进程PIDWindows下排查netstat -ano | findstr 8080 taskkill /PID 占用端口的PID /F还有一种情况是端口并没有被别的进程占用但你之前启动过同一个应用没杀干净。SpringBoot启动时检测到端口被占用会启动失败先去查后台java进程ps -ef | grep java把可疑进程处理掉再启动。如果是自己写的项目里配置了server.servlet.context-path比如设成了/escapedboy那访问路径就不再是根路径了URL变成了/escapedboy/。这个不算故障但很多人部署后访问不了就是被这个绊倒的开发时记得确认context-path到底配没配。4.2 数据库连接与编码问题数据库相关的报错就那几类先说最常见的Access denied for user rootlocalhost本质是用户名或密码错误。密码里如果有特殊字符比如、#、$在YAML里要加双引号或者单引号包裹否则解析会出错。YAML解析有这种情况密码配置包上引号最保险。Communications link failure这个报错常见于MySQL 8.0。原因通常是URL连接串没加serverTimezone参数或者MySQL服务本身没有启动。还有一种可能数据库账号只授权了localhost访问而项目配置的URL用了192.168.x.x这种局域网IP这时要改用user%的授权方式。中文乱码问题在MySQL里非常典型。数据库连接串加characterEncodingutf8是第一步建表时指定utf8mb4编码是第二步网页端的meta charsetutf-8是第三步三重保障缺一不可。特别是MySQL 8.0默认字符集虽然是utf8mb4但旧库从5.7迁移过来的话可能还是latin1中文读写直接就乱成一团。数据库驱动版本不匹配也会导致连接失败MySQL 5.7用com.mysql.jdbc.Driver没问题但换成MySQL 8.0后必须升级驱动并改成com.mysql.cj.jdbc.Driver同时Maven依赖里的mysql-connector版本要同步升到8.x。版本不匹配的表现极其迷惑有时报ClassNotFound有时报SSL连接错误排查时需要先确认驱动版本。这里顺手整理一个速查表以后出了问题可以直接对号入座。问题现象可能原因解决方案Access denied用户名密码错误、权限未授权核对密码授权账号运行Communications link failureMySQL未启动、serverTimezone缺失启动MySQL补时区参数中文乱码连接串无编码参数、库表非utf8连接串加utf8改库表字符集ClassNotFound驱动MySQL版本与驱动不匹配升级驱动改用cj驱动连接超时网络不通、端口未开放检查服务器安全组和防火墙4.3 SpringBoot版本过高带来的兼容性问题很多人在搜索资料的时候会遇到一大波新教程SpringBoot 3.x JDK17的组合看着很新鲜但照搬之后项目反而起不来。这里想清楚一个问题提纲和样例代码大多是基于SpringBoot 2.x写的SpringBoot 3把javax包整体替换成了jakartaController、 Entity里的注解全都要把javax.*改成jakarta.*这套迁移逻辑对新手来说得不偿失。如果你的项目是基于SpringBoot 3.x开发的而网上找到的生成模板都是2.x最简单的处理方式是在IDEA创建项目时直接选择SpringBoot 2.7.x版本JDK保持1.8这样不用处理任何包名的兼容问题。旧版本项目模板支持还是很多的选择的时候留意Group的依赖版本即可。另一个版本问题是依赖之间的冲突。比如引入一个第三方库时这个库版本较旧依赖的Spring版本和你项目版本不兼容。表现就是启动时出现大量NoSuchMethodError或BeanCreationException。这类问题很难一次性想到是哪里的版本问题解决思路是用Maven Helper插件查看依赖树找到冲突的包在pom里对指定依赖做exclusion排除或者升级第三方库版本。排查版本问题的利器就是Maven Helper的Show Dependencies图形化依赖树功能。4.4 静态资源与前后端配置细节后端接口都能正常调了页面却一片空白或者样式全部丢失这种问题的频率极高。最常见原因就是模板路径和静态资源路径对应不上。Thymeleaf页面里引用css的方式link th:href{/css/style.css} relstylesheet不加th:而直接写href/css/style.css在有context-path的情况下就会失效因为资源实际路径是/projectname/static/css/style.css。统一用th:href的URL表达式就能自动拼接context-path省去很多麻烦。还有一种情况是项目继承了WebMvcConfigurationSupport或者自己加了EnableWebMvc导致SpringBoot默认的静态资源映射被覆盖所有css和js全部404。这种情况的解决办法是保留SpringBoot的自动配置不要轻易自定义addResourceHandlers或者在自己定义的同时手动把classpath:/static/加进去。前后端分离的坑也不少见。Vue项目打包后把dist里的文件放到static目录能解决静态展示问题但Vue Router如果用了history模式刷新页面会出现404。原因是没有服务器端转发需要在SpringBoot里写一个控制器把所有非静态资源路径转发到index.html或者改用hash模式让URL带#来避开路径转发。这个细节我在帮朋友调项目时碰到过一次第一次排查确实得花点时间。说回我这套项目因为是Thymeleaf服务端渲染天然避开了前后端分离的路由问题部署和运行都简单了不少。如果你的需求复杂到必须上Vue那建议把前端工程单独部署到NginxAPI通过Nginx反向代理转发给SpringBoot分工明确后续各自构建和部署都不会互相影响。5. 部署文档与代码讲解的配套经验做了这么多遍项目我还摸索出一套项目交付三件套的整理方法源码目录一份完整注释版本、一份部署文档、一份代码讲解文档。很多人觉得代码写完了项目就交差了实际上对课设、毕设、甚至企业内部分享来说配套文档的价值不低于代码本身。部署文档写的核心要点是把环境变量、配置文件、启动命令、验证方式四件事写清楚。最怕的是文档里写着配置好环境即可这种话每一步具体命令是什么、运行后会看到什么LOG、端口是否正常都能写进去才叫合格文档。我在每个项目的部署文档里固定加一个验证清单小节把访问首页能看到轮播图后台能登录新增角色后前台能看到这种关键路径列出来任何人按这个清单走一遍就知道部署是否真正成功。代码讲解文档的写法也有一套方法论。按页面请求-Controller-Service-Mapper-数据库这条链路来组织比按类逐个讲要清晰得多。我的讲解文档固定包含三块核心业务流程图用文字列出、关键代码片段加注释说明、每个模块的改进扩展点。读者按这条路理解项目逻辑比自己翻一遍源码效率高很多。还有一个细节是源码注释和文档注释的风格统一。变量命名用通俗的英文单词关键业务逻辑加注释Controller的接口方法上标注请求方式和功能说明。不要写那种i就是循环变量的废话注释要写清楚这里的状态过滤决定前台是否展示该角色这类有业务含义的说明。注释的目的是让别人快速理解意图不是告诉别人你写了什么代码。另外Git版本管理习惯建议一开始就养成。每做一个模块提交一次到Git仓库commit message写清楚这轮改动做了什么。万一改坏了代码回滚到上一个commit就能救回来不至于整个项目报废。课程设计中老师最反感看到的时间就是从最终版v1到最终版v8这样命名的文件Git仓库才是正经做项目该用的工具。最后分享一个我做了多轮这类项目沉淀下来的体会这类SpringBoot介绍系统的技术难度并不高核心价值在于把从零到部署的整条链路走通。对我个人来说真正在上面的收获不是某个技术点而是学会了排查问题的思路——遇到异常先看日志日志找关键字定位到具体类和方法再结合上下文判断原因。这套思路比记住任何框架API都值钱。如果正在读这篇文章的你打算把这个项目作为练手的起点我建议你在跑通基础功能之后挑一个模块自己改造一下比如把角色列表改成Elasticsearch搜索或者加上Redis缓存热点数据。折腾的过程才是真正的学习过程。
返回列表