SpringBoot学习之应用开发篇(二)1.6 - Redis缓存

目录

一、Redis缓存数据

1、pom.xml增加依赖

2、application*的配置

3、Redis配置类与缓存类

3-1、创建Redis配置类

3-2、Redis缓存类

4、测试类

5、测试结果

5-1、模式一测试结果

5-2、模式二测试结果

6、项目目录


一、Redis缓存数据

1、pom.xml增加依赖

<!-- Redis缓存(1.4版本后多了个data) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--  lettuce集成Redis服务,需要导入此包(Jedis直连模式不需要导入它) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

2、application*的配置

# 在application.properties文件中,加入配置
# 配置日志输出级别以观察SQL的执行情况
logging.level.org.springboot.springboot01=debug

# 在application-dev.properties文件中,加入配置
######################## Redis配置 ########################
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=123456
# 连接超时时间(毫秒)
spring.redis.timeout=5000

##### Jedis是直连模式,多个线程间共享一个Jedis实例时,线程不安全。
##### 可通过创建Jedis实例来解决,但连接数量增多时,物理连接成本较高,会影响性能
##### 较好的解决方法是:使用JedisPool
## 连接池中的最小空闲连接,默认值是0。
#spring.redis.jedis.pool.min-idle=50
## 连接池中的最大空闲连接,默认值是8。
#spring.redis.jedis.pool.max-idle=500
## 最大连接数,如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)
#spring.redis.jedis.pool.max-active=1000
## 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException
#spring.redis.jedis.pool.max-wait=2000

##### Lettuce的连接是基于Netty的,连接实例可以在多个线程间共享
##### Netty可以使多线程的应用使用同一个连接实例,而不用担心并发线程的数量
##### 通过异步的方式可以让我们更好地利用系统资源
# 连接池中的最小空闲连接,默认值是0。
spring.redis.lettuce.pool.min-idle=50
# 连接池中的最大空闲连接,默认值是8。
spring.redis.lettuce.pool.max-idle=500
# 最大连接数,如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)
spring.redis.lettuce.pool.max-active=1000
# 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException
spring.redis.lettuce.pool.max-wait=2000

3、Redis配置类与缓存类

3-1、创建Redis配置类

在org.springboot.springboot01下,新增config包,并创建配置类:RedisConfig

/**
 * Redis配置
 *
 * Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,
 * 应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,
 * 当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例
 *
 * RedisTemplate和StringRedisTemplate的区别:
 *  1. 两者的关系是StringRedisTemplate继承RedisTemplate。
 *  2. 两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。
 *  3. SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
 *      StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
 *      RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    private static final Logger log = LoggerFactory.getLogger(RedisConfig.class);

    /**
     * 模式一
     * 配置自定义redisTemplate(用于org.springboot.springboot01.utils.RedisUtils自定义方法的使用)
     *
     * @param redisConnectionFactory redisConnectionFactory
     * @return RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 配置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer(Object.class);

        // 设置值(value)的序列化采用Jackson2JsonRedisSerializer
        redisTemplate.setHashKeySerializer(serializer);
        redisTemplate.setValueSerializer(serializer);

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

    /**
     * 模式二
     * 使用注解配置形式:自定义缓存Key的生成策略
     * @return 缓存Key
     */
    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            for (Object obj : params) {
                sb.append(obj.toString());
            }
            log.info("keyGenerator的结果值:{}", sb.toString());
            return sb.toString();
        };
    }

    /**
     * 模式二
     * 使用注解配置形式,如@Cacheable
     *
     * @param redisConnectionFactory redisConnectionFactory
     * @return CacheManager
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
                this.setCacheConfigurationWithTtl(7200), this.initialCacheConfigurations());
    }

    /**
     * 默认策略,未配置的 key,会使用这个
     *
     * @param time 设定的过期时间,秒为单位
     *
     * @return RedisCacheConfiguration
     */
    private RedisCacheConfiguration setCacheConfigurationWithTtl(long time) {
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        serializer.setObjectMapper(om);

        return RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer)).entryTtl(Duration.ofSeconds(time));
    }

    /**
     * 指定的过期策略
     * 失效间隔时间 0 秒 (表示永久缓存)
     * 失效间隔时间 1800 秒 (0.5小时)
     * 失效间隔时间 3600 秒 (1小时)
     * 失效间隔时间 7200 秒 (2小时)
     * 失效间隔时间 21600 秒 (6小时)
     *
     * @return Map<String, RedisCacheConfiguration>
     */
    private Map<String, RedisCacheConfiguration> initialCacheConfigurations() {
        Map<String, RedisCacheConfiguration> redisMap = new HashMap<>();
        redisMap.put("StudentList", setCacheConfigurationWithTtl(0));
        redisMap.put("UserList", setCacheConfigurationWithTtl(3600));

        return redisMap;
    }
}

3-2、Redis缓存类

3-2-1、在org.springboot.springboot01.utils下,创建工具缓存类:RedisUtils(模式一使用的)

@Component
public class RedisUtils {
    private static final Logger log = LoggerFactory.getLogger(RedisUtils.class);

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            log.error("判断key={}是否存在异常", key, e);
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(Arrays.asList(key));
            }
        }
    }

    /**
     * 放入缓存
     *
     * @param key 键
     * @param value 值
     * @return boolean
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
        } catch (Exception e) {
            log.error("key={}, value={}放入缓存失败", key, value, e);
            return false;
        }
        return true;
    }

    /**
     * 放入缓存,并设定有效期
     *
     * @param key 键
     * @param value 值
     * @param time 指失效时间(以秒为单位)
     * @return boolean
     */
    public boolean set(String key, Object value, long time) {
        try {
            // 放入缓存
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
        } catch (Exception e) {
            log.error("key={}, value={}放入缓存失败", key, value, e);
            return false;
        }
        return true;
    }

    /**
     * 读取缓存
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }
}

3-2-2、在org.springboot.springboot01.service和org.springboot.springboot01.service.impl和下,创建注解缓存类:CacheService和CacheServiceImpl(模式二使用的)

// 缓存服务接口
public interface CacheService {

    List<User> listUser();

    List<Student> listStudent();

    List<Student> cacheStudentList();
}

// 缓存实现接口类
/**
 * unless="#result == null":不对结果为null的进行缓存(函数返回值符合条件的排除掉、只缓存其余不符合条件的)
 * 缓存的key使用的是自定义方法keyGenerator,也可以自己设置
 * 更新缓存:@CachePut
 * 获取缓存(如果没有就去数据库找,找到就重新放入缓存):@Cacheable
 */
@Service
public class CacheServiceImpl implements CacheService {
    private static final Logger log = LoggerFactory.getLogger(CacheServiceImpl.class);

    @Resource
    private MysqlStudentMapper mysqlStudentMapper;

    @Override
    @CachePut(value = "UserList", keyGenerator = "keyGenerator", unless = "#result == null")
    public List<User> listUser() {
        log.debug("进入listUser方法,获取用户信息,并缓存");
        List<User> userList = new ArrayList<>();
        userList.add(new User("wangwu", "王五", "111111", "超级管理员", "17777777777"));
        userList.add(new User("zhangsan", "张三", "123456", "管理员", "17677777777"));
        return userList;
    }

    @Override
    @CachePut(value = "StudentList", keyGenerator = "keyGenerator", unless = "#result == null")
    public List<Student> listStudent() {
        log.debug("进入listStudent方法,获取学生信息,并缓存");
        return mysqlStudentMapper.listAllStudent();
    }

    @Override
    @Cacheable(value = "StudentList", keyGenerator = "keyGenerator", unless = "#result == null")
    public List<Student> cacheStudentList() {
        log.debug("未获得cacheStudentList方法的缓存");
        return null;
    }
}

4、测试类

在src/test/java下,新增org.springboot.springboot01包,并新增测试类ApplicationTest

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class ApplicationTest {
    private static final Logger log = LoggerFactory.getLogger(ApplicationTest.class);

    @Resource
    private RedisUtils redisUtils;
    @Resource
    private CacheService cacheService;

    @Test
    public void testMode1() {
        redisUtils.set("k001", "v001");
        log.debug("把key=k001,value=v001放入缓存");

        Object value = redisUtils.get("k001");
        log.debug("获取key=k001的缓存值:{}", value);

        redisUtils.set("k002", "v002", 100);
        log.debug("设置key=k002的缓存有效时间");
    }

    @Test
    public void testMode2() {
        log.debug("查询学生信息:{},并缓存", JSON.toJSONString(cacheService.listStudent()));
        log.debug("查询用户信息:{},并缓存", JSON.toJSONString(cacheService.listUser()));
        log.debug("获取学生缓存信息:{}", JSON.toJSONString(cacheService.cacheStudentList()));
    }
}

5、测试结果

测试操作:选中要测试的方法,点击右键,出现如下图片所示(Run普通运行,Debug可打断点运行)

5-1、模式一测试结果

5-1-1、设置了两个key值(使用的是RedisDesktopManager客户端查看)

5-1-2、没有设置有效期的

5-1-3、设置了有效期的

5-2、模式二测试结果

5-2-1、无参方法(使用的是RedisDesktopManager客户端查看)

5-2-2、有参方法(使用的是RedisDesktopManager客户端查看)

// 1. 测试类方法testMode2中,增加下面的调用
log.debug("查询用户信息:{},并缓存", JSON.toJSONString(cacheService.listUser("wangwu", "17777777777")));

// 2. 增加接口方法
List<User> listUser(String userName, String phoneNo);

// 3. 实现类实现接口方法
@Override
@CachePut(value = "UserList", keyGenerator = "keyGenerator", unless = "#result == null")
public List<User> listUser(String userName, String phoneNo) {
    log.debug("进入listUser方法,获取用户信息,并缓存");
    List<User> userList = new ArrayList<>();
    userList.add(new User("wangwu", "王五", "111111", "超级管理员", "17777777777"));
    userList.add(new User("zhangsan", "张三", "123456", "管理员", "17677777777"));
    return userList;
}

增加了上面三步程序代码,就可以测试有参数的方法缓存的情况,缓存的内容,如下所示(key值是【value = "UserList"】中的UserList加上::,然后再加上参数值组合);参数值组合可参照keyGenerator自定义生成策略。

6、项目目录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员的微笑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值