☰
SSM+JSP+MySQL羽毛球平台实战:从XML配置到emoji存储
2026/10/9 7:34:59 网站建设 项目流程

简介:这是一套面向计算机专业本科生的Java毕业设计实战项目,基于SSM(Spring+SpringMVC+MyBatis)框架与JSP前端技术构建,专为毕设选题、课程设计及Java进阶学习者提供开箱即用的羽毛球主题社交平台解决方案。资源完整覆盖用户端与管理员端双角色功能:用户可完成注册登录、浏览新闻、场地预约与评论、在线留言、个人资料管理;管理员可进行用户/场地/新闻/留言等全维度后台管控。压缩包共48.8MB,含可直接运行的Java源码、MySQL数据库脚本、详细开发说明文档及配套软件工具,文件类型以.java业务逻辑类、.jsp页面模板、.sql建表语句和.docx文档为主,结构清晰、注释规范,已通过导师验收并完成全流程调试。目前已有132人下载学习,适合需要真实项目练手、理解MVC分层架构、掌握前后端交互与数据库设计的初学者与毕设学生。

1. 这不是又一个“学生管理系统”:SSM+JSP+MySQL 羽毛球交流平台,为什么能跑通、能上线、还能真被球友用?

你搜“Java毕业设计”,满屏是“图书管理系统”“学生成绩管理”“宿舍管理系统”——功能雷同、界面简陋、数据库字段凑数、连登录都卡在 session 失效上。但这个标题里的「羽毛球交流平台」不是贴标签:它天然带真实业务约束——球友约场要实时查空闲场地、发帖带图片和地理位置、评论需按热度排序、私信得支持未读标记、用户等级得靠积分动态计算。这些需求倒逼你必须把 SSM(Spring + SpringMVC + MyBatis)的事务边界划清楚、JSP 的 EL 表达式和 JSTL 标签用到位、MySQL 的索引和字符集配合理、甚至得处理utf8mb4下 emoji 存储这种“小问题”。这不是练手项目,是微型社交产品雏形。适合大三下到大四上、已写过 Servlet/JDBC 小项目、正卡在“怎么把框架串成闭环”的同学——它不教你 Spring Boot 自动装配,但让你亲手拧紧每一颗螺丝:从web.xml的 DispatcherServlet 注册,到mybatis-config.xml里 typeAliases 的路径映射,再到 JSP 页面里<c:forEach>嵌套<c:if>的渲染逻辑。做完它,你简历上写的“熟悉 SSM 框架”才不是空话。


2. 从零搭起骨架:SSM 三层结构怎么分、为什么这么分、每个配置文件改哪几行

2.1 为什么坚持用 XML 配置而非注解全开?——这是毕业设计的“可控性刚需”

很多教程一上来就@SpringBootApplication+@RestController,看似省事,但毕业答辩时老师问:“你的事务控制在哪一层生效?MyBatis 的 SqlSession 是怎么被 Spring 管理的?DispatcherServlet 怎么知道该把/post/list转给哪个 Controller?”——你答不上来,就暴露了“只会抄代码”。而 XML 配置(spring-context.xml、spring-mvc.xml、mybatis-config.xml)像一张透明电路图:

  • spring-context.xml定义 Service 层 Bean 和事务管理器(<tx:annotation-driven/>必须配transaction-manager="transactionManager");
  • spring-mvc.xml只管 Controller 和视图解析器(InternalResourceViewResolver的prefix="/WEB-INF/jsp/"和suffix=".jsp"不能错);
  • mybatis-config.xml专注 SQL 映射(<typeAliases>里package="com.yumao.entity"比<typeAlias>逐个写更安全)。

提示:别信“XML 过时论”。毕业设计评审看的是你对容器生命周期的理解深度,不是你用了多少新语法。

2.2web.xml是整个项目的“总开关”,漏掉这 3 行就启动失败

<!-- web.xml --> <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 1. 加载 Spring 上下文 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-context.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 2. 配置 DispatcherServlet --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 3. 字符编码过滤器(关键!否则中文存库乱码) --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>

逻辑说明:

  • 第 1 步ContextLoaderListener加载spring-context.xml,创建 IoC 容器(Service/Dao 层在此初始化);
  • 第 2 步DispatcherServlet加载spring-mvc.xml,创建 Web 容器(Controller 层在此加载),<load-on-startup>1</load-on-startup>确保它早于其他 Servlet 启动;
  • 第 3 步CharacterEncodingFilter必须放在最前(<url-pattern>/*</url-pattern>),否则 POST 请求中文参数进 Controller 就是??。

参数说明:

  • contextConfigLocation的classpath:前缀表示从src/main/resources目录找配置文件;
  • DispatcherServlet的<url-pattern>/</url-pattern>是 RESTful 风格基础,但注意:它会拦截所有请求(包括.jsp),所以后续 JSP 页面必须放在/WEB-INF/jsp/下(外部无法直接访问,由 Controller 跳转);
  • forceEncoding=true强制覆盖 request/response 的编码,比只设encoding=UTF-8更可靠。

2.3 MyBatis 的SqlSessionFactoryBean配置:别让数据库连接成为“玄学”

<!-- spring-context.xml 中 MyBatis 部分 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/badminton?useUnicode=true&amp;characterEncoding=utf8mb4&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true&amp;useSSL=false"/> <property name="user" value="root"/> <property name="password" value="123456"/> <property name="initialPoolSize" value="5"/> <property name="maxPoolSize" value="20"/> <property name="minPoolSize" value="3"/> <property name="acquireIncrement" value="2"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.yumao.dao"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean>

逻辑说明:

  • 数据源用c3p0(非 HikariCP)是因为毕业设计环境简单,c3p0 的日志更友好,出错时能看到连接池状态;
  • JDBC URL 中useUnicode=true&characterEncoding=utf8mb4解决中文和 emoji 存储(羽毛球表情 🏸 必须支持);serverTimezone=Asia/Shanghai避免 MySQL 8.0+ 的时区报错;allowPublicKeyRetrieval=true&useSSL=false是本地开发绕过 SSL 认证的务实选择(生产环境必须配 SSL);
  • MapperScannerConfigurer自动扫描com.yumao.dao包下所有接口,无需手动注册 Mapper Bean,但要求 DAO 接口名必须与mapper/*.xml文件名一致(如PostDao.java对应PostMapper.xml)。

参数说明:

  • initialPoolSize=5:应用启动时预创建 5 个连接,避免首请求慢;
  • maxPoolSize=20:最大连接数,设太高易占满 MySQL 连接数(默认 151,需show variables like 'max_connections';查);
  • mapperLocations="classpath:mapper/*.xml":指定 MyBatis XML 映射文件位置,classpath:表示src/main/resources/mapper/目录。

3. JSP 页面不是“写完就能用”:EL 表达式、JSTL 标签、表单提交的三重校验链

3.1 JSP 页面必须声明的 3 个指令:少一行就编译失败

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>羽毛球交流平台 - 首页</title> </head> <body> <h1>欢迎,${sessionScope.user.username}!</h1> <c:if test="${not empty sessionScope.user}"> <a href="${pageContext.request.contextPath}/logout">退出登录</a> </c:if> </body> </html>

逻辑说明:

  • <%@ page contentType="text/html;charset=UTF-8" %>告诉浏览器用 UTF-8 解析 HTML,否则中文标题显示为方块;
  • <%@ taglib prefix="c" ... %>引入 JSTL Core 标签库,<c:if>、<c:forEach>才能用;
  • <%@ taglib prefix="fmt" ... %>引入格式化标签,<fmt:formatDate>显示时间才不报错;
  • ${sessionScope.user.username}从 Session 作用域取值,比request.getAttribute("user")更安全(避免页面跳转时丢失);
  • ${pageContext.request.contextPath}动态获取项目上下文路径(如/badminton),避免硬编码/badminton/logout导致部署到 ROOT 时 404。

参数说明:

  • contentType="text/html;charset=UTF-8"中charset=UTF-8是关键,缺了它,JSP 编译后 HTML 的<meta charset="UTF-8">也救不了乱码;
  • uri="http://java.sun.com/jsp/jstl/core"是 JSTL 1.2 的标准 URI,Tomcat 8+ 自带,无需额外 jar;
  • sessionScope明确指定作用域,防止同名变量在 page/request/session/application 中冲突。

3.2 表单提交的“防重复提交”实战:JSP + Controller + MySQL 三级联动

用户点击“发布帖子”按钮,网络延迟导致他连点两次,后端若没处理,就会插入两条一模一样的记录。解决方案不是前端加disabled(可绕过),而是服务端幂等:

Step 1:JSP 页面加唯一 token

<c:set var="token" value="${sessionScope.token}" scope="page"/> <c:if test="${empty token}"> <c:set var="token" value="<%=java.util.UUID.randomUUID().toString()%>" scope="session"/> </c:if> <form action="${pageContext.request.contextPath}/post/save" method="post"> <input type="hidden" name="token" value="${token}"> <textarea name="content"></textarea> <button type="submit">发布</button> </form>

Step 2:Controller 校验并消费 token

@Controller public class PostController { @RequestMapping("/post/save") public String savePost(@RequestParam String content, @RequestParam String token, HttpSession session, Model model) { // 1. 校验 token 是否存在且匹配 String sessionToken = (String) session.getAttribute("token"); if (sessionToken == null || !sessionToken.equals(token)) { model.addAttribute("error", "请勿重复提交"); return "post/add"; // 返回原页面 } // 2. 消费 token(清空,防止再次使用) session.removeAttribute("token"); // 3. 保存业务数据 Post post = new Post(); post.setContent(content); post.setUserId(((User) session.getAttribute("user")).getId()); postService.save(post); return "redirect:/post/list"; } }

Step 3:MySQL 建唯一索引兜底(防极端并发)

-- 在 post 表中增加唯一约束(假设用 content+user_id 组合防刷) ALTER TABLE `post` ADD UNIQUE INDEX `uk_content_user` (`content`(200), `user_id`);

逻辑说明:

  • JSP 生成 token 存 Session,表单提交时携带,Controller 比对后立即清除——这是最轻量的幂等方案;
  • MySQL 唯一索引是最后一道防线,content(200)是前 200 字符截取,避免长文本索引过大;
  • 不用 Redis 是因为毕业设计环境通常无 Redis,且单机 MySQL 足够应付并发。

4. MySQL 数据库设计:不是建完表就完事,字符集、索引、外键的 3 个生死线

4.1utf8mb4字符集 +collation配置:让“🏸”和“张伟”都能存

MySQL 默认utf8实际是utf8mb3(最多 3 字节),不支持 emoji(需 4 字节)。毕业设计若允许用户昵称带 emoji 或发帖含表情,必须升级:

Step 1:创建数据库时指定字符集

CREATE DATABASE badminton DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Step 2:修改表及字段字符集(已有表需执行)

-- 修改表默认字符集 ALTER TABLE user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 修改特定字段(如 nickname、content) ALTER TABLE user MODIFY COLUMN nickname VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE post MODIFY COLUMN content TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Step 3:JDBC URL 强制指定(已在 2.3 节给出)jdbc:mysql://localhost:3306/badminton?useUnicode=true&characterEncoding=utf8mb4&...

逻辑说明:

  • utf8mb4_unicode_ci比utf8mb4_general_ci排序更准确(尤其多语言混合时),且兼容性更好;
  • CONVERT TO会重建表,锁表时间长,但毕业设计数据量小,可接受;
  • 单独MODIFY COLUMN比ALTER TABLE ... CHANGE COLUMN更安全,不改变字段名。

4.2 索引不是“越多越好”:羽毛球平台的 4 个必建索引

表名字段索引类型原因SQL
userphoneUNIQUE手机号注册,唯一性强制校验ALTER TABLE user ADD UNIQUE INDEX uk_phone (phone);
poststatus, create_time联合索引首页按“审核通过+最新”排序,避免 filesortALTER TABLE post ADD INDEX idx_status_time (status, create_time DESC);
commentpost_id, create_time联合索引查某帖子所有评论并按时间倒序,覆盖查询ALTER TABLE comment ADD INDEX idx_post_time (post_id, create_time DESC);
court_bookingcourt_id, booking_date, start_time联合索引查某场地某天某时段是否可约,精确匹配ALTER TABLE court_booking ADD INDEX idx_court_date_time (court_id, booking_date, start_time);

逻辑说明:

  • user.phone建 UNIQUE 索引,注册时SELECT COUNT(*) FROM user WHERE phone=?就能秒级判断是否已存在,不用查完再 Java 判断;
  • post.status, create_time联合索引,当执行SELECT * FROM post WHERE status=1 ORDER BY create_time DESC LIMIT 10时,MySQL 直接用索引扫描,不排序;
  • comment.post_id, create_time是典型“查询+排序”场景,联合索引让WHERE post_id=? ORDER BY create_time DESC全索引覆盖;
  • court_booking的三字段索引,确保WHERE court_id=5 AND booking_date='2024-06-01' AND start_time='19:00'能走索引,避免全表扫描。

4.3 外键不是“必须加”:什么时候该用、什么时候该砍

该用外键的场景(保证数据一致性):

  • post.user_id→user.id:帖子必须属于真实用户,删用户时应级联删其帖子(ON DELETE CASCADE);
  • comment.post_id→post.id:评论必须关联有效帖子,删帖子时删评论(ON DELETE CASCADE)。

不该用外键的场景(性能/灵活性):

  • user.avatar_url:头像地址是字符串,可能存七牛云或本地路径,无对应表,强行建外键反而增加维护成本;
  • post.tags:标签是逗号分隔字符串(如"双打,业余,南京"),非范式设计,外键无意义。

建外键 SQL 示例:

-- post 表外键 ALTER TABLE post ADD CONSTRAINT fk_post_user FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE; -- comment 表外键 ALTER TABLE comment ADD CONSTRAINT fk_comment_post FOREIGN KEY (post_id) REFERENCES post(id) ON DELETE CASCADE ON UPDATE CASCADE;

逻辑说明:

  • ON DELETE CASCADE是毕业设计常用策略,避免“孤儿记录”(如用户删了,帖子还在);
  • ON UPDATE CASCADE保证主键更新时(极少发生)子表同步,但毕业设计主键通常不更新,可省略;
  • 外键会降低 INSERT/UPDATE 性能(需检查约束),但数据量 <10 万时感知不到,且一致性价值远高于这点损耗。

5. 避坑指南:SSM+JSP+MySQL 羽毛球平台的 5 个血泪经验

5.1 现象:JSP 页面显示${user.nickname}为空,但 Controller 里System.out.println(user.getNickname())有值

原因:JSP 中 EL 表达式默认从pageScope→requestScope→sessionScope→applicationScope顺序查找,而 Controller 用model.addAttribute("user", user)是存到requestScope,但页面跳转用了response.sendRedirect()(重定向),导致 request 作用域丢失。
解决:

  • 方案 A(推荐):Controller 返回String时用return "user/profile";(内部转发),不要return "redirect:/user/profile";
  • 方案 B:重定向时用RedirectAttributes传参:attributes.addFlashAttribute("user", user),它会暂存到 Session,下次 request 读取后自动清除。

5.2 现象:MySQL 插入中文显示???,但SHOW VARIABLES LIKE 'character_set%'全是utf8mb4

原因:JDBC URL 没加characterEncoding=utf8mb4,或 Tomcat 的server.xml中Connector没配URIEncoding="UTF-8"。
解决:

  • 检查 JDBC URL 必须含?useUnicode=true&characterEncoding=utf8mb4;
  • 修改conf/server.xml,在<Connector port="8080" ... />中添加URIEncoding="UTF-8";
  • 重启 Tomcat,再测试。

5.3 现象:MyBatis 执行SELECT * FROM post WHERE status = #{status}报错Parameter 'status' not found

原因:Mapper 接口方法参数没加@Param注解,且方法只有一个参数(此时 MyBatis 3.4+ 可省略,但低版本或复杂参数必须加)。
解决:

// 错误写法(无注解,且 MyBatis 版本 <3.4) List<Post> selectByStatus(int status); // 正确写法(加 @Param) List<Post> selectByStatus(@Param("status") int status);

或升级 MyBatis 到 3.4+ 并确保mybatis-config.xml中<setting name="useColumnLabel" value="true"/>。

5.4 现象:JSP 页面<c:forEach items="${posts}" var="post">报javax.el.PropertyNotFoundException: Property 'title' not found

原因:Post实体类的 getter 方法命名不规范(如getTitle()写成gettitle()),或字段名title与 getter 不匹配(如字段postTitle但 getter 是getTitle())。
解决:

  • 检查实体类:字段private String title;必须对应public String getTitle() { return title; };
  • 使用 IDE 自动生成 getter/setter(IntelliJ 快捷键Alt+Insert→Getter and Setter),避免手写错误;
  • 在mybatis-config.xml中开启日志:<setting name="logImpl" value="STDOUT_LOGGING"/>,看 SQL 执行时是否报Could not find setter for title。

5.5 现象:Tomcat 启动报java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet

原因:Maven 依赖范围错误,spring-webmvc依赖的scope被设为test或provided(Tomcat 已提供,但实际需要打包进 WAR)。
解决:

  • 检查pom.xml,确保spring-webmvc无<scope>标签或为compile:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.37</version> <!-- 删除 <scope>test</scope> 或 <scope>provided</scope> --> </dependency>
  • 清理 Maven:mvn clean compile package,重新生成 WAR。

6. 进阶技巧:用 MySQL 视图 + JSP 分页 + SSM 缓存,把羽毛球平台响应速度提 3 倍

6.1 用 MySQL 视图简化复杂查询:把“用户发帖数+最新帖子标题”合成一张虚拟表

羽毛球平台首页要显示每个用户的发帖总数和最新一条帖子标题,传统做法是:

  • 先SELECT * FROM user;
  • 再对每个用户SELECT COUNT(*), MAX(title) FROM post WHERE user_id=?—— N+1 查询,100 个用户就是 101 次数据库交互。

用视图一步到位:

CREATE VIEW user_post_summary AS SELECT u.id AS user_id, u.username, u.avatar_url, COALESCE(p.post_count, 0) AS post_count, p.latest_title FROM user u LEFT JOIN ( SELECT user_id, COUNT(*) AS post_count, MAX(title) AS latest_title FROM post GROUP BY user_id ) p ON u.id = p.user_id;

Controller 直接查视图:

// UserPostSummary.java 实体类 public class UserPostSummary { private Long userId; private String username; private String avatarUrl; private Integer postCount; private String latestTitle; // getter/setter... } // DAO 接口 List<UserPostSummary> selectAllUserSummary(); // Mapper XML <select id="selectAllUserSummary" resultType="com.yumao.entity.UserPostSummary"> SELECT * FROM user_post_summary ORDER BY post_count DESC LIMIT 20 </select>

优势:

  • 一次查询返回全部数据,无 N+1;
  • 视图逻辑在数据库层,业务代码干净;
  • COALESCE(p.post_count, 0)处理 NULL,避免 Java 空指针。

6.2 JSP 原生分页:不用插件,5 行代码实现“上一页/下一页/跳转”

<!-- post/list.jsp --> <c:choose> <c:when test="${empty page.list}"> <p>暂无帖子</p> </c:when> <c:otherwise> <c:forEach items="${page.list}" var="post"> <div>${post.title} - ${post.content}</div> </c:forEach> <!-- 分页导航 --> <div class="pagination"> <c:if test="${page.pageNum > 1}"> <a href="${pageContext.request.contextPath}/post/list?pageNum=${page.pageNum-1}">上一页</a> </c:if> <span>第 ${page.pageNum} 页,共 ${page.pages} 页</span> <c:if test="${page.pageNum < page.pages}"> <a href="${pageContext.request.contextPath}/post/list?pageNum=${page.pageNum+1}">下一页</a> </c:if> <form action="${pageContext.request.contextPath}/post/list" method="get" style="display:inline;"> <input type="number" name="pageNum" min="1" max="${page.pages}" value="${page.pageNum}" style="width:50px;"> <button type="submit">跳转</button> </form> </div> </c:otherwise> </c:choose>

配套 Controller 分页逻辑:

@RequestMapping("/post/list") public String listPosts(@RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "10") int pageSize, Model model) { PageHelper.startPage(pageNum, pageSize); // PageHelper 开启分页 List<Post> posts = postService.selectAll(); // 此时 MyBatis 自动加 LIMIT PageInfo<Post> pageInfo = new PageInfo<>(posts); // 封装分页信息 model.addAttribute("page", pageInfo); return "post/list"; }

关键点:

  • PageHelper.startPage()必须在postService.selectAll()之前调用;
  • PageInfo对象自带pageNum、pages、list等属性,JSP 直接${page.pageNum}取值;
  • 跳转输入框min="1" max="${page.pages}"防止用户输超范围页码。

6.3 SSM 二级缓存实战:MyBatis + Ehcache,让热门帖子查询快 10 倍

MyBatis 一级缓存(SqlSession 级)默认开启,但每次请求新建 SqlSession,无效。二级缓存(Mapper 级)需手动开启:

Step 1:添加 Ehcache 依赖

<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.11</version> </dependency>

Step 2:配置ehcache.xml

<!-- src/main/resources/ehcache.xml --> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true" diskPersistent="false"/> </ehcache>

Step 3:Mapper XML 开启缓存

<!-- mapper/PostMapper.xml --> <mapper namespace="com.yumao.dao.PostDao"> <!-- 开启二级缓存 --> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/> <select id="selectById" resultType="com.yumao.entity.Post" useCache="true"> SELECT * FROM post WHERE id = #{id} </select> </mapper>

效果验证:

  • 首次访问/post/1,SQL 打印日志;
  • 第二次访问同一 ID,日志无 SQL 输出,说明命中缓存;
  • timeToLiveSeconds="600"表示缓存存活 10 分钟,过期自动刷新。

避坑提醒:

  • useCache="true"是默认值,但显式写出更清晰;
  • 实体类Post必须实现Serializable,否则 Ehcache 序列化失败;
  • 更新操作(INSERT/UPDATE/DELETE)会自动清空该 Mapper 的缓存,无需手动干预。

我带过 3 届毕业设计,最常看到的翻车点不是技术不会,而是“以为配好就完事”。比如web.xml里CharacterEncodingFilter放在DispatcherServlet后面,结果中文参数永远乱码;或者mybatis-config.xml忘了<typeAliases>,导致resultType="Post"找不到类。这些坑我都踩过,现在养成习惯:每改一个配置,立刻用curl -X POST测接口,看日志里 SQL 参数是不是对的。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询