第15章 Springboot集成Redis实现缓存

1,163 阅读5分钟

任务描述

任务要求

使用IDEA开发工具构建一个项目多模块工程。study-springboot-chapter09学习关于Springboot集成redis缓存知识点

  1. 基于study-springboot工程,复制study-springboot-chapter09标准项目,坐标groupId(com.cbitedu)、artifactId(study-springboot-chapter09),其他默认
  2. 继承study-springboot工程依赖
  3. 详细学习Spring Mybatis。
  4. 引入缓存框架redis实现数据缓存

任务收获

  1. 如何集成第三方持久化技术Spring Mybatis
  2. 如何引入MySQL数据库依赖
  3. Spring Boot中整合redis
  4. 学会使用JUnit完成单元测试
  5. Redis缓存的使用

任务准备

环境要求

  1. JDK1.8+
  2. MySQL8.0.27+
  3. Maven 3.6.1+
  4. IDEA/VSCode

数据库准备

创建数据库platform,并创建城市表。

-- ----------------------------
-- Table structure for city
-- ----------------------------
DROP TABLE IF EXISTS `city`;
CREATE TABLE `city`
(
    `id`          int                                                           NOT NULL AUTO_INCREMENT COMMENT '主键',
    `province_id` int                                                           NULL DEFAULT NULL COMMENT '所属省份',
    `city_name`   varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '城市名称',
    `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci         NULL COMMENT '描述',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB
  CHARACTER SET = utf8mb4
  COLLATE = utf8mb4_0900_ai_ci
  ROW_FORMAT = Dynamic;

工程目录要求

study-springboot-chapter09

任务实施

如何在Spring Boot的缓存支持中使用Redis实现数据缓存。

第一步:pom.xml中增加相关依赖:

 <!-- Spring Boot Data Cache 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <!-- Spring Boot Redis 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!-- Spring Boot Mybatis 依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
         <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

第二步:配置文件中增加配置信息,以本地运行为例,重点关注redis配置一段。比如:

#服务配置
server:
  port: 81
#   #设置日志相关打印sql 语句
logging:
  level:
    com.cbitedu.springboot: debug
    org.springframework.web: debug
#关闭运行日志图标(banner)
mybatis:
  #  # 指定全局配置文件位置
  config-location: classpath:mybatis/mybatis-config.xml
  #  # 指定sql映射文件位置
  mapper-locations: classpath:mybatis/mapper/*.xml
spring:
  datasource:
    url:  jdbc:mysql://localhost:3306/platform?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    username:  root
    password:  root
    driver-class-name:  com.mysql.cj.jdbc.Driver
  #配置redis缓存
  redis:
    # Redis 数据库索引(默认为 0)
    database: 0
    # Redis 服务器地址
    host: localhost
    # Redis 服务器连接端口
    port: 6379
    password:
    timeout: 10s
    lettuce:
      pool:
        #连接池最大连接数(使用负值表示没有限制) 默认 8
        max-active: 50
        #连接池中的最大空闲连接 默认 8
        max-idle: 8
        #连接池中的最小空闲连接 默认 0
        min-idle: 0

关于连接池的配置,注意几点:

  1. Redis的连接池配置在1.x版本中前缀为spring.redis.pool与Spring Boot 2.x有所不同。
  2. 在1.x版本中采用jedis作为连接池,而在2.x版本中采用了lettuce作为连接池
  3. 以上配置均为默认值,实际上生产需进一步根据部署情况与业务要求做适当修改.

第三步:完成任务准备中的数据库工作,创建表

第四步:分别在com.cbitedu.springboot下创建软件包

  • 实体类:entity
  • 接口层:mapper
  • 服务层:service
  • 控制台:controller

创建city表的映射对象City

com.cbitedu.springboot.entity

package com.cbitedu.springboot.entity;

import java.io.Serializable;

/**
 * 城市实体类
 *
 *
 */
public class City implements Serializable {

    private static final long serialVersionUID = -1L;

    /**
     * 城市编号
     */
    private Long id;

    /**
     * 省份编号
     */
    private Long provinceId;

    /**
     * 城市名称
     */
    private String cityName;

    /**
     * 描述
     */
    private String description;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getProvinceId() {
        return provinceId;
    }

    public void setProvinceId(Long provinceId) {
        this.provinceId = provinceId;
    }

    public String getCityName() {
        return cityName;
    }

    public void setCityName(String cityName) {
        this.cityName = cityName;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @Override
    public String toString() {
        return "City{" +
                "id=" + id +
                ", provinceId=" + provinceId +
                ", cityName='" + cityName + ''' +
                ", description='" + description + ''' +
                '}';
    }
}

创建映射接口mapper( com.cbitedu.springboot.dao

package com.cbitedu.springboot.dao;

import com.cbitedu.springboot.entity.City;
import org.apache.ibatis.annotations.Param;
import java.util.List;

/**
 * 城市 DAO 接口类
 *
 *
 */
public interface CityDao {

    /**
     * 获取城市信息列表
     *
     * @return
     */
    List<City> findAllCity();

    /**
     * 根据城市 ID,获取城市信息
     *
     * @param id
     * @return
     */
    City findById(@Param("id") Long id);

    Long saveCity(City city);

    Long updateCity(City city);

    Long deleteCity(Long id);
}

创建服务接口UserService com.cbitedu.springboot.service

package com.cbitedu.springboot.service;


import com.cbitedu.springboot.entity.City;

import java.util.List;

/**
 * 城市业务逻辑接口类
 *
 *
 */
public interface CityService {
    /**
     * 根据城市 ID,查询城市信息
     *
     * @param id
     * @return
     */
    City findCityById(Long id);

    /**
     * 新增城市信息
     *
     * @param city
     * @return
     */
    Long saveCity(City city);

    /**
     * 更新城市信息
     *
     * @param city
     * @return
     */
    Long updateCity(City city);

    /**
     * 根据城市 ID,删除城市信息
     *
     * @param id
     * @return
     */
    Long deleteCity(Long id);
}

创建服务接口实现类UserServiceImpl com.cbitedu.springboot.service.impl

package com.cbitedu.springboot.service.impl;

import com.cbitedu.springboot.dao.CityDao;
import com.cbitedu.springboot.entity.City;
import com.cbitedu.springboot.service.CityService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * 城市业务逻辑实现类
 * <p>
 *
 */
@Service
public class CityServiceImpl implements CityService {

    private static final Logger LOGGER = LoggerFactory.getLogger(CityServiceImpl.class);

    @Autowired
    private CityDao cityDao;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 获取城市逻辑:
     * 如果缓存存在,从缓存中获取城市信息
     * 如果缓存不存在,从 DB 中获取城市信息,然后插入缓存
     */
    public City findCityById(Long id) {
        // 从缓存中获取城市信息
        String key = "city_" + id;
        ValueOperations<String, City> operations = redisTemplate.opsForValue();

        // 缓存存在
        boolean hasKey = redisTemplate.hasKey(key);
        if (hasKey) {
            City city = operations.get(key);

            LOGGER.info("CityServiceImpl.findCityById() : 从缓存中获取了城市 >> " + city.toString());
            return city;
        }

        // 从 DB 中获取城市信息
        City city = cityDao.findById(id);

        // 插入缓存
        operations.set(key, city, 10, TimeUnit.SECONDS);
        LOGGER.info("CityServiceImpl.findCityById() : 城市插入缓存 >> " + city.toString());

        return city;
    }

    @Override
    public Long saveCity(City city) {
        return cityDao.saveCity(city);
    }

    /**
     * 更新城市逻辑:
     * 如果缓存存在,删除
     * 如果缓存不存在,不操作
     */
    @Override
    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("CityServiceImpl.updateCity() : 从缓存中删除城市 >> " + city.toString());
        }

        return ret;
    }

    @Override
    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("CityServiceImpl.deleteCity() : 从缓存中删除城市 ID >> " + id);
        }
        return ret;
    }

}

再来试试单元测试:SpringCacheTest(redis)

package com.cbitedu.springboot.web;

import com.cbitedu.springboot.entity.City;
import com.cbitedu.springboot.service.CityService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringCacheTest {
    private static final Logger logger = LoggerFactory.getLogger(SpringCacheTest.class);
    @Autowired
    CityService cityService;

    @Test
    public void contextLoads() {
    }

    @Test
    public void TestInsertCity() {
        City city=new City();
        city.setCityName("长沙");
        city.setDescription("中部城市");
        city.setProvinceId(1l);
        cityService.saveCity(city);

    }

    @Test
    public void TestSelectCity() {
        City city=new City();
        city=cityService.findCityById(1l);
       logger.info("第一次查询"+city.toString());
        city=cityService.findCityById(1l);
        logger.info("第二次查询"+city.toString());

    }

}

执行测试输出可以得到:

可以看到:

  1. 第一行输出的CacheManager type为org.springframework.data.redis.cache.RedisCacheManager,执行了数据库查询。
  2. 第二次查询的时候,没有输出SQL语句,所以是走的Redis缓存获取

整合成功!

实验实训

1、场景预设-秒杀系统如何实现

缓存选型

在项目中使用redis做为缓存,还没有使用memcache,考虑因素主要有两点:
1.redis丰富的数据结构,其hash,list,set以及功能丰富的String的支持,对于实际项目中的使用有很大的帮忙。(可参考官网redis.io)
2.redis单点的性能也非常高效(利用项目中的数据测试优于memcache).