当前位置: 首页 > news >正文

Spring Boot 与数据访问全解析:MyBatis、Thymeleaf、拦截器实战

Spring Boot 与数据访问全解析:MyBatis、Thymeleaf、拦截器实战

        结合我们之前学习的 Spring Boot 基础和配置知识,本文将聚焦 “数据访问 + 前端渲染 + 权限控制” 核心链路 —— 从 MyBatis 操作数据库,到 PageHelper 分页,再到 Thymeleaf 服务器端渲染,最后通过 WebMvcConfigurer 和拦截器实现静态资源管理与权限校验。同时补充 AJAX 异步通信知识点,详细对比 Thymeleaf 与传统 EL 表达式的差异,并完整实现 “权限菜单展示” 常见项目需求,帮我们形成从后端到前端的完整开发能力。

一、Spring Boot 整合 MyBatis:数据库访问核心

        MyBatis 是 Java 生态主流的 ORM 框架,Spring Boot 通过mybatis-spring-boot-starter简化整合,我们从 “项目创建→完整 CRUD” 逐步拆解,避免初学者常见的 “Mapper 注入失败”“配置错误” 等问题。

1.1 第一步:创建项目与依赖配置

1.1.1 用 IDEA 创建项目(可视化操作)
  1. 新建 Spring Boot 项目→选择 “Spring Initializr”,填写基础信息(Group:com.lh,Artifact:springboot03,Java:17);

  2. 选择核心依赖(必选 3 个):

    • MyBatis Framework:MyBatis 核心依赖,自动整合 Spring;

    • MySQL Driver:MySQL 数据库驱动(适配 MySQL 8.x);

    • JDBC API:提供 JDBC 核心支持(含数据源自动配置、JdbcTemplate 等),为数据访问提供底层基础;

    • Spring Web:提供 HTTP 接口,用于测试数据访问;

  3. 完成创建,IDEA 自动生成项目结构和pom.xml

1.1.2 核心依赖解析(pom.xml)
<dependencies><!-- 1. Spring Web:支持HTTP接口,必选 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>​<!-- 2. MyBatis Starter:自动配置MyBatis,无需手动配SqlSessionFactory --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.5</version> <!-- 与Spring Boot 2.7.x兼容 --></dependency><!-- 3. 提供 JDBC 核心支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency>​<!-- 4. MySQL驱动:runtime scope表示运行时依赖 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency>​<!-- 5. 测试依赖:可选,用于单元测试 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
  • 依赖作用说明:

    mybatis-spring-boot-starter已包含 MyBatis 核心、Spring 整合包,无需额外配置SqlSessionFactory;

    mysql-connector-j是 MySQL 8.x 的驱动类名(旧版是mysql-connector-java)。

1.2 第二步:配置数据源(application.yml)

        Spring Boot 通过spring.datasource配置数据库连接,需注意 URL 参数(时区、编码)和驱动类名(MySQL 8.x 用com.mysql.cj.jdbc.Driver):

 spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driver  # MySQL 8.x驱动类名(必须带cj)username: root  # 数据库用户名password: root  # 数据库密码# URL参数说明:serverTimezone=Asia/Shanghai(时区,避免乱码)、useUnicode=true(支持中文)url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8
  • 易错点:

    若 URL 缺少serverTimezone,启动会报 “时区错误”;驱动类名用旧版com.mysql.jdbc.Driver会报 “弃用警告”。

1.3 第三步:开发核心业务代码(CRUD)

        我们以 “用户表(user)” 为例,实现 “查询所有、新增、删除、修改” 操作,遵循 “实体类→Mapper→Service→Controller” 分层开发规范。

1.3.1 1. 实体类(User.java)

        对应数据库user表(字段:id、name、age、pwd),用 Lombok 简化 Getter/Setter(需引入 Lombok 依赖):

package com.lh.springboot03.entity;​import lombok.Data;  // Lombok注解:自动生成Getter/Setter/toStringimport lombok.NoArgsConstructor;import lombok.AllArgsConstructor;​@Data@NoArgsConstructor  // 无参构造@AllArgsConstructor // 全参构造public class User {private Integer id;    // 主键private String name;    // 用户名private Integer age;    // 年龄private String pwd;     // 密码(实际项目需加密)}
  • Lombok 依赖补充:在pom.xml中添加 Lombok 依赖,避免手动写 Getter/Setter:

     <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
1.3.2 2. Mapper 接口(UserMapperjava)

        MyBatis 的 Mapper 接口用于定义数据库操作,通过注解写 SQL(简单 SQL 用注解,复杂 SQL 用 XML):

 package com.lh.springboot03.mapper;​import com.lh.springboot03.entity.User;import org.apache.ibatis.annotations.Delete;import org.apache.ibatis.annotations.Insert;import org.apache.ibatis.annotations.Select;import org.apache.ibatis.annotations.Update;import java.util.List;​// 方式1:加@Mapper注解,Spring自动扫描(适用于Mapper少的场景)// @Mapperpublic interface UserMapper {// 1. 查询所有用户@Select("select id, name, age, pwd from user")  // SQL字段需与实体类属性对应List<User> findAllUser();​// 2. 新增用户(#{}对应实体类属性,自动防SQL注入)@Insert("insert into user(name, age, pwd) values(#{name}, #{age}, #{pwd})")int addUser(User user);  // 返回值:影响行数​// 3. 删除用户(#{id}对应方法参数)@Delete("delete from user where id = #{id}")int deleteUser(Integer id);​// 4. 修改用户@Update("update user set name=#{name}, age=#{age}, pwd=#{pwd} where id=#{id}")int updateUser(User user);}
  • Mapper 扫描方式:

    方式 1:在每个 Mapper 接口加@Mapper注解(繁琐);

    方式 2(推荐):在主类加@MapperScan,批量扫描 Mapper 包:

     package com.lh.springboot03;​import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;​@SpringBootApplication// 扫描mapper包:指定Mapper接口所在路径@MapperScan("com.lh.springboot03.mapper")public class Springboot03Application {public static void main(String[] args) {SpringApplication.run(Springboot03Application.class, args);}}
1.3.3 3. Service 层(接口 + 实现类)

Service 层封装业务逻辑,调用 Mapper 接口:

 // 1. Service接口(UserService.java)package com.lh.springboot03.service;​import com.lh.springboot03.entity.User;import java.util.List;​public interface UserService {List<User> findAllUser();int addUser(User user);int deleteUser(Integer id);int updateUser(User user);}
实现类:
// 2. Service实现类(UserServiceImpl.java)package com.lh.springboot03.service.impl;​import com.lh.springboot03.entity.User;import com.lh.springboot03.mapper.UserMapper;import com.lh.springboot03.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;​@Service  // 标记为Service层Bean,交给Spring管理public class UserServiceImpl implements UserService {​// 注入Mapper接口(Spring通过@MapperScan自动生成代理对象)@Autowiredprivate UserMapper userMapper;​@Overridepublic List<User> findAllUser() {return userMapper.findAllUser();  // 调用Mapper方法}​@Overridepublic int addUser(User user) {// 可添加业务逻辑(如密码加密:user.setPwd(BCrypt.hashpw(user.getPwd(), BCrypt.gensalt())))return userMapper.addUser(user);}​@Overridepublic int deleteUser(Integer id) {return userMapper.deleteUser(id);}​@Overridepublic int updateUser(User user) {return userMapper.updateUser(user);}}
1.3.4 4. Controller 层(UserController.java)

Controller 层提供 HTTP 接口,调用 Service 层,返回 JSON 响应(用@RestController):

 package com.lh.springboot03.controller;​import com.lh.springboot03.entity.User;import com.lh.springboot03.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;​// @RestController = @Controller + @ResponseBody(自动返回JSON,无需手动处理)@RestControllerpublic class UserController {​@Autowiredprivate UserService userService;​// 1. 查询所有用户:GET http://localhost:8080/user@RequestMapping("/user")public List<User> findAllUser() {return userService.findAllUser();}​// 2. 新增用户:GET http://localhost:8080/adduser?name=李四&age=22&pwd=456@RequestMapping("/adduser")public int addUser(User user) {// 前端传参自动绑定到User对象(参数名与属性名一致)return userService.addUser(user);}​// 3. 删除用户:GET http://localhost:8080/deleteUser?id=1@RequestMapping("/deleteUser")public int deleteUser(Integer id) {return userService.deleteUser(id);}​// 4. 修改用户:GET http://localhost:8080/updateUser?id=2&name=李四2&age=23&pwd=789@RequestMapping("/updateUser")public int updateUser(User user) {return userService.updateUser(user);}}
1.3.5 测试:验证 CRUD 接口
  1. 启动项目,确保数据库mybatis已创建user表(数据示例:id=1, name=zs, age=18, pwd=123);

  2. 访问http://localhost:8080/user,返回用户列表 JSON:

     [{"id":1,"name":"zs","age":18,"pwd":"123"},{"id":2,"name":"刘海","age":20,"pwd":"123"}]
  3. 其他接口测试:

    • 新增:http://localhost:8080/adduser?name=王五&age=25&pwd=123,返回1(成功);

    • 删除:http://localhost:8080/deleteUser?id=3,返回1(成功)。

二、整合 PageHelper:实现分页查询

当用户表数据量大时,需要分页展示,PageHelper 是 MyBatis 的分页插件,能自动拦截 SQL 添加分页语句(limit),无需手动写分页 SQL

2.1 第一步:引入 PageHelper 依赖

pom.xml中添加 PageHelper Starter(自动整合 MyBatis):

<!-- PageHelper分页插件(与Spring Boot兼容) --><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>2.1.0</version></dependency>

2.2 第二步:修改 Controller 实现分页

在 UserController 中新增分页接口,核心是PageHelper.startPage(page, size)开启分页:

import com.github.pagehelper.PageHelper;import com.github.pagehelper.PageInfo;​@RestControllerpublic class UserController {// ... 其他方法省略​// 分页查询:GET http://localhost:8080/userPage?page=1&size=2@RequestMapping("/userPage")public PageInfo<User> findAllUserPage(Integer page, Integer size) {// 1. 开启分页:page=当前页(从1开始),size=每页条数PageHelper.startPage(page, size);// 2. 查询数据(PageHelper自动拦截SQL,添加limit ? ?)List<User> userList = userService.findAllUser();// 3. 封装分页信息(总条数、总页数等)PageInfo<User> pageInfo = new PageInfo<>(userList);return pageInfo;}}

2.3 第三步:测试分页接口

访问http://localhost:8080/userPage?page=1&size=2,返回分页结果 JSON,关键属性说明:

 {"total": 4,          // 总记录数"list": [            // 当前页数据(2条){"id":1,"name":"zs","age":18,"pwd":"123"},{"id":2,"name":"刘海","age":20,"pwd":"123"}],"pageNum": 1,        // 当前页"pageSize": 2,       // 每页条数"pages": 2,          // 总页数"hasNextPage": true, // 是否有下一页"hasPreviousPage": false // 是否有上一页}
  • PageInfo 核心属性:

    前端分页控件需要total(总记录数)、pageNum(当前页)、pages(总页数)、hasNextPage(是否下一页)等属性,无需手动计算。

三、Thymeleaf 深度解析:Spring Boot 推荐的模板引擎

传统 JSP 需要依赖 Tomcat 引擎,且不支持服务器端渲染优化,Thymeleaf 是 Spring Boot 官方推荐的模板引擎,支持纯 HTML 文件(无需 JSP)能直接在浏览器预览,同时提供丰富的服务器端渲染功能。

3.1 第一步:引入 Thymeleaf 依赖

pom.xml中添加 Thymeleaf Starter:

 <!-- Thymeleaf模板引擎依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>

3.2 第二步:Thymeleaf 自动配置原理

Spring Boot 通过ThymeleafAutoConfiguration自动配置,核心默认配置(无需手动写):

// ThymeleafProperties类中的默认配置public class ThymeleafProperties {public static final String DEFAULT_PREFIX = "classpath:/templates/"; // 页面存放路径public static final String DEFAULT_SUFFIX = ".html";               // 页面后缀private String prefix = DEFAULT_PREFIX;private String suffix = DEFAULT_SUFFIX;private boolean cache = false; // 开发环境关闭缓存(修改页面无需重启)}
  • 页面存放路径:Thymeleaf 页面默认放在src/main/resources/templates目录下,访问时无需写路径和后缀(如userlist对应templates/userlist.html)。

3.3 第三步:Thymeleaf 核心语法(重点补充)

Thymeleaf 通过th:前缀属性实现服务器端渲染,我们结合案例讲常用属性,对比传统 EL 表达式(JSP):

Thymeleaf 属性作用案例(Thymeleaf)传统 EL 表达式(JSP)
th:text设置元素文本内容(自动转义,防 XSS)<td th:text="${user.name}"></td><td>${user.name}</td>(需 JSP 引擎)
th:each循环遍历集合<tr th:each="user : ${userList}"><c:forEach items="${userList}" var="user">(需 JSTL)
th:href生成 URL(支持动态参数)<a th:href="@{/userPage(page=1, size=2)}"><a href="/userPage?page=1&size=2">(静态 URL)
th:action表单提交地址<form th:action="@{/login}" method="post"><form action="/login" method="post">
th:if条件判断(true 时显示元素)<div th:if="${user.age > 18}">成年</div><c:if test="${user.age > 18}">成年</c:if>
th:object绑定对象(简化属性引用)<div th:object="${user}"><span th:text="*{name}"></span></div>无直接对应,需${user.name}多次写对象名
语法案例:用户列表页面(userlist.html)
 <!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><!-- 必须加xmlns:th命名空间,否则Thymeleaf属性不生效 --><head><meta charset="UTF-8"><title>用户列表</title><style>table {border-collapse: collapse;}td, th {border: 1px solid #000; padding: 8px;}</style></head><body><h1>用户列表(Thymeleaf渲染)</h1><table><tr><th>ID</th><th>用户名</th><th>年龄</th><th>密码</th></tr><!-- th:each循环:user为循环变量,${userList}为后端传的集合 --><tr th:each="user, stat : ${userList}"><!-- stat是循环状态对象,可获取index(索引)、count(计数) --><td th:text="${user.id}"></td><td th:text="${user.name}"></td><td th:text="${user.age}"></td><td th:text="${user.pwd}"></td><!-- th:href动态生成URL,传递id参数 --><td><a th:href="@{/updateUserPage(id=${user.id})}">修改</a><a th:href="@{/deleteUser(id=${user.id})}">删除</a></td></tr></table>​<!-- 分页控件 --><div style="margin-top: 10px;"><!-- 上一页:判断是否有上一页 --><a th:if="${pageInfo.hasPreviousPage}" th:href="@{/userPage(page=${pageInfo.pageNum-1}, size=${pageInfo.pageSize})}">上一页</a><!-- 当前页 --><span th:text="'当前页:' + ${pageInfo.pageNum}"></span><!-- 下一页:判断是否有下一页 --><a th:if="${pageInfo.hasNextPage}" th:href="@{/userPage(page=${pageInfo.pageNum+1}, size=${pageInfo.pageSize})}">下一页</a></div></body></html>

3.4 第四步:Controller 传递数据到 Thymeleaf

修改 Controller,用ModelAndViewModel传递数据到页面(服务器端渲染):

 package com.lh.springboot03.controller;​import com.github.pagehelper.PageHelper;import com.github.pagehelper.PageInfo;import com.lh.springboot03.entity.User;import com.lh.springboot03.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.servlet.ModelAndView;​@Controller // 用@Controller(非@RestController),返回页面public class ThymeleafUserController {​@Autowiredprivate UserService userService;​// 分页查询用户,跳转到userlist.html:http://localhost:8080/findAllUserPage?page=1&size=2@RequestMapping("/findAllUserPage")public ModelAndView findAllUserPage(Integer page, Integer size) {// 1. 分页查询PageHelper.startPage(page, size);PageInfo<User> pageInfo = new PageInfo<>(userService.findAllUser());​// 2. 创建ModelAndView,指定页面名称(对应templates/userlist.html)ModelAndView mv = new ModelAndView("userlist");// 3. 传递数据到页面:key为页面使用的变量名,value为数据mv.addObject("pageInfo", pageInfo);mv.addObject("userList", pageInfo.getList()); // 也可直接传pageInfo,页面用pageInfo.list​return mv;}}

3.5 Thymeleaf vs 传统 EL 表达式(JSP):什么时候用哪个?

对比维度Thymeleaf传统 EL 表达式(JSP)
依赖环境无依赖(纯 HTML),Spring Boot 自动支持依赖 Tomcat JSP 引擎,需额外配置
页面预览直接用浏览器打开 HTML,显示默认值必须部署到服务器才能预览,否则 EL 表达式原样显示
安全性自动转义 HTML,防 XSS 攻击需手动处理转义(如c:out标签)
功能丰富度支持循环、条件、片段引入、国际化需依赖 JSTL 标签库(如c:forEach
开发效率语法简洁,IDEA 有自动提示需记忆 JSTL 标签,语法繁琐

选择建议

  • 新项目(Spring Boot):优先用 Thymeleaf,无需 JSP 依赖,开发效率高;

  • 旧项目(传统 SSM):若已用 JSP+EL,可继续使用,无需迁移;

  • 纯静态页面:用 Thymeleaf 的th:text="${xxx} ?: '默认值'",支持预览时显示默认值。

四、WebMvcConfigurer:配置静态资源与拦截器

        WebMvcConfigurer 是 Spring MVC 的核心接口,用于定制 Spring MVC 行为(静态资源、拦截器、跨域等),无需 XML 配置,纯 Java 代码实现。

4.1 配置静态资源(addResourceHandlers)

        Spring Boot 默认静态资源路径是classpath:/static/(CSS、JS、图片),但有时需要配置自定义路径(如本地磁盘文件),通过addResourceHandlers实现:

实战:配置静态资源(类路径 + 本地磁盘)
package com.lh.springboot03.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.file.Paths;@Configuration // 标记为配置类
public class WebMvcConfig implements WebMvcConfigurer {// 配置静态资源映射@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {// 1. 配置类路径静态资源:访问 /my-files/xxx → 对应 classpath:/files/xxxregistry.addResourceHandler("/my-files/**") // 前端访问URL模式(**匹配任意路径).addResourceLocations("classpath:/files/") // 后端资源路径(类路径下的files文件夹).setCachePeriod(3600); // 缓存时间(秒),开发环境可设0// 2. 配置本地磁盘静态资源:访问 /upload/xxx → 对应 D:/upload/xxxString localPath = "D:/upload/";// 本地路径需转成URI格式(处理路径分隔符)String resourceLocation = Paths.get(localPath).toUri().toString();registry.addResourceHandler("/upload/**").addResourceLocations(resourceLocation); // 本地磁盘路径,需加file:前缀(URI已包含)}
}
  • 测试访问:

    • 类路径资源:src/main/resources/files/b.txt → 访问 http://localhost:8080/my-files/b.txt

    • 本地磁盘资源:D:/upload/a.txt → 访问 http://localhost:8080/upload/a.txt

4.2 配置拦截器(addInterceptors)

        拦截器用于 “预处理请求”(如登录验证、日志记录),Spring MVC 通过HandlerInterceptor接口实现,需 3 步:创建拦截器→注册拦截器→测试。

4.2.1 1. 创建自定义拦截器(实现 HandlerInterceptor)

        拦截器有 3 个核心方法,执行时机不同:

package com.lh.springboot03.interceptor;import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;// 1. 登录验证拦截器:未登录用户不能访问/auth/**路径
@Component // 交给Spring管理,后续注册时注入
public class LoginInterceptor implements HandlerInterceptor {/*** 1. preHandle:控制器方法执行前执行(核心)* @return true:继续执行控制器;false:中断请求(不执行控制器)*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 从Session获取登录用户(登录时需将用户存入Session)HttpSession session = request.getSession();Object loginUser = session.getAttribute("loginUser");if (loginUser == null) {// 未登录:跳转到登录页,中断请求response.sendRedirect("/login.html");return false;}// 已登录:继续执行控制器return true;}/*** 2. postHandle:控制器方法执行后,页面渲染前执行(少用)* 用途:修改ModelAndView中的数据*/// @Override// public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {//     if (modelAndView != null) {//         modelAndView.addObject("msg", "从拦截器添加的消息");//     }// }/*** 3. afterCompletion:整个请求完成后执行(页面渲染后)* 用途:资源清理、日志记录*/// @Override// public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//     System.out.println("请求完成,清理资源");// }
}// 2. 日志记录拦截器:记录所有请求的访问日志
@Component
public class LoggingInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 记录请求URL、用户、时间HttpSession session = request.getSession();String username = (String) session.getAttribute("loginUser");String url = request.getRequestURI();String time = new java.util.Date().toString();System.out.printf("用户:%s,访问URL:%s,时间:%s%n", username, url, time);return true; // 继续执行}
}
4.2.2 2. 注册拦截器(addInterceptors)

        在 WebMvcConfig 中注册拦截器,指定拦截路径和排除路径:

package com.lh.springboot03.config;import com.lh.springboot03.interceptor.LoginInterceptor;
import com.lh.springboot03.interceptor.LoggingInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class WebMvcConfig implements WebMvcConfigurer {// 注入自定义拦截器@Autowiredprivate LoginInterceptor loginInterceptor;@Autowiredprivate LoggingInterceptor loggingInterceptor;// 注册拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 1. 注册登录拦截器:拦截/auth/**路径,排除登录页、静态资源registry.addInterceptor(loginInterceptor).addPathPatterns("/auth/**") // 拦截的路径(/**表示所有路径).excludePathPatterns(       // 排除的路径(不拦截)"/login.html",       // 登录页"/login",            // 登录接口"/static/**",        // 静态资源(CSS、JS)"/my-files/**",      // 自定义静态资源"/error"             // 错误页面);// 2. 注册日志拦截器:拦截所有路径registry.addInterceptor(loggingInterceptor).addPathPatterns("/**");}// 静态资源配置(之前的代码)...
}
4.2.3 3. 测试拦截器
  1. 创建登录页(templates/login.html):

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <body><form th:action="@{/login}" method="post">用户名:<input type="text" name="username"><br>密码:<input type="password" name="password"><br><input type="submit" value="登录"></form>
    </body>
    </html>
  2. 创建登录 Controller:

    @Controller
    public class LoginController {// 跳转到登录页:http://localhost:8080/login.html@RequestMapping("/login.html")public String toLogin() {return "login"; // 对应templates/login.html}// 处理登录请求:POST /login@RequestMapping("/login")public String login(String username, String password, HttpServletRequest request) {// 模拟数据库校验(实际项目需查数据库)if ("admin".equals(username) && "123456".equals(password)) {// 登录成功:将用户存入Sessionrequest.getSession().setAttribute("loginUser", username);return "redirect:/auth/index"; // 跳转到需登录的路径}// 登录失败:返回登录页return "login";}// 需登录的路径:/auth/index@RequestMapping("/auth/index")public String index() {return "index"; // 对应templates/index.html}
    }
  3. 测试流程:

    • 访问http://localhost:8080/auth/index(未登录)→ 被 LoginInterceptor 拦截,跳转到/login.html

    • 输入正确用户名密码(admin/123456)→ 登录成功,跳转到/auth/index

    • 控制台输出日志(LoggingInterceptor):用户:admin,访问URL:/auth/index,时间:...

4.3 拦截器 vs 过滤器(Filter):核心区别

对比维度拦截器(Interceptor)过滤器(Filter)
技术依赖依赖 Spring MVC 框架,仅 Spring Web 应用可用依赖 Servlet 规范,所有 Web 应用可用(非 Spring 也能用)
执行时机控制器方法执行前后(Spring MVC 流程内)请求进入 Servlet 前、响应返回客户端前(Servlet 流程)
拦截范围仅拦截控制器请求(如/user),不拦截静态资源拦截所有请求(包括静态资源、Servlet)
功能权限可访问 Spring 容器中的 Bean(如 Service、Mapper)不可访问 Spring Bean,需手动获取容器
执行顺序按注册顺序执行,支持 preHandle/postHandle/afterCompletion按 web.xml 或 @Order 排序,仅 doFilter 方法

选择建议

  • 业务逻辑拦截(登录验证、权限校验):用拦截器(可访问 Spring Bean);

  • 通用请求处理(字符编码、压缩、跨域):用过滤器(Servlet 层面,范围更广)。

五、补充:AJAX 核心知识点(结合常见项目需求使用)

        AJAX(Asynchronous JavaScript and XML)是 “异步 JavaScript 和 XML” 的缩写,核心作用是不刷新页面,异步与服务器通信,常用于动态加载数据(如作业中的菜单渲染)。现在主流用 JSON 传递数据(替代 XML),我们以 jQuery AJAX 为例讲解。

5.1 AJAX 核心作用与优势

  • 核心作用:页面不刷新,局部更新数据(如点击 “加载更多” 不刷新页面,加载新数据);

  • 优势:减少页面刷新,提升用户体验;减少数据传输量(仅传必要数据);

  • 常见场景:动态菜单加载、表单异步提交、分页无刷新、搜索联想。

5.2 jQuery AJAX 基础语法

        需先引入 jQuery(从 CDN 或本地静态资源),语法结构:

$.ajax({url: "/api/getMenuList",  // 后端接口URLtype: "GET",             // 请求方法(GET/POST)data: {roleId: 2},       // 请求参数(JSON格式)dataType: "json",        // 预期后端返回数据类型(json/text/html)success: function(res) { // 请求成功回调(res为后端返回的JSON数据)console.log("成功:", res);// 处理数据(如渲染菜单)renderMenu(res.data);},error: function(xhr, status, error) { // 请求失败回调console.error("失败:", error);alert("加载菜单失败!");}
});// 简化写法(GET请求)
$.get("/api/getMenuList", {roleId: 2}, function(res) {renderMenu(res.data);
}, "json");// 简化写法(POST请求)
$.post("/api/getMenuList", {roleId: 2}, function(res) {renderMenu(res.data);
}, "json");

5.3 AJAX 跨域问题(补充)

        若前端和后端不在同一域名(如前端localhost:8081,后端localhost:8080),会出现跨域错误,需在后端配置 CORS(跨域资源共享)

// 在WebMvcConfig中配置跨域
@Override
public void addCorsMappings(CorsRegistry registry) {registry.addMapping("/api/**") // 允许跨域的接口路径.allowedOrigins("http://localhost:8081") // 允许的前端域名.allowedMethods("GET", "POST") // 允许的请求方法.allowedHeaders("*") // 允许的请求头.allowCredentials(true); // 是否允许携带Cookie
}

六、项目需求实现:权限菜单展示功能

6.1 需求分析

基于用户 - 角色 - 菜单的权限模型,实现:

  1. 所有请求需拦截器验证登录;

  2. 登录时根据用户角色 ID,查询该角色拥有的菜单;

  3. 用 AJAX 异步加载菜单数据,渲染到页面。

6.2 表设计(4 张表)

表名字段说明
userid、username、password、roleId用户表,roleId 关联 role 表主键
roleid、rolename角色表(如 “管理员”“普通用户”)
menuid、menuName、menuUrl菜单表(如 “用户管理”→/auth/user
r_mrole_id、menu_id角色 - 菜单中间表(多对多关系)

测试数据

  • role 表:id=2,rolename=“管理员”;

  • r_m 表:(2,1)、(2,3)、(2,6);

  • menu 表:id=1→菜单名 “用户管理”,menuUrl=“/auth/user”;id=3→“订单管理”,menuUrl=“/auth/order”;id=6→“菜单管理”,menuUrl=“/auth/menu”。

6.3 后端实现(分步骤)

6.3.1 1. 创建实体类(Menu.java、Role.java)
// Menu.java(菜单实体类)
package com.lh.springboot03.entity;import lombok.Data;@Data
public class Menu {private Integer id;         // 菜单IDprivate String menuName;    // 菜单名称private String menuUrl;     // 菜单URL
}

// Role.java(角色实体类,可选,本作业用roleId查询)
package com.lh.springboot03.entity;import lombok.Data;@Data
public class Role {private Integer id;         // 角色IDprivate String rolename;    // 角色名称
}
6.3.2 2. 编写 Mapper 接口(MenuMapper.java)

        通过角色 ID 查询菜单(联表查询 r_m 和 menu):

package com.lh.springboot03.mapper;import com.lh.springboot03.entity.Menu;
import org.apache.ibatis.annotations.Select;
import java.util.List;public interface MenuMapper {// 联表查询:通过roleId查菜单(r_m中间表关联menu表)@Select("SELECT m.id, m.menuName, m.menuUrl " +"FROM menu m " +"JOIN r_m rm ON m.id = rm.menu_id " +"WHERE rm.role_id = #{roleId}")List<Menu> findMenuByRoleId(Integer roleId);
}
6.3.3 3. Service 层(MenuService.java)
// 接口
public interface MenuService {List<Menu> findMenuByRoleId(Integer roleId);
}// 实现类
@Service
public class MenuServiceImpl implements MenuService {@Autowiredprivate MenuMapper menuMapper;@Overridepublic List<Menu> findMenuByRoleId(Integer roleId) {return menuMapper.findMenuByRoleId(roleId);}
}
6.3.4 4. Controller 层(MenuController.java)

        提供 AJAX 接口,返回菜单列表:

@RestController
@RequestMapping("/api")
public class MenuController {@Autowiredprivate MenuService menuService;// AJAX接口:根据roleId查菜单,返回JSON@RequestMapping("/getMenuList")public Map<String, Object> getMenuList(Integer roleId) {Map<String, Object> result = new HashMap<>();try {List<Menu> menuList = menuService.findMenuByRoleId(roleId);result.put("code", 0); // 0=成功result.put("data", menuList);result.put("msg", "加载菜单成功");} catch (Exception e) {result.put("code", 500); // 500=失败result.put("msg", "加载菜单失败:" + e.getMessage());}return result;}
}
6.3.5 5. 完善拦截器(验证登录 + 获取角色 ID)

        修改 LoginInterceptor,登录时获取用户的 roleId,存入 Session:

// 1. 修改User实体类,增加roleId字段
@Data
public class User {// ... 原有字段private Integer roleId; // 新增:关联角色表
}// 2. 修改LoginController,登录时查询用户的roleId,存入Session
@RequestMapping("/login")
public String login(String username, String password, HttpServletRequest request) {// 模拟数据库查询(实际项目需查user表)if ("admin".equals(username) && "123456".equals(password)) {// 模拟用户数据:id=1,username=admin,roleId=2(管理员角色)User user = new User();user.setId(1);user.setUsername(username);user.setRoleId(2);// 存入SessionHttpSession session = request.getSession();session.setAttribute("loginUser", user);return "redirect:/index.html"; // 跳转到首页}return "login";
}// 3. 修改LoginInterceptor,验证登录时获取roleId(可选,用于后续权限控制)
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session = request.getSession();User loginUser = (User) session.getAttribute("loginUser");if (loginUser == null) {response.sendRedirect("/login.html");return false;}// 可选:将roleId放入请求属性,供后续使用request.setAttribute("roleId", loginUser.getRoleId());return true;
}

6.4 前端实现(AJAX 渲染菜单)

6.4.1 1. 首页(index.html)

        引入 jQuery,用 AJAX 加载菜单:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>首页</title><!-- 引入jQuery(CDN) --><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><style>.menu { list-style: none; padding: 0; }.menu li { margin: 5px 0; }.menu a { text-decoration: none; color: #333; }.menu a:hover { color: #1890ff; }</style>
</head>
<body><h1>首页(已登录)</h1><h3>菜单列表</h3><!-- 菜单容器 --><ul class="menu" id="menuContainer"></ul><script>// 页面加载完成后执行$(function() {// 1. 从Session获取roleId(需后端传递,这里简化用固定值2,实际项目需后端渲染到页面)var roleId = 2; // 实际项目:var roleId = [[${loginUser.roleId}]];(Thymeleaf渲染)// 2. AJAX请求菜单数据$.ajax({url: "/api/getMenuList",type: "GET",data: { roleId: roleId },dataType: "json",success: function(res) {if (res.code === 0) {// 3. 渲染菜单renderMenu(res.data);} else {alert(res.msg);}},error: function() {alert("加载菜单失败,请重试!");}});});// 3. 渲染菜单的函数function renderMenu(menuList) {var $menuContainer = $("#menuContainer");$menuContainer.empty(); // 清空容器// 循环遍历菜单列表$.each(menuList, function(index, menu) {// 创建菜单<li>和<a>标签var $li = $("<li></li>");var $a = $("<a></a>").attr("href", menu.menuUrl) // 设置菜单URL.text(menu.menuName);     // 设置菜单名称$li.append($a);$menuContainer.append($li);});}</script>
</body>
</html>
6.4.2 2. 测试 AJAX 菜单加载
  1. 启动项目,登录(admin/123456)→ 跳转到 index.html;

  2. 页面加载时,AJAX 请求/api/getMenuList?roleId=2,后端返回菜单列表;

  3. 前端渲染菜单,显示 “用户管理”“订单管理”“菜单管理”,点击菜单跳转到对应 URL。

七、总结

本文从 “数据访问→前端渲染→权限控制” 完整覆盖 Spring Boot 核心开发场景:

  1. MyBatis 整合:掌握 CRUD 开发,理解 @MapperScan 的作用,避免 Mapper 注入失败;

  2. PageHelper 分页:简化分页逻辑,理解 PageInfo 的核心属性;

  3. Thymeleaf:掌握常用语法,对比 EL 表达式,知道什么时候用 Thymeleaf;

  4. WebMvcConfigurer:配置静态资源和拦截器,理解拦截器与过滤器的区别;

  5. AJAX:掌握异步通信语法,结合作业实现动态菜单渲染;

  6. 权限菜单作业:理解用户 - 角色 - 菜单的多对多关系,实现从后端查询到前端渲染的完整流程。

后续学习可扩展:菜单的父子级渲染(递归 AJAX)、权限的细粒度控制(按钮级权限)、用 Vue 替代 jQuery 实现前端渲染,逐步向前后端分离架构过渡。

http://www.dtcms.com/a/439518.html

相关文章:

  • 永久免费个人网站申请注册电子购物网站开发公司
  • 工信部网站黑名单软件开发工程师的岗位职责
  • 阿里云做的网站为啥没有ftp汉南做网站
  • 深度学习是如何收敛的?梯度下降算法原理详解
  • 1.Kali Linux操作系统的下载(2025年10月2日)
  • JVM(七)--- 垃圾回收
  • 专门制作网站郑州男科医院哪家治疗比较好
  • 网站开发 需求文档结构设计网站推荐
  • 自定义异常类中的super(msg)的作用
  • 我想卖自己做的鞋子 上哪个网站好扬州市建筑信息平台
  • 十里河网站建设百度做营销网站多少钱
  • 新版网页传奇网站优化怎么做外链
  • 衡阳网站建设icp备网页设计技术论文范文
  • Linux驱动开发核心概念详解 - 从入门到精通
  • 深圳市建设工程交易服务中心网站在南海建设工程交易中心网站
  • 寻找哈尔滨网站建设服务器内部打不开网站
  • 函数展开成幂级数的方法总结
  • 自己可以做类似拓者的网站吗郑州网站建设行情
  • 中国顶级 GEO 优化专家孟庆涛:用 15 年积淀定义 2025 年 GEO 优化新标准
  • 建筑方案的网站wordpress首页做全屏
  • 建设银行手机官方网站下载安装推荐大良营销网站建设
  • 华为手机网站建设策划方案wordpress文章模块化
  • 修改wordpress用户密码深圳网站营销seo电话
  • 杭州建设企业网站的网络规划设计师考海明码吗
  • DAY24 方法引用、Base64、正则、lombok
  • 大学网站建设包括哪些课程专业网站搭建报价
  • 网站上的图片做多大免费网站整站模板源码
  • 江苏建设厅官方网站安全员长沙建长沙建网站公司
  • 杭州做网站小芒上海闵行区租房价格
  • 做美食的网站可以放些小图片简历网站后怎样才能被谷歌 百度收录吗