SpringBoot | 第十一章:Redis 的集成和簡單使用

原文出處: oKong

前言

上幾節(jié)講了利用Mybatis-Plus這個第三方的ORM框架進行數(shù)據(jù)庫訪問,在實際工作中,在存儲一些非結(jié)構(gòu)化或者緩存一些臨時數(shù)據(jù)及熱點數(shù)據(jù)時,一般上都會用上mongodbredis進行這方面的需求。所以這一章節(jié)準備講下緩存數(shù)據(jù)庫Redis的集成,同時會介紹下基于Redis和注解驅(qū)動的Spring Cache的簡單使用。

Redis 介紹

大家應(yīng)該對Redis應(yīng)該比較熟悉了。這幾年也是大行其道的緩存數(shù)據(jù)庫,目前的memcached由于使用場景及其存儲數(shù)據(jù)結(jié)構(gòu)的單一(不知道現(xiàn)在是否有改善,現(xiàn)在基本沒有接觸了),在工作中也使用的少了。引用官網(wǎng)的簡介,Redis是一個開源的使用ANSI C語言編寫、遵守BSD協(xié)議、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫,并提供多種語言的API。

推薦redis中國社區(qū):http://www.redis.cn/

SpringBoot的Redis集成

0.本章節(jié)以上一章節(jié)的示例基礎(chǔ)上進行集成。所以大家可下載第十章節(jié)示例或者在章節(jié)末尾直接下載本章節(jié)示例。

1.pom依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

直接引入,相關(guān)依賴會自動加載的,這就是springboot讓人愉悅之處呀。

2.application.properties配置加入redis相關(guān)配置

配置自動加載類為:org.springframework.boot.autoconfigure.data.redis.RedisProperties,可在屬性文件中點擊某屬性快捷跳轉(zhuǎn)。注意到其啟動類為org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration。這里就不介紹了,后面會寫一篇關(guān)于Springboot自動加載配置的文章。

# REDIS (RedisProperties)
# Redis數(shù)據(jù)庫索引(默認為0)
spring.redis.database=0
# Redis服務(wù)器地址
spring.redis.host=127.0.0.1
# Redis服務(wù)器連接端口
spring.redis.port=6379 
# Redis服務(wù)器連接密碼(默認為空)
spring.redis.password=
# 連接池最大連接數(shù)(使用負值表示沒有限制)
spring.redis.pool.max-active=8 
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.pool.max-wait=-1 
# 連接池中的最大空閑連接
spring.redis.pool.max-idle=8 
# 連接池中的最小空閑連接
spring.redis.pool.min-idle=0 
# 連接超時時間(毫秒)
spring.redis.timeout=0

3.一般上通過以上兩步就可使用了,但工作中一般上是通過StringRedisTemplate(默認采用string的序列化,保存key和值時都是通過此序列化策略)接口進行操作,所以這里直接配置了StringRedisTemplatebean類。
RedisConfig.java

/**
 * 
 * @author oKong
 *
 */
@Configuration
public class RedisConfig {
     
    /**
     *  定義 StringRedisTemplate ,指定序列化和反序列化的處理類
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
                Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //序列化 值時使用此序列化方法
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

4.編寫控制類,測試集成是否生效。
RedisController.java

@RestController
@RequestMapping("/redis")
@Api(tags = "redis 測試API")
public class RedisController {
 
    @Autowired
    StringRedisTemplate redisTemplate;
     
    @GetMapping("set/{key}/{value}")
    @ApiOperation(value="設(shè)置緩存")
    public String set(@PathVariable("key")String key,@PathVariable("value") String value) {
        //注意這里的 key不能為null spring 內(nèi)部有檢驗
        redisTemplate.opsForValue().set(key, value);
        return key + "," + value;
    }
     
    @GetMapping("get/{key}")
    @ApiOperation(value="根據(jù)key獲取緩存")
    public String get(@PathVariable("key") String key) {
         
        return "key=" + key + ",value=" + redisTemplate.opsForValue().get(key);
    }
}

5.訪問:http://127.0.0.1:8080/swagger-ui.html。 也可直接瀏覽器輸入:

set值

set值

get值

get值

瀏覽器訪問

查看redis記錄:

redis記錄

至此,redis就集成好了。實際中可根據(jù)業(yè)務(wù)需要進行相關(guān)操作,比如緩存session記錄,緩存菜單列表等。

Spring Cache 和 redis 使用。

Spring CacheSpring框架提供的對緩存使用的抽象類,支持多種緩存,比如Redis、EHCache等,集成很方便。同時提供了多種注解來簡化緩存的使用,可對方法進行緩存。

0.修改RedisConfig配置類,加入注解@EnableCaching,同時設(shè)置CacheManager緩存管理類,這里使用RedisCacheManager,其他的管理類還有:SimpleCacheManagerConcurrentMapCacheManager等,默認提供的在類org.springframework.cache.support下,可自行查閱。

/**
 * 
 * @author oKong
 *
 */
@Configuration
@EnableCaching
public class RedisConfig {
     
    /**
     *  定義 StringRedisTemplate ,指定序列號和反序列化的處理類
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
                Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //序列化 值時使用此序列化方法
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
     
    @Bean
    public CacheManager cacheManager(RedisTemplate<String,String> redisTemplate) {
        RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
        //使用前綴
        rcm.setUsePrefix(true);
        //緩存分割符 默認為 ":"
//        rcm.setCachePrefix(new DefaultRedisCachePrefix(":"));
        //設(shè)置緩存過期時間
        //rcm.setDefaultExpiration(60);//秒
        return rcm;
    }
}

1.改造UserController控制層,引入@Cacheable等注解。

/**
 * 用戶控制層 簡單演示增刪改查及分頁
 * 新增了swagger文檔內(nèi)容 2018-07-21
 * 新增了@caching使用 2018-07-23
 * @author oKong
 *
 */
@RestController
@RequestMapping("/user")
@Api(tags="用戶API")
public class UserController {
 
    @Autowired
    IUserService userService;
     
    @PostMapping("add")
    @ApiOperation(value="用戶新增")
    //正常業(yè)務(wù)時, 需要在user類里面進行事務(wù)控制,控制層一般不進行業(yè)務(wù)控制的。
    //@Transactional(rollbackFor = Exception.class)
    public Map<String,String> addUser(@Valid @RequestBody UserReq userReq){
         
        User user = new User();
        user.setCode(userReq.getCode());
        user.setName(userReq.getName());
        //由于設(shè)置了主鍵策略 id可不用賦值 會自動生成
        //user.setId(0L);
        userService.insert(user);
        Map<String,String> result = new HashMap<String,String>();
        result.put("respCode", "01");
        result.put("respMsg", "新增成功");
        //事務(wù)測試
        //System.out.println(1/0);
        return result;
    }
     
    @PostMapping("update")
    @ApiOperation(value="用戶修改")    
    //更新時 直接刪除緩存 以保證下次獲取時先從數(shù)據(jù)庫中獲取最新數(shù)據(jù)
    @CacheEvict(value="OKONG", key="#userReq.id")
    public Map<String,String> updateUser(@Valid @RequestBody UserReq userReq){
         
        if(userReq.getId() == null || "".equals(userReq.getId())) {
            throw new CommonException("0000", "更新時ID不能為空");
        }
        User user = new User();
        user.setCode(userReq.getCode());
        user.setName(userReq.getName());
        user.setId(Long.parseLong(userReq.getId()));        
        userService.updateById(user);
        Map<String,String> result = new HashMap<String,String>();
        result.put("respCode", "01");
        result.put("respMsg", "更新成功");
        return result;
    }
     
    @GetMapping("/get/{id}")
    @ApiOperation(value="用戶查詢(ID)")    
    @ApiImplicitParam(name="id",value="查詢ID",required=true)
    @Cacheable(value="OKONG",key="#id")
    public Map<String,Object> getUser(@PathVariable("id") String id){
        //查詢
        User user = userService.selectById(id);
        if(user == null) {
            throw new CommonException("0001", "用戶ID:" + id + ",未找到");
        }
        UserResp resp = UserResp.builder()
                .id(user.getId().toString())
                .code(user.getCode())
                .name(user.getName())
                .status(user.getStatus())
                .build();
        Map<String,Object> result = new HashMap<String,Object>();
        result.put("respCode", "01");
        result.put("respMsg", "成功");
        result.put("data", resp);
        return result;
    }
     
    @GetMapping("/page")
    @ApiOperation(value="用戶查詢(分頁)")        
    public Map<String,Object> pageUser(int current, int size){
        //分頁
        Page<User> page = new Page<>(current, size);
        Map<String,Object> result = new HashMap<String,Object>();
        result.put("respCode", "01");
        result.put("respMsg", "成功");
        result.put("data", userService.selectPage(page));
        return result;
    }
         
}

2.利用Swagger控制頁面,新增一個用戶,然后獲取用戶,會發(fā)現(xiàn)緩存里已經(jīng)有此id的用戶數(shù)據(jù)了。

第一次獲取

redis查看:

redis

再次獲取,會發(fā)現(xiàn)這次沒有直接訪問數(shù)據(jù)庫了,而是直接從緩存讀取。大家可在觀察下控制臺的輸出情況(可先清空控制臺,然后在請求)。

控制臺

此時控制臺無任何輸出,但前端已經(jīng)獲取到值了。

關(guān)于SpringCache 注解的簡單介紹

  • @Cacheable:標記在一個方法上,也可以標記在一個類上。主要是緩存標注對象的返回結(jié)果,標注在方法上緩存該方法的返回值,標注在類上,緩存該類所有的方法返回值。
    參數(shù): value緩存名、 key緩存鍵值、 condition滿足緩存條件、unless否決緩存條件

  • @CacheEvict:從緩存中移除相應(yīng)數(shù)據(jù)。

  • @CachePut:方法支持緩存功能。與@Cacheable不同的是使用@CachePut標注的方法在執(zhí)行前不會去檢查緩存中是否存在之前執(zhí)行過的結(jié)果,而是每次都會執(zhí)行該方法,并將執(zhí)行結(jié)果以鍵值對的形式存入指定的緩存中。

  • @Caching:多個Cache注解使用,比如新增用戶時,刪除用戶屬性等需要刪除或者更新多個緩存時,集合以上三個注解。

常用的就以上幾個,對于@CacheConfig沒使用過,這里就不說明了。

對于對幾個注解類的簡單使用就結(jié)束了,相關(guān)的詳細用法,比如自定義條件緩存,自定義注解等,這里就不闡述了,請讀者自行

SpEL上下文數(shù)據(jù)

Spring Cache提供了一些供我們使用的SpEL上下文數(shù)據(jù),下表直接摘自互聯(lián)網(wǎng)

名稱 位置 描述 示例
methodName root對象 當前被調(diào)用的方法名 root.methodName
method root對象 當前被調(diào)用的方法 root.method.name
target root對象 當前被調(diào)用的目標對象 root.target
targetClass root對象 當前被調(diào)用的目標對象類 root.targetClass
args root對象 當前被調(diào)用的方法的參數(shù)列表 root.args[0]
caches root對象 當前方法調(diào)用使用的緩存列表(如@Cacheable(value={“cache1”, “cache2”})),則有兩個cache root.caches[0].name
argument name 執(zhí)行上下文 當前被調(diào)用的方法的參數(shù),如findById(Long id),我們可以通過#id拿到參數(shù) user.id
result 執(zhí)行上下文 方法執(zhí)行后的返回值(僅當方法執(zhí)行之后的判斷有效,如‘unless’,’cache evict’的beforeInvocation=false) result
@CacheEvict(value = "user", key = "#user.id", condition = "#root.target.canCache() and #root.caches[0].get(#user.id).get().username ne #user.username", beforeInvocation = true) public  void  conditionUpdate(User user)

總結(jié)

本章節(jié)主要是對redis結(jié)合Spring Cache的集成和簡單使用進行了說明,詳細的用法,可自行搜索相關(guān)資料下,這里就不闡述了。因為對于百分之八十之上的緩存要求基本能滿足了。使用緩存時,一定要注意緩存生命周期的控制,不然容易出現(xiàn)數(shù)據(jù)不一致的情況,謹記!

最后

目前互聯(lián)網(wǎng)上很多大佬都有SpringBoot系列教程,如有雷同,請多多包涵了。本文是作者在電腦前一字一句敲的,每一步都是實踐的。若文中有所錯誤之處,還望提出,謝謝。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容