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

📅 2026/7/30 10:08:50
Spring Boot + MyBatis + Redis 整合实战 —— 项目源码深度解析
Spring Boot MyBatis Redis 整合实战 —— 项目源码深度解析本文基于一个完整的 Spring Boot MyBatis Redis 整合示例项目逐层剖析项目结构、核心代码与缓存策略帮助你快速理解三者如何协同工作。一、项目概览本项目是一个城市信息 CRUD 管理系统核心技术栈技术版本作用Spring Boot2.1.3.RELEASE应用框架MyBatis1.2.0ORM 持久层Redis-缓存层MySQL8.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!-- Spring Boot Redis 依赖 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency!-- Spring Boot Web 依赖 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- Spring Boot MyBatis 依赖 --dependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion1.2.0/version/dependency!-- MySQL 连接驱动 --dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.33/version/dependency核心依赖只有四个Redis、Web、MyBatis、MySQL 驱动简洁清晰。四、配置文件application.properties## 数据源配置 spring.datasource.urljdbc:mysql://localhost:3306/springbootdb?useUnicodetruecharacterEncodingutf8useSSLfalseallowPublicKeyRetrievaltrue spring.datasource.usernameroot spring.datasource.password123456 spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver ## MyBatis 配置 mybatis.typeAliasesPackageorg.spring.springboot.domain mybatis.mapperLocationsclasspath:mapper/*.xml ## Redis 配置 spring.redis.database0 spring.redis.host127.0.0.1 spring.redis.port6379 spring.redis.password spring.redis.pool.max-active8 spring.redis.pool.max-wait-1 spring.redis.pool.max-idle8 spring.redis.pool.min-idle0 spring.redis.timeout5000配置分为三部分数据源连接本地 MySQL 的springbootdb数据库MyBatis指定实体类包路径和 Mapper XML 文件位置Redis连接本地 6379 端口无密码使用默认数据库索引 0五、启动类Application.javaSpringBootApplicationMapperScan(org.spring.springboot.dao)publicclassApplication{publicstaticvoidmain(String[]args){SpringApplication.run(Application.class,args);}}两个关键注解SpringBootApplication标记为 Spring Boot 应用自动配置 组件扫描MapperScan(org.spring.springboot.dao)扫描 DAO 包自动注册 MyBatis Mapper 接口六、实体类City.javapublicclassCityimplementsSerializable{privatestaticfinallongserialVersionUID-1L;privateLongid;// 城市编号privateLongprovinceId;// 省份编号privateStringcityName;// 城市名称privateStringdescription;// 描述}注意实体类实现了Serializable接口。这是因为对象需要被序列化后存入 Redis 缓存不实现该接口会导致序列化异常。七、DAO 层CityDao.java CityMapper.xmlDAO 接口publicinterfaceCityDao{ListCityfindAllCity();CityfindById(Param(id)Longid);LongsaveCity(Citycity);LongupdateCity(Citycity);LongdeleteCity(Longid);}MyBatis SQL 映射mappernamespaceorg.spring.springboot.dao.CityDaoresultMapidBaseResultMaptypeorg.spring.springboot.domain.Cityresultcolumnidpropertyid/resultcolumnprovince_idpropertyprovinceId/resultcolumncity_namepropertycityName/resultcolumndescriptionpropertydescription//resultMapselectidfindByIdresultMapBaseResultMapparameterTypejava.lang.LongSELECT id, province_id, city_name, description FROM city WHERE id #{id}/selectinsertidsaveCityparameterTypeCityuseGeneratedKeystruekeyPropertyidINSERT INTO city(id, province_id, city_name, description) VALUES(#{id}, #{provinceId}, #{cityName}, #{description})/insertupdateidupdateCityparameterTypeCityUPDATE city SETiftestprovinceId!nullprovince_id #{provinceId},/ififtestcityName!nullcity_name #{cityName},/ififtestdescription!nulldescription #{description}/ifWHERE id #{id}/updatedeleteiddeleteCityparameterTypejava.lang.LongDELETE FROM city WHERE id #{id}/delete/mapper亮点updateCity使用了if动态 SQL只更新传入的非空字段避免将字段误置为 NULL。八、Redis 序列化配置RedisConfig.javaConfigurationpublicclassRedisConfig{BeanpublicRedisTemplateString,ObjectredisTemplate(RedisConnectionFactoryconnectionFactory){RedisTemplateString,ObjecttemplatenewRedisTemplate();template.setConnectionFactory(connectionFactory);StringRedisSerializerstringSerializernewStringRedisSerializer();Jackson2JsonRedisSerializerObjectjsonSerializernewJackson2JsonRedisSerializer(Object.class);template.setKeySerializer(stringSerializer);template.setValueSerializer(jsonSerializer);template.setHashKeySerializer(stringSerializer);template.setHashValueSerializer(jsonSerializer);template.afterPropertiesSet();returntemplate;}}为什么需要这个配置Spring Data Redis 默认使用 JDK 序列化存入 Redis 的数据是二进制乱码无法通过redis-cli直接阅读。此配置将 Value 的序列化方式改为Jackson JSON使得缓存中的数据以可读的 JSON 格式存储。部分序列化方式效果KeyStringcity_1清晰可读ValueJackson JSON{id:1,cityName:杭州,...}而非二进制乱码九、业务层 —— 核心缓存逻辑CityServiceImpl.java这是整个项目最核心的文件实现了经典的Cache-Aside旁路缓存策略9.1 查询逻辑 —— 先缓存后数据库publicCityfindCityById(Longid){Stringkeycity_id;ValueOperationsString,CityoperationsredisTemplate.opsForValue();// 1. 先查缓存booleanhasKeyredisTemplate.hasKey(key);if(hasKey){Citycityoperations.get(key);LOGGER.info(从缓存中获取了城市 city);returncity;}// 2. 缓存未命中查数据库CitycitycityDao.findById(id);if(citynull){LOGGER.warn(数据库未找到城市id{},id);returnnull;}// 3. 写入缓存TTL 500 秒operations.set(key,city,500,TimeUnit.SECONDS);LOGGER.info(城市插入缓存 city);returncity;}流程图查询请求 → Redis 缓存命中 ├─ 是 → 直接返回缓存数据 ✅ └─ 否 → 查询 MySQL DB ├─ 找到 → 写入 RedisTTL 500s→ 返回 └─ 未找到 → 返回 null不缓存空值9.2 更新逻辑 —— 先更新数据库再删除缓存publicLongupdateCity(Citycity){LongretcityDao.updateCity(city);// 删除缓存保证下次查询走数据库获取最新数据Stringkeycity_city.getId();booleanhasKeyredisTemplate.hasKey(key);if(hasKey){redisTemplate.delete(key);LOGGER.info(从缓存中删除城市 city);}returnret;}9.3 删除逻辑 —— 先删除数据库再删除缓存publicLongdeleteCity(Longid){LongretcityDao.deleteCity(id);Stringkeycity_id;booleanhasKeyredisTemplate.hasKey(key);if(hasKey){redisTemplate.delete(key);LOGGER.info(从缓存中删除城市 ID id);}returnret;}缓存策略总结操作步骤原因查询查缓存 → 未命中查 DB → 写缓存减少数据库压力更新更新 DB → 删除缓存保证数据一致性下次查询自动加载新数据删除删除 DB → 删除缓存防止缓存中存在已删除的脏数据这种策略被称为Cache-Aside Pattern旁路缓存模式是业界最常用的缓存方案。十、REST 接口层CityRestController.javaRestControllerpublicclassCityRestController{AutowiredprivateCityServicecityService;RequestMapping(value/api/city/{id},methodRequestMethod.GET)publicResultObjectfindOneCity(PathVariable(id)Longid){...}RequestMapping(value/api/city,methodRequestMethod.POST)publicResultCitycreateCity(RequestBodyCitycity){...}RequestMapping(value/api/city,methodRequestMethod.PUT)publicResultCitymodifyCity(RequestBodyCitycity){...}RequestMapping(value/api/city/{id},methodRequestMethod.DELETE)publicResultLongdeleteCity(PathVariable(id)Longid){...}}API 接口一览方法路径功能请求示例GET/api/city/{id}查询城市GET /api/city/1POST/api/city新增城市POST /api/city JSON BodyPUT/api/city更新城市PUT /api/city JSON BodyDELETE/api/city/{id}删除城市DELETE /api/city/1十一、统一响应封装Result.javapublicclassResultT{privateintcode;// 200 成功privateStringmessage;// 响应提示privateTdata;// 业务数据publicstaticTResultTsuccess(Tdata){...}publicstaticTResultTerror(intcode,Stringmessage){...}}所有接口统一返回ResultT格式{code:200,message:success,data:{id:1,provinceId:1,cityName:杭州,description:人间天堂}}十二、整体数据流转┌──────────┐ ┌──────────────┐ ┌───────────────┐ ┌──────────┐ │ 客户端 │────▶│ Controller │────▶│ Service │────▶│ Redis │ │ 请求 │────│ 接口层 │◀────│ 业务层 │◀────│ 缓存 │ └──────────┘ └──────────────┘ └───────┬───────┘ └──────────┘ │ ▼ ┌──────────────┐ │ MySQL │ │ 数据库 │ └──────────────┘完整请求流程客户端发起GET /api/city/1请求CityRestController接收请求调用CityService.findCityById(1)CityServiceImpl先查询 Redis 缓存city_1缓存命中 → 直接返回缓存未命中 → 通过CityDao查询 MySQL查询结果写入 RedisTTL 500 秒然后返回给客户端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 缓存来提升系统性能。