Spring Boot + MyBatis + Redis 整合实战 —— 项目源码深度解析

Spring Boot + MyBatis + Redis 整合实战 ------ 项目源码深度解析

本文基于一个完整的 Spring Boot + MyBatis + Redis 整合示例项目,逐层剖析项目结构、核心代码与缓存策略,帮助你快速理解三者如何协同工作。


一、项目概览

本项目是一个城市信息 CRUD 管理系统,核心技术栈:

技术 版本 作用
Spring Boot 2.1.3.RELEASE 应用框架
MyBatis 1.2.0 ORM 持久层
Redis - 缓存层
MySQL 8.0.33 关系型数据库

二、项目结构

复制代码
springboot-mybatis-redis/
├── pom.xml                              # Maven 依赖配置
├── src/
│   ├── main/
│   │   ├── java/org/spring/springboot/
│   │   │   ├── Application.java              # 启动入口类
│   │   │   ├── common/
│   │   │   │   ── Result.java               # 统一响应封装
│   │   │   ├── controller/
│   │   │   │   └── CityRestController.java   # REST 接口层
│   │   │   ├── dao/
│   │   │   │   └── CityDao.java              # MyBatis DAO 接口
│   │   │   ├── domain/
│   │   │   │   └── City.java                 # 城市实体类
│   │   │   └── service/
│   │   │       ├── CityService.java          # 业务接口
│   │   │       └── impl/
│   │   │           ├── CityServiceImpl.java  # 业务实现(含 Redis 缓存逻辑)
│   │   │           └── RedisConfig.java      # Redis 序列化配置
│   │   └── resources/
│   │       ├── application.properties        # 配置文件(DB/Redis/MyBatis)
│   │       └── mapper/
│   │           ── CityMapper.xml            # MyBatis SQL 映射文件
│   └── test/resources/
│       └── city_post.json                    # 测试数据
└── target/                                   # 编译输出目录

三、依赖配置(pom.xml)

xml 复制代码
<!-- Spring Boot Redis 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Spring Boot Web 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Spring Boot MyBatis 依赖 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.2.0</version>
</dependency>

<!-- MySQL 连接驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

核心依赖只有四个:RedisWebMyBatisMySQL 驱动,简洁清晰。


四、配置文件(application.properties)

properties 复制代码
## 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdb?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

## MyBatis 配置
mybatis.typeAliasesPackage=org.spring.springboot.domain
mybatis.mapperLocations=classpath:mapper/*.xml

## Redis 配置
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.timeout=5000

配置分为三部分:

  • 数据源 :连接本地 MySQL 的 springbootdb 数据库
  • MyBatis:指定实体类包路径和 Mapper XML 文件位置
  • Redis:连接本地 6379 端口,无密码,使用默认数据库索引 0

五、启动类(Application.java)

java 复制代码
@SpringBootApplication
@MapperScan("org.spring.springboot.dao")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

两个关键注解:

  • @SpringBootApplication:标记为 Spring Boot 应用,自动配置 + 组件扫描
  • @MapperScan("org.spring.springboot.dao"):扫描 DAO 包,自动注册 MyBatis Mapper 接口

六、实体类(City.java)

java 复制代码
public class City implements Serializable {

    private static final long serialVersionUID = -1L;

    private Long id;           // 城市编号
    private Long provinceId;   // 省份编号
    private String cityName;   // 城市名称
    private String description; // 描述
}

注意 :实体类实现了 Serializable 接口。这是因为对象需要被序列化后存入 Redis 缓存,不实现该接口会导致序列化异常。


七、DAO 层(CityDao.java + CityMapper.xml)

DAO 接口

java 复制代码
public interface CityDao {
    List<City> findAllCity();
    City findById(@Param("id") Long id);
    Long saveCity(City city);
    Long updateCity(City city);
    Long deleteCity(Long id);
}

MyBatis SQL 映射

xml 复制代码
<mapper namespace="org.spring.springboot.dao.CityDao">
    <resultMap id="BaseResultMap" type="org.spring.springboot.domain.City">
        <result column="id" property="id" />
        <result column="province_id" property="provinceId" />
        <result column="city_name" property="cityName" />
        <result column="description" property="description" />
    </resultMap>

    <select id="findById" resultMap="BaseResultMap" parameterType="java.lang.Long">
        SELECT id, province_id, city_name, description FROM city WHERE id = #{id}
    </select>

    <insert id="saveCity" parameterType="City" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO city(id, province_id, city_name, description)
        VALUES(#{id}, #{provinceId}, #{cityName}, #{description})
    </insert>

    <update id="updateCity" parameterType="City">
        UPDATE city
        SET
        <if test="provinceId!=null">province_id = #{provinceId},</if>
        <if test="cityName!=null">city_name = #{cityName},</if>
        <if test="description!=null">description = #{description}</if>
        WHERE id = #{id}
    </update>

    <delete id="deleteCity" parameterType="java.lang.Long">
        DELETE FROM city WHERE id = #{id}
    </delete>
</mapper>

亮点updateCity 使用了 <if> 动态 SQL,只更新传入的非空字段,避免将字段误置为 NULL。


八、Redis 序列化配置(RedisConfig.java)

java 复制代码
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer<Object> jsonSerializer =
                new Jackson2JsonRedisSerializer<>(Object.class);

        template.setKeySerializer(stringSerializer);
        template.setValueSerializer(jsonSerializer);
        template.setHashKeySerializer(stringSerializer);
        template.setHashValueSerializer(jsonSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

为什么需要这个配置?

Spring Data Redis 默认使用 JDK 序列化,存入 Redis 的数据是二进制乱码,无法通过 redis-cli 直接阅读。此配置将 Value 的序列化方式改为 Jackson JSON,使得缓存中的数据以可读的 JSON 格式存储。

部分 序列化方式 效果
Key String city_1 清晰可读
Value Jackson JSON {"id":1,"cityName":"杭州",...} 而非二进制乱码

九、业务层 ------ 核心缓存逻辑(CityServiceImpl.java)

这是整个项目最核心的文件 ,实现了经典的 Cache-Aside(旁路缓存)策略

9.1 查询逻辑 ------ 先缓存后数据库

java 复制代码
public City findCityById(Long id) {
    String key = "city_" + id;
    ValueOperations<String, City> operations = redisTemplate.opsForValue();

    // 1. 先查缓存
    boolean hasKey = redisTemplate.hasKey(key);
    if (hasKey) {
        City city = operations.get(key);
        LOGGER.info("从缓存中获取了城市 >> " + city);
        return city;
    }

    // 2. 缓存未命中,查数据库
    City city = cityDao.findById(id);

    if (city == null) {
        LOGGER.warn("数据库未找到城市,id={}", id);
        return null;
    }

    // 3. 写入缓存,TTL 500 秒
    operations.set(key, city, 500, TimeUnit.SECONDS);
    LOGGER.info("城市插入缓存 >> " + city);

    return city;
}

流程图

复制代码
查询请求 → Redis 缓存命中?
              ├─ 是 → 直接返回缓存数据 ✅
              └─ 否 → 查询 MySQL DB
                        ├─ 找到 → 写入 Redis(TTL 500s)→ 返回
                        └─ 未找到 → 返回 null(不缓存空值)

9.2 更新逻辑 ------ 先更新数据库,再删除缓存

java 复制代码
public Long updateCity(City city) {
    Long ret = cityDao.updateCity(city);

    // 删除缓存,保证下次查询走数据库获取最新数据
    String key = "city_" + city.getId();
    boolean hasKey = redisTemplate.hasKey(key);
    if (hasKey) {
        redisTemplate.delete(key);
        LOGGER.info("从缓存中删除城市 >> " + city);
    }

    return ret;
}

9.3 删除逻辑 ------ 先删除数据库,再删除缓存

java 复制代码
public Long deleteCity(Long id) {
    Long ret = cityDao.deleteCity(id);

    String key = "city_" + id;
    boolean hasKey = redisTemplate.hasKey(key);
    if (hasKey) {
        redisTemplate.delete(key);
        LOGGER.info("从缓存中删除城市 ID >> " + id);
    }
    return ret;
}

缓存策略总结

操作 步骤 原因
查询 查缓存 → 未命中查 DB → 写缓存 减少数据库压力
更新 更新 DB → 删除缓存 保证数据一致性,下次查询自动加载新数据
删除 删除 DB → 删除缓存 防止缓存中存在已删除的脏数据

这种策略被称为 Cache-Aside Pattern(旁路缓存模式),是业界最常用的缓存方案。


十、REST 接口层(CityRestController.java)

java 复制代码
@RestController
public class CityRestController {

    @Autowired
    private CityService cityService;

    @RequestMapping(value = "/api/city/{id}", method = RequestMethod.GET)
    public Result<Object> findOneCity(@PathVariable("id") Long id) { ... }

    @RequestMapping(value = "/api/city", method = RequestMethod.POST)
    public Result<City> createCity(@RequestBody City city) { ... }

    @RequestMapping(value = "/api/city", method = RequestMethod.PUT)
    public Result<City> modifyCity(@RequestBody City city) { ... }

    @RequestMapping(value = "/api/city/{id}", method = RequestMethod.DELETE)
    public Result<Long> deleteCity(@PathVariable("id") Long id) { ... }
}

API 接口一览

方法 路径 功能 请求示例
GET /api/city/{id} 查询城市 GET /api/city/1
POST /api/city 新增城市 POST /api/city + JSON Body
PUT /api/city 更新城市 PUT /api/city + JSON Body
DELETE /api/city/{id} 删除城市 DELETE /api/city/1

十一、统一响应封装(Result.java)

java 复制代码
public class Result<T> {
    private int code;        // 200 = 成功
    private String message;  // 响应提示
    private T data;          // 业务数据

    public static <T> Result<T> success(T data) { ... }
    public static <T> Result<T> error(int code, String message) { ... }
}

所有接口统一返回 Result<T> 格式:

json 复制代码
{
    "code": 200,
    "message": "success",
    "data": {
        "id": 1,
        "provinceId": 1,
        "cityName": "杭州",
        "description": "人间天堂"
    }
}

十二、整体数据流转

复制代码
┌──────────┐     ┌──────────────┐     ┌───────────────┐     ┌──────────┐
│  客户端   │────▶│  Controller  │────▶│   Service      │────▶│  Redis   │
│  请求    │────│  接口层       │◀────│   业务层       │◀────│  缓存    │
└──────────┘     └──────────────┘     └───────┬───────┘     └──────────┘
                                              │
                                              ▼
                                       ┌──────────────┐
                                       │    MySQL      │
                                       │    数据库      │
                                       └──────────────┘

完整请求流程

  1. 客户端发起 GET /api/city/1 请求
  2. CityRestController 接收请求,调用 CityService.findCityById(1)
  3. CityServiceImpl 先查询 Redis 缓存 city_1
  4. 缓存命中 → 直接返回;缓存未命中 → 通过 CityDao 查询 MySQL
  5. 查询结果写入 Redis(TTL 500 秒),然后返回给客户端
  6. Result 封装为统一 JSON 格式响应

十三、总结

本项目虽然代码量不大,但完整展示了 Spring Boot + MyBatis + Redis 三者整合的核心要点:

要点 说明
分层架构 Controller → Service → DAO,职责清晰
MyBatis 集成 接口 + XML 映射,动态 SQL 支持
Redis 缓存 Cache-Aside 策略,查询走缓存,写操作删缓存
序列化方案 Jackson JSON 替代 JDK 序列化,数据可读
统一响应 Result 封装,前后端交互规范
空值保护 DB 未查到不缓存 null,避免 NPE

这是一个非常适合作为 Spring Boot 缓存入门 的学习项目,掌握了这个项目的思路,就可以在实际业务中灵活运用 Redis 缓存来提升系统性能。

相关推荐
ShiXZ2136 小时前
Redis 常用指令全集:redis-cli 实战速查手册
数据库·redis·缓存
Muscleheng9 小时前
SpringBoot 集成 DeepSeek 实现 RAG 文档问答
java·spring boot·ai·springai
suconnect12 小时前
Spring Boot接入企业RAG:文档切分、向量检索、权限过滤和答案溯源
java·spring boot·后端
weixin_BYSJ198712 小时前
springboot校园自习室管理小程序---附源码32142
java·javascript·spring boot·python·django·flask·php
旺仔学长 哈哈13 小时前
Spring Boot 智能停车场管理系统---附源码+数据库文档
数据库·spring boot·后端·智能停车场
小Ti客栈13 小时前
SpringBoot 事件发布机制:原理与应用
java·spring boot·spring
huahailing102416 小时前
Spring Boot 集成 XXL-Job 完整实现方案(支持动态CRUD)
java·spring boot·后端
音符犹如代码17 小时前
Arthas Profiler 火焰图实战:CPU 热点在哪一目了然
java·jvm·spring boot
阿里云云原生18 小时前
AI Agent 上线容易稳定难?阿里云 AgentLoop 推出“经验自进化”闭环治理方案
人工智能·阿里云·mybatis·agentscope