Spring Boot 2.x使用篇(五)—— Spring缓存注解与Redis

1、搭建案例测试环境

1.1 创建数据库与数据表

  在MySQL中创建数据表t_user,具体如下:

CREATE TABLE `t_user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_name` varchar(60) NOT NULL DEFAULT '',
  `sex` varchar(3) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '1',
  `note` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

在这里插入图片描述

1.2 添加依赖

  在pom文件中添加MySQL数据库连接驱动类依赖、DBCP相关依赖、MyBatis相关依赖、Redis相关依赖和Jedis相关依赖,并配置build标签使得xml文件有效。具体如下:

<dependencies>
    <!--MySQL数据库连接驱动类-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--引入第三方数据源DBCP依赖-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-dbcp2</artifactId>
    </dependency>
    <!--MyBatis相关依赖-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.1</version>
    </dependency>
    <!--Redis相关依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <exclusions>
            <!--不依赖Redis的异步客户端lettuce -->
            <exclusion>
                <groupId>io.lettuce</groupId>
                <artifactId>lettuce-core</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--引入Redis的客户端驱动jedis -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
</dependencies>

<build>
    <resources>
        <resource>
            <directory>${basedir}/src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
    </resources>
</build>

1.3 创建用户POJO

  首先在enumration包下创建性别枚举类SexEnum,具体如下:

package com.ccff.springboot.demo.chapter7_2.enumration;

/**
 * Created by wangzhefeng01 on 2019/8/12.
 */
public enum SexEnum {
    MALE(1,"男"),
    FEMALE(2,"女");

    private Integer id;
    private String value;

    SexEnum(Integer id, String value) {
        this.id = id;
        this.value = value;
    }

    public static SexEnum getSexEnumById(Integer id){
        for (SexEnum sex : SexEnum.values()) {
            if (sex.getId() == id){
                return sex;
            }
        }
        return null;
    }

    public Integer getId() {
        return id;
    }

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

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

  然后在pojo包下创建名为User的POJO类,具体如下:

package com.ccff.springboot.demo.chapter7_2.pojo;

import com.ccff.springboot.demo.chapter7_2.enumration.SexEnum;
import org.apache.ibatis.type.Alias;

import java.io.Serializable;

/**
 * Created by wangzhefeng01 on 2019/8/12.
 */
@Alias(value = "user")      //定义MyBatis的别名
public class User implements Serializable{
    private static final long serialVersionUID = 7760614561073458247L;
    
    private Long id = null;

    private String userName = null;

    private SexEnum sex = SexEnum.MALE;

    private String note = null;

    public User() {
    }

    public User(Long id, String userName, SexEnum sex, String note) {
        this.id = id;
        this.userName = userName;
        this.sex = sex;
        this.note = note;
    }

    public User(String userName, String note) {
        this.userName = userName;
        this.note = note;
    }

    public User(String userName, SexEnum sex, String note) {
        this.userName = userName;
        this.sex = sex;
        this.note = note;
    }

    public Long getId() {
        return id;
    }

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

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }

    public SexEnum getSex() {
        return sex;
    }

    public void setSex(SexEnum sex) {
        this.sex = sex;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

1.4 创建Dao接口

  在dao包下创建名为UserDao的接口,具体如下:

package com.ccff.springboot.demo.chapter7_2.dao;

import com.ccff.springboot.demo.chapter7_2.pojo.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * Created by wangzhefeng01 on 2019/8/12.
 */
@Repository
public interface UserDao {
    //获取单个用户
    User getUser(Long id);

    //保存用户
    int insertUser(User user);

    //修改用户
    int updateUser(User user);

    //查询用户,指定MyBatis的参数名称
    List<User> findUsers(@Param("userName") String userName, @Param("note") String note);

    //删除用户
    int deleteUser(Long id);
}

1.5 创建MyBatis映射文件

  在mapper包下创建名为UserMapper.xml的MyBatis映射XML文件,具体如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ccff.springboot.demo.chapter7_2.dao.UserDao">
    <select id="getUser" parameterType="long" resultType="user">
        select id, user_name as userName, note from t_user where id = #{id}
    </select>

    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id"
            parameterType="user">
        insert into t_user(user_name, note)
        values(#{userName}, #{note})
    </insert>

    <update id="updateUser">
        update t_user
        <set>
            <if test="userName != null">user_name =#{userName},</if>
            <if test="note != null">note =#{note}</if>
        </set>
        where id = #{id}
    </update>

    <select id="findUsers" resultType="user">
        select id, user_name as userName, note from t_user
        <where>
            <if test="userName != null">
                and user_name = #{userName}
            </if>
            <if test="note != null">
                and note = #{note}
            </if>
        </where>
    </select>

    <delete id="deleteUser" parameterType="long">
        delete from t_user where id = #{id}
    </delete>
</mapper>

1.6 创建服务接口与其实现类

  在service包下创建名为IUserService的服务接口,具体如下:

package com.ccff.springboot.demo.chapter7_2.service;

import com.ccff.springboot.demo.chapter7_2.pojo.User;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * Created by wangzhefeng01 on 2019/8/12.
 */
public interface IUserServcie {
    //获取单个用户
    User getUser(Long id);

    //保存用户
    int insertUser(User user);

    // 修改用户,指定MyBatis的参数名称
    User updateUserName(Long id, String userName);

    //查询用户,指定MyBatis的参数名称
    List<User> findUsers(String userName, String note);

    //删除用户
    int deleteUser(Long id);
}

  在service.impl包下创建名为UserServiceImpl的服务接口实现类,具体如下:

package com.ccff.springboot.demo.chapter7_2.service.impl;

import com.ccff.springboot.demo.chapter7_2.dao.UserDao;
import com.ccff.springboot.demo.chapter7_2.pojo.User;
import com.ccff.springboot.demo.chapter7_2.service.IUserServcie;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * Created by wangzhefeng01 on 2019/8/12.
 */
@Service
public class UserServcieImpl implements IUserServcie {
    @Autowired
    private UserDao userDao = null;

    @Override
    public User getUser(Long id) {
        return userDao.getUser(id);
    }

    @Override
    public int insertUser(User user) {
        return userDao.insertUser(user);
    }

    @Override
    public User updateUserName(Long id, String userName) {
        User user = this.getUser(id);
        if (user == null) {
            return null;
        }
        user.setUserName(userName);
        userDao.updateUser(user);
        return user;

    }

    @Override
    public List<User> findUsers(String userName, String note) {
        return userDao.findUsers(userName, note);
    }

    @Override
    public int deleteUser(Long id) {
        return userDao.deleteUser(id);
    }
}

1.7 创建控制器类

  在controller包下创建名为UserController的控制器类,具体如下:

package com.ccff.springboot.demo.chapter7_2.controller;

import com.ccff.springboot.demo.chapter7_2.pojo.User;
import com.ccff.springboot.demo.chapter7_2.service.IUserServcie;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by wangzhefeng01 on 2019/8/12.
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private IUserServcie userServcie = null;

    @GetMapping("/getUser")
    public User getUser(Long id){
        return userServcie.getUser(id);
    }

    @GetMapping("/insertUser")
    public User insertUser(String userName, String note){
        User user = new User(userName, note);
        userServcie.insertUser(user);
        return user;
    }

    @GetMapping("/findUsers")
    public List<User> findUsers(String userName, String note) {
        return userServcie.findUsers(userName, note);
    }

    @GetMapping("/updateUserName")
    public Map<String, Object> updateUserName(Long id, String userName) {
        User user = userServcie.updateUserName(id, userName);
        boolean flag = user != null;
        String message = flag? "更新成功" : "更新失败";
        return resultMap(flag, message);
    }

    @GetMapping("/deleteUser")
    public Map<String, Object> deleteUser(Long id) {
        int result = userServcie.deleteUser(id);
        boolean flag = result == 1;
        String message = flag? "删除成功" : "删除失败";
        return resultMap(flag, message);
    }

    private Map<String, Object> resultMap(boolean success, String message) {
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("success", success);
        result.put("message", message);
        return result;
    }
}

1.8 配置application.properties文件

  在application.properties文件对连接MySQL数据库四要素、DBCP数据源、MyBatis相关、Redis相关进行配置,具体如下:

# =========================== 配置数据库连接四要素 ===========================
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring_boot_chapter5
spring.datasource.username=root
spring.datasource.password=20161224cc
# =========================== 配置数据库连接四要素 ===========================

# =========================== 配置第三方DBCP2数据源 ===========================
# 指定数据库连接池类型
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
# 最大等待连接中的数量,设0为没有限制
spring.datasource.dbcp2.max-idle=10
# 最大连接活动数
spring.datasource.dbcp2.max-total=50
# 最大等待毫秒数,单位为ms,超过时间会出错误信息
spring.datasource.dbcp2.max-wait-millis=10000
# 数据库连接池初始化连接数
spring.datasource.dbcp2.initial-size=5
# =========================== 配置第三方DBCP2数据源 ===========================

# =========================== 配置MyBatis ===========================
# MyBatis映射文件通配
mybatis.mapper-locations=classpath:com/ccff/springboot/demo/chapter7_2/mapper/*.xml
# MyBatis扫描别名包,和注解@Alias联用
mybatis.type-aliases-package=com.ccff.springboot.demo.chapter7_2.pojo
# 配置typeHandler的扫描包
mybatis.type-handlers-package=com.ccff.springboot.demo.chapter7_2.typehandler
# =========================== 配置MyBatis ===========================

# =========================== 配置Jedis连接池属性 ===========================
spring.redis.jedis.pool.min-idle=5
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=2000
# =========================== 配置Jedis连接池属性 ===========================

# =========================== 配置Redis服务器属性 ===========================
spring.redis.port=6379
spring.redis.host=127.0.0.1
spring.redis.timeout=1000
# =========================== 配置Redis服务器属性 ===========================

# =========================== 配置日志 ===========================
logging.level.root=debug
logging.level.org.springframework=debug
logging.level..org.org.mybatis=debug
# =========================== 配置日志 ===========================

1.9 配置Spring Boot启动类

  为了能够使得MyBatis的mapper映射文件与dao接口实现映射,需要在Spring Boot启动类中添加注解@MapperScan。同时设置了Redis的序列化器。具体如下:

package com.ccff.springboot.demo.chapter7_2;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Repository;

import javax.annotation.PostConstruct;

/**
 * Created by wangzhefeng01 on 2019/8/12.
 */
@SpringBootApplication
@MapperScan(basePackages="com.ccff.springboot.demo.chapter7_2.dao", annotationClass = Repository.class)
@EnableCaching
public class Chapter7_2Application {
    //注入RedisTemplate
    @Autowired
    private RedisTemplate redisTemplate = null;

    //注入Redis连接工厂
    @Autowired
    private RedisConnectionFactory connectionFactory = null;

    //定义Spring自定义后初始化方法
    @PostConstruct
    public void init(){
        initRedisTemplate();
    }

    /**
     * 设置RedisTemplate的序列化器
     */
    private void initRedisTemplate() {
        RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
    }
    
    public static void main(String[] args) {
        SpringApplication.run(Chapter7_2Application.class,args);
    }
}

  至此,本次案例演示开发环境已经搭建完成。当前该环境已经可以成功运行,由于篇幅有限,这里便不再赘述测试。

2、Spring缓存管理器和缓存的启用

  Spring在使用缓存注解之前,需要配置缓存管理器,缓存管理器将提供一些重要的信息,如缓存类型、超时时间等。

  Spring可以支持多种缓存的使用,因此它存在多种缓存管理器,并提供了缓存处理器的接口CacheManager和与之相关的类。而考虑Redis作为缓存,Spring中主要使用的类是RedisCacheManager。

  在Spring Boot中,我们可以通过对application.properties进行配置来生成缓存管理器,常用的缓存管理器的配置如下:

spring.cache.cache-name=	#如果由底层的缓存管理器支持创建,以逗号分隔的列表来缓存名称

spring.cache.redis.cache-null-values=true	#是否允许Redis缓存空值

spring.cache.redis.key-prefix=	#Redis的键前缀

spring.cache.redis.time-to-live=0ms	#缓存超时时间戳,配置为0则不设置超时时间

spring.cache.redis.use-key-prefis=true	#是否启用Redis的键前缀

spring.cache.type=	#缓存类型,在默认的情况下,Spring会根据上下文探测

  为了使用Redis缓存,我们在application.properties配置文件中添加关于配置Redis缓存管理器的配置如下:

# =========================== 配置Redis缓存管理器 ===========================
spring.cache.type=redis
spring.cache.cache-names=redisCache
# =========================== 配置Redis缓存管理器 ===========================

  其中:

  • spring.cache.type:配置的是缓存的类型,为Redis,Spring会自动生成RedisCacheManager对象
  • spring.cache.cache-names:配置的是缓存名称,多个名称可以使用逗号分隔,以便于缓存注解的使用。

  最后,为了使用缓存管理器,需要在Spring Boot的启动类中添加驱动缓存的注解@EnableCaching,这样就可以驱动Spring缓存机制工作了,具体修改如下:

package com.ccff.springboot.demo.chapter7_2;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.stereotype.Repository;

/**
 * Created by wangzhefeng01 on 2019/8/12.
 */
@SpringBootApplication
@MapperScan(basePackages="com.ccff.springboot.demo.chapter7_2.dao", annotationClass = Repository.class)
@EnableCaching
public class Chapter7_2Application {
    public static void main(String[] args) {
        SpringApplication.run(Chapter7_2Application.class,args);
    }
}

3、开发缓存注解

  在配置了缓存管理器后,Spring就允许用注解的方式使用缓存了。这里有以下三个缓存注解:

  • @CachePut:表示将方法结果返回存放到缓存中
  • @Cacheable:表示先从缓存中通过定义的键查询,如果可以查询到数据,则返回;否则执行该方法,返回数据,并将返回结果保存到缓存中
  • @CacheEvict:通过定义的键移除缓存,它有一个Boolean类型的配置项beforeInvocation,表示在方法之前或者之后移除缓存。因其默认值为false,所以默认为方法之后将缓存移除。

  由于注解@CachePut与注解@Cacheable两个注解的配置项比较接近,所以这里就将这两个注解一并介绍,它们的属性如下:

属性配置类型描述
valueString[]使用缓存的名称
conditionStringSpring表达式,如果表达式返回值为false,则不会将缓存应用到方法上,true则会
keyStringSpring表达式,可以通过它来计算对应缓存的key
unlessStringSpring表达式,如果表达式返回值为true,则不会将方法的结果放到缓存上

  注解@CacheEvict的属性如下:

属性配置类型描述
valueString[]使用缓存的名称
conditionStringSpring表达式,如果表达式返回值为true,则执行移除缓存,false则不执行
keyString指定Spring表达式返回缓存的key
allEntriesString如果为true,则删除特定缓存所有键值对,默认值为false,请注意它将清除所有缓存服务器的缓存,这个属性慎用!
beforeInvocationString指定在方法前后移除缓存,如果指定为true,则在方法前删除缓存;如果为false,则在方法调用后删除缓存,默认值为false

  除此之外,我们还需要了解一些Spring表达式和缓存注解之间的约定,通过这些约定去引用方法的参数和返回值的内容,使得其注入key所定义的Spring表达式的结果中,表达式值的引用如下表:

表达式描述备注
#root.args定义传递给缓存方法的参数不常用,不予讨论
#root.caches该方法执行时对应的缓存名称,它是一个数组同上
#root.target执行缓存的目标对象同上
#root.targetClass目标对象的类,它是#root.target.class的缩写同上
#root.method缓存方法同上
#root.methodName缓存方法的名称,它是的#root.method.name缩写同上
#result方法返回结果值,还可以使用Spring表达式进一步读取其属性请注意该表达式不能用于注解@Cacheable,因为该注解的方法可能不会被执行,这样返回值就无从谈起了
#Argument任意方法的参数,可以通过方法本身的名称或者下标去定义比如getUser(Long id)方法,想读取id这个参数,可以写为#id,或者#a0、#p0,这里建议写为#id,这样可读性高

  在服务接口实现类中添加缓存注解,具体如下:

package com.ccff.springboot.demo.chapter7_2.service.impl;

import com.ccff.springboot.demo.chapter7_2.dao.UserDao;
import com.ccff.springboot.demo.chapter7_2.pojo.User;
import com.ccff.springboot.demo.chapter7_2.service.IUserServcie;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * Created by wangzhefeng01 on 2019/8/12.
 */
@Service
public class UserServcieImpl implements IUserServcie {
    @Autowired
    private UserDao userDao = null;

    // 获取id,取参数id缓存用户
    @Override
    @Transactional
    @Cacheable(value = "redisCache", key = "'redis_user_'+#id")
    public User getUser(Long id) {
        return userDao.getUser(id);
    }

    // 插入用户,最后MyBatis会回填id,取结果id缓存用户
    @Override
    @Transactional
    @CachePut(value = "redisCache", key = "'redis_user_'+#result.id")
    public int insertUser(User user) {
        return userDao.insertUser(user);
    }

    // 更新数据后,充值缓存,使用condition配置项使得结果返回为null,不缓存
    @Override
    @Transactional
    @CachePut(value = "redisCache", condition = "#result != 'null'", key = "'redis_user_'+#id")
    public User updateUserName(Long id, String userName) {
        // 此处调用getUser方法,该方法缓存注解失效,
        // 所以这里还会执行SQL,将查询到数据库最新数据
        User user = this.getUser(id);
        if (user == null) {
            return null;
        }
        user.setUserName(userName);
        userDao.updateUser(user);
        return user;

    }

    // 命中率低,所以不采用缓存机制
    @Override
    @Transactional
    public List<User> findUsers(String userName, String note) {
        return userDao.findUsers(userName, note);
    }

    // 移除缓存
    @Override
    @Transactional
    @CacheEvict(value = "redisCache", key = "'redis_user_'+#id", beforeInvocation = false)
    public int deleteUser(Long id) {
        return userDao.deleteUser(id);
    }
}

4、测试缓存注解

  运行Spring Boot启动类后,请求/user/getUser?id=5,即查询id为5的用户信息。由于在服务接口实现类中,在getUser方法上使用了缓存注解@Cacheable,即getUser将方法结果返回存放到缓存中。

  在请求后首先在浏览器内能看到返回查询到的id为5的用户信息,结果如下:
在这里插入图片描述
  然后,打开Redis客户端,查询可以看到对应的缓存信息如下:
在这里插入图片描述
  由上图我们看到,Redis缓存机制会使用#{chcheName}::#{key}的形式作为键保存数据,其次对于这个缓存是永远不会超时的,这样会带来缓存不会被刷新的问题,这在某些时候会存在刷新不及时的问题,需要我们后续进行解决。

5、缓存注解自调用失败问题

  在UserController中,当请求updateUserName方法时,会调用getUser方法,此时在getUser方法上的缓存注解将会失效。

  这时因为Spring的缓存机制也是基于Spring AOP原理的,而在Spring中AOP是通过动态代理技术来实现的,这里updateUserName调用getUser方法是类内部的自调用,并不存在代理对象的调用,这样便不会出现AOP,也就不会使用到标注在getUser方法上的缓存注解去获取缓存的值了,这是需要注意的地方。

  为了解决这个问题,可以参考关于数据库事务两个服务(Service)类相互调用,或者直接从Spring IoC容器中获取代理对象操作,这样才能克服自调用缓存失败的问题。

6、缓存脏数据说明

  使用缓存可以使得系统性能大幅度提高,但是也引发了很多问题,其中最为严重的问题就是脏数据问题。

  一般而言,对于数据的读操作是允许不是实时数据的。例如一些电商网站还存在一些排名榜单,而这个排名往往都不是实时的,它会存在延迟,其实对于查询是可以存在延迟的,也就是存在脏数据是允许的。

  但是如果一个脏数据始终存在就说不通了,这样会造成数据失真比较严重。一般对于查询而言,我们可以规定一个时间,让缓存失效,在Redis中也可以设置超时时间,当缓存超过超时时间后,则应用不再能够从缓存中获取数据,而只能从数据库中重新获取最新数据,以保证数据失真不至于太离谱。

  对于那些要求实时性比较高的数据,我们可以把缓存时间设置的更少一些,这样就会更加频繁地刷新缓存,而不利的是会增加数据库的压力。对于那些要求不是那么高的,则可以使超时时间长一些,这样就可以降低数据库的压力。

  对于数据的写操作,往往采取的策略就完全不一样了,需要我们谨慎一些,一般会认为缓存不可信,所以会考虑从数据库中先读取最新的数据,然后再更新数据,以避免将缓存的脏数据写入数据库中,导致出现业务问题。

7、自定义缓存管理器

  有些时候我们并不希望采用Redis缓存机制所定义的键的生成规则,或不希望缓存永不超时等,此时我们可以自定义缓存管理器。

  在Spring中我们有两种方法定制缓存管理器,一种是通过配置消除缓存键的前缀和自定义超时时间的属性来定制生成RedisCacheManager;另一种方法是不采用Spring Boot为我们生成的方式,而是完全通过自己的代码创建缓存管理器,尤其是当需要比较多自定义的时候,更推荐使用自定义代码的方式。

  首先,修改application.properties配置文件中对缓存管理器的配置,增加对应的新配置,使得Spring Boot为我们生成的RedisCacheManager对象的时候,消除前缀的设置并且设置超时时间,具体配置如下:

# =========================== 配置Redis缓存管理器 ===========================
spring.cache.type=redis
spring.cache.cache-names=redisCache
# 禁用前缀
spring.cache.redis.use-key-prefix=false
# 允许保存空值
spring.cache.redis.cache-null-values=true
# 自定义前缀
spring.cache.redis.key-prefix=
# 定义超时时间,单位为毫秒
spring.cache.redis.time-to-live=600000
# =========================== 配置Redis缓存管理器 ===========================

  有时候,在自定义时可能存在比较多的配置,也可以不采用Spring Boot自动配置的缓存管理器,而是使用自定义的缓存管理器,这样也是没问题的。在去掉application.properties配置文件中对缓存管理器的配置后,然后在Spring Boot的启动类中向Spring IoC容器中增加缓存管理器。

package com.ccff.springboot.demo.chapter7_2;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Repository;
import redis.clients.jedis.Jedis;

import javax.annotation.PostConstruct;
import java.time.Duration;

/**
 * Created by wangzhefeng01 on 2019/8/12.
 */
@SpringBootApplication
@MapperScan(basePackages="com.ccff.springboot.demo.chapter7_2.dao", annotationClass = Repository.class)
@EnableCaching
public class Chapter7_2Application {
    //注入RedisTemplate
    @Autowired
    private RedisTemplate redisTemplate = null;

    //定义Spring自定义后初始化方法
    @PostConstruct
    public void init(){
        initRedisTemplate();
    }

    /**
     * 设置RedisTemplate的序列化器
     */
    private void initRedisTemplate() {
        RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
    }

    //注入连接工厂,由Spring Boot自动配置生成
    @Autowired
    private RedisConnectionFactory redisConnectionFactory = null;
    
    //自定义Redis缓存管理器
    @Bean(name = "redisCacheManager")
    public RedisCacheManager initRedisCacheManager(){
        //Redis枷锁的写入器
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory);
        //启动Redis的默认设置
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
        //设置JDK序列化器
        configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()));
        //禁用前缀
        configuration = configuration.disableKeyPrefix();
        //设置10分钟的超时时间
        configuration = configuration.entryTtl(Duration.ofMinutes(10));
        //创建Redis缓存管理器
        RedisCacheManager redisCacheManager = new RedisCacheManager(writer,configuration);
        
        return redisCacheManager;
    }

    public static void main(String[] args) {
        SpringApplication.run(Chapter7_2Application.class,args);
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值