优雅的缓存解决方案--SpringCache和Redis集成(SpringBoot)

1. 前言

一个系统在于数据库交互的过程当中,内存的速度远远快于硬盘速度,当咱们重复地获取相同数据时,咱们一次又一次地请求数据库或远程服务,者无疑时性能上地浪费(这会致使大量时间被浪费在数据库查询或者远程方法调用上导致程序性能恶化),因而有了“缓存”。 本文将介绍在spring boot项目开发中怎样使用spring提供的Spring Cache 与最近很火的 Redis 数据库来实现数据的缓存。html

2. SpringCache简介

Spring CacheSpring框架提供的对缓存使用的抽象类,支持多种缓存,好比RedisEHCache等,集成很方便。同时提供了多种注解来简化缓存的使用,可对方法进行缓存。java

2.1 关于SpringCache 注解的简单介绍

  • @Cacheable:标记在一个方法上,也能够标记在一个类上。主要是缓存标注对象的返回结果,标注在方法上缓存该方法的返回值,标注在类上,缓存该类全部的方法返回值。 参数: value缓存名、 key缓存键值、 condition知足缓存条件、unless否决缓存条件

@Cacheable

  • @CacheEvict:从缓存中移除相应数据。

@CacheEvict

  • @CachePut:方法支持缓存功能。与@Cacheable不一样的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在以前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

@CachePut

  • @Caching:多个Cache注解使用,好比新增用户时,删除用户属性等须要删除或者更新多个缓存时,集合以上三个注解。

2.2 SpEL上下文数据

Spring Cache提供了一些供咱们使用的SpEL上下文数据,下表直接摘自Spring官方文档:git

名字 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodName
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象 #root.target
targetClass root对象 当前被调用的目标对象类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表(如@Cacheable(value={"cache1", "cache2"})),则有两个cache #root.caches[0].name
argument name 执行上下文 当前被调用的方法的参数,如findById(Long id),咱们能够经过#id拿到参数 #user.id
result 执行上下文 方法执行后的返回值(仅当方法执行以后的判断有效,如‘unless’,'cache evict'的beforeInvocation=false) #result

其余关于 Cache 详细配置或注解,请参考文章基于Redis的Spring cache 缓存介绍或spring官方文档github

3. Redis简介

Redis 是彻底开源免费的,遵照BSD协议,是一个高性能的key-value数据库。web

Redis 与其余 key - value 缓存产品有如下三个特色:redis

  • Redis支持数据的持久化,能够将内存中的数据保存在磁盘中,重启的时候能够再次加载进行使用。
  • Redis不只仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。

Redis 的安装和使用请自行Google 。spring

4. 实践--SpringCache和Redis集成

4.1 步骤

咱们要把一个查询函数加入缓存功能,大体须要三步。数据库

  • 1、在函数执行前,咱们须要先检查缓存中是否存在数据,若是存在则返回缓存数据。
  • 2、若是不存在,就须要在数据库的数据查询出来。
  • 3、最后把数据存放在缓存中,当下次调用此函数时,就能够直接使用缓存数据,减轻了数据库压力。

本实例没有存入MySQL数据库,主要是为了方便实践,实际使用中你们能够把service层中的方法改成数据库操做代码便可。json

4.2 具体操做

添加依赖

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

注: 其实咱们从官方文档能够看到spring-boot-starter-data-redis 已经包含了jedis客户端,咱们在使用jedis链接池的时候没必要再添加jedis依赖。缓存

jedis

配置SpringCache,Redis链接等信息

  • SpringCache配置--RedisConfig.java
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/** * @Author: MaoLin * @Date: 2019/3/26 17:04 * @Version 1.0 */

@Configuration
@EnableCaching
public class RedisConfig {

    /** * 申明缓存管理器,会建立一个切面(aspect)并触发Spring缓存注解的切点(pointcut) * 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值 * @return */
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return RedisCacheManager.create(redisConnectionFactory);
    }

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        // 建立一个模板类
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        // 将刚才的redis链接工厂设置到模板类中
        template.setConnectionFactory(factory);
        // 设置key的序列化器
        template.setKeySerializer(new StringRedisSerializer());
        // 设置value的序列化器
        //使用Jackson 2,将对象序列化为JSON
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //json转对象类,不设置默认的会将json转成hashmap
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);

        return template;
    } 
}

复制代码
  • redis配置--application.yml
server:
  port: 8080
spring:
  # redis相关配置
  redis:
    database: 0
    host: localhost
    port: 6379
    password: 123456
    jedis:
      pool:
        # 链接池最大链接数(使用负值表示没有限制)
        max-active: 8
        # 链接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
        # 链接池中的最大空闲链接
        max-idle: 8
        # 链接池中的最小空闲链接
        min-idle: 0
    # 链接超时时间(毫秒)默认是2000ms
    timeout: 2000ms
  cache:
    redis:
      ## Entry expiration in milliseconds. By default the entries never expire.
      time-to-live: 1d
      #写入redis时是否使用键前缀。
      use-key-prefix: true
复制代码

编写实体类

import lombok.Data;
import java.io.Serializable;

/** * @Author: MaoLin * @Date: 2019/3/24 14:36 * @Version 1.0 */

@Data
//lombok依赖,可省略get set方法
public class User implements Serializable {

    private int userId;

    private String userName;

    private String userPassword;

    public User(int userId, String userName, String userPassword) {
        this.userId = userId;
        this.userName = userName;
        this.userPassword = userPassword;
    }
}

复制代码

service简单操做

import com.ml.demo.entity.User;
import org.springframework.stereotype.Service;

/** * @Author: MaoLin * @Date: 2019/3/24 14:38 * @Version 1.0 */
@Service
public class UserDao {

    public User getUser(int userId) {
        System.out.println("执行此方法,说明没有缓存,若是没有走到这里,就说明缓存成功了");
        User user = new User(userId, "没有缓存_"+userId, "password_"+userId);
        return user;
    }

    public User getUser2(int userId) {
        System.out.println("执行此方法,说明没有缓存,若是没有走到这里,就说明缓存成功了");
        User user = new User(userId, "name_nocache"+userId, "nocache");
        return user;
    }
}
复制代码

控制层

  • 在方法上添加相应的方法便可操做缓存了,SpringCache 对象能够对redis自行操做,减小了不少工做啊,仍是那个开箱即用的Spring
import com.ml.demo.dao.UserDao;
import com.ml.demo.entity.User;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

/** * @Author: MaoLin * @Date: 2019/3/26 17:03 * @Version 1.0 */


@RestController
public class testController {
    @Resource
    private UserDao userDao;

    /** * 查询出一条数据而且添加到缓存 * * @param userId * @return */
    @RequestMapping("/getUser")
    @Cacheable("userCache")
    public User getPrud(@RequestParam(required = true) String userId) {
        System.out.println("若是没有缓存,就会调用下面方法,若是有缓存,则直接输出,不会输出此段话");
        return userDao.getUser(Integer.parseInt(userId));
    }

    /** * 删除一个缓存 * * @param userId * @return */
    @RequestMapping(value = "/deleteUser")
    @CacheEvict("userCache")
    public String deleteUser(@RequestParam(required = true) String userId) {
        return "删除成功";
    }

    /** * 添加一条保存的数据到缓存,缓存的key是当前user的id * * @param user * @return */
    @RequestMapping("/saveUser")
    @CachePut(value = "userCache", key = "#result.userId +''")
    public User saveUser(User user) {
        return user;
    }


    /** * 返回结果userPassword中含有nocache字符串就不缓存 * * @param userId * @return */
    @RequestMapping("/getUser2")
    @CachePut(value = "userCache", unless = "#result.userPassword.contains('nocache')")
    public User getUser2(@RequestParam(required = true) String userId) {
        System.out.println("若是走到这里说明,说明缓存没有生效!");
        User user = new User(Integer.parseInt(userId), "name_nocache" + userId, "nocache");
        return user;
    }


    @RequestMapping("/getUser3")
    @Cacheable(value = "userCache", key = "#root.targetClass.getName() + #root.methodName + #userId")
    public User getUser3(@RequestParam(required = true) String userId) {
        System.out.println("若是第二次没有走到这里说明缓存被添加了");
        return userDao.getUser(Integer.parseInt(userId));
    }
}

复制代码

接下来最重要的工做:跑起来

运行结果

  • 存入数据:

  • 从缓存读取数据:

  • 删除缓存:

  • 再读取:

  • 此时没有缓存,调用方法,并存入缓存

  • 此为cache中的条件:含有nocache字符时不存入缓存。本身去探索就好。

5. 小结&参考资料

小结

为了实现缓存,在网上参考了不少博客、资料,可是都不尽人意,后来通过几天的学习,发现Spring提供了缓存对象,我结合redis,优雅地实现了缓存。学习代码是个艰辛的过程,我在学习这部分时看了好多书,找了好多博客资料,终于找到了合适的缓存方案,很开心,不过这还只是一小步啊,加油!!!

参考资料

相关文章
相关标签/搜索