SpringBoot(4) ------>整合CacheManager與Redis


1、向pom文件中添加依賴

     <!--springboot中的redis依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

2、application.yml添加配置

spring:
  redis:
    host: localhost     # Redis服務器地址
    database: 0         # Redis數據庫索引(默認為0)
    port: 6379          # Redis服務器連接端口
    password: ld123456  # Redis服務器連接密碼(默認為空)

3、RedisConfig配置類

package com.donleo.mybatis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
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.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.Arrays;

/**
 * @author liangd
 * date 2020-12-04 16:15
 * code Redis配置類
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    /**
     * 配置redisTemplate 代替默認配置
     * @param redisConnectionFactory  RedisConnectionFactory
     * @return  RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);

        template.setValueSerializer(serializer);
        //使用StringRedisSerializer來序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

    /**
     *
     * 配置 cacheManager 代替默認的cacheManager (緩存管理器)
     * @param factory RedisConnectionFactory
     * @return  CacheManager
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(objectMapper);
        // 配置序列化
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));

        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }

    /**
     * 配置KeyGenerator
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        //lambda 表達式
        return (target, method, params) -> method.getName() + Arrays.asList(params);
    }
    /*
        // 1. 不需要參數,返回值為 5
            () -> 5

        // 2. 接收一個參數(數字類型),返回其2倍的值
            x -> 2 * x

        // 3. 接受2個參數(數字),並返回他們的差值
            (x, y) -> x – y

        // 4. 接收2個int型整數,返回他們的和
            (int x, int y) -> x + y

        // 5. 接受一個 string 對象,並在控制台打印,不返回任何值(看起來像是返回void)
            (String s) -> System.out.print(s)
     */
     /*   @Bean
       @Override
       public KeyGenerator keyGenerator() {
           return (target,  method,  params)->{
               return method.getName()+ Arrays.asList(params);
           };
       }*/
}

4、自定義Redis工具類

  1)接口層

package com.donleo.mybatis.service;

/**
 * @author liangd
 * date 2020-12-04 16:19
 * code 自定義封裝redis接口定義
 */
public interface IRedisService {
    /**
     * 指定緩存失效時間
     * @param key 鍵
     * @param time 時間(秒)
     * @return
     */
    boolean expire(String key, long time);

    /**
     * 根據key 獲取過期時間
     * @param key 鍵 不能為null
     * @return 時間(秒) 返回0代表為永久有效
     */
    long getExpire(String key);

    /**
     * 判斷key是否存在
     * @param key 鍵
     * @return true 存在 false不存在
     */

    boolean hasKey(String key);

    /**
     * 刪除緩存
     * @param key 可以傳一個值 或多個
     */

    void del(String... key);

    /**
     * 普通緩存獲取
     * @param key 鍵
     * @return*/
    Object get(String key);

    /**
     * 普通緩存放入
     * @param key 鍵
     * @param value 值
     * @return true成功 false失敗
     */
    boolean set(String key, Object value);

    /**
     * 普通緩存放入並設置時間
     * @param key 鍵
     * @param value 值
     * @param time 時間(秒) time要大於0 如果time小於等於0 將設置無限期
     * @return true成功 false 失敗
     */
    boolean set(String key, Object value, long time);
}

  2)實現層

package com.donleo.mybatis.service.impl;

import com.donleo.mybatis.service.IRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.concurrent.TimeUnit;

/**
 * @author liangd
 * date 2020-12-04 16:21
 * code 自定義封裝Redis接口定義
 */
@Service
public class RedisServiceImpl implements IRedisService {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 指定緩存失效時間
     *
     * @param key  鍵
     * @param time 時間(秒)
     * @return
     */
    @Override
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根據key 獲取過期時間
     *
     * @param key 鍵 不能為null
     * @return 時間(秒) 返回0代表為永久有效
     */
    @Override
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判斷key是否存在
     *
     * @param key 鍵
     * @return true 存在 false不存在
     */
    @Override
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 刪除緩存
     *
     * @param key 可以傳一個值 或多個
     */
    @Override
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //============================String=============================

    /**
     * 普通緩存獲取
     *
     * @param key 鍵
     * @return*/
    @Override
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通緩存放入
     *
     * @param key   鍵
     * @param value 值
     * @return true成功 false失敗
     */
    @Override
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通緩存放入並設置時間
     *
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒) time要大於0 如果time小於等於0 將設置無限期
     * @return true成功 false 失敗
     */
    @Override
    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);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

5、測試-->通過Redis工具類緩存

package com.donleo.mybatis.service.impl;

import com.donleo.mybatis.common.CommonResult;
import com.donleo.mybatis.dao.IDeptMapper;
import com.donleo.mybatis.model.Dept;
import com.donleo.mybatis.service.IDeptService;
import com.donleo.mybatis.service.IRedisService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author liangd
 * date 2020-12-03 11:09
 * code 部門邏輯層(使用自定義RedisTemplate設置緩存)
 */
@Service
public class DeptServiceImpl implements IDeptService {

    @Resource
    private IDeptMapper deptMapper;

    @Resource
    private IRedisService redisService;

    @Override
    public CommonResult findAll() {
        List<Dept> list;
        boolean b = redisService.hasKey("deptList:");
        if (b) {
            Object object = redisService.get("deptList:");
            list = (List<Dept>) object;
        } else {
            list = deptMapper.selectAll();
            redisService.set("deptList:",list);
        }
        return CommonResult.success(list);
    }

    @Override
    public Integer add(Dept dept) {
        dept.setId(null);
        deptMapper.insert(dept);
        return dept.getId();
    }

    @Override
    public Integer delete(Integer id) {
        redisService.del("dept:"+id);
        redisService.del("deptList:");
        return deptMapper.deleteByPrimaryKey(id);
    }

    @Override
    public Integer update(Dept dept) {
        return deptMapper.updateByPrimaryKeySelective(dept);
    }

    @Override
    public Dept findById(Integer id) {
        //判斷redis中是否存在當前key,加一個冒號生成文件夾
        boolean b = redisService.hasKey("dept:" + id);
        Dept dept;
        //如果存在,從redis中查詢,否則從數據庫中查詢
        if (b) {
            dept = (Dept) redisService.get("dept:" + id);
        } else {
            dept = deptMapper.selectByPrimaryKey(id);
            //放入resis中
            redisService.set("dept:"+id, dept);
        }
        return dept;
    }
}

6、測試-->通過CacheManager緩存(1)

package com.donleo.mybatis.service.impl;

import com.donleo.mybatis.common.CommonResult;
import com.donleo.mybatis.dao.IRoleMapper;
import com.donleo.mybatis.model.Role;
import com.donleo.mybatis.service.IRoleService;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author liangd
 * date 2020-12-04 17:42
 * code 角色邏輯實現層(使用注解CacheManager設置緩存)
 * 使用@CacheConfig配置緩存名
 */
@Service
@CacheConfig(cacheNames = "role")
public class RoleServiceImpl implements IRoleService {

    @Resource
    private IRoleMapper roleMapper;

    /**
     * 使用@Caching注解可以讓我們在一個方法或者類上同時指定多個Spring Cache相關的注解。
     * 其擁有三個屬性:cacheable、put和evict,分別用於指定@Cacheable、@CachePut和@CacheEvict。
     *
     * @param role
     * @return
     */
    @Override
    @Caching(
            put = @CachePut(key = "#role.id"),
            evict = @CacheEvict(key = "'findAllRole[]'")
    )
    public CommonResult add(Role role) {
        role.setId(null);
        try {
            roleMapper.insert(role);
        } catch (Exception e) {
            return CommonResult.failed();
        }
        return CommonResult.success(role.getId());
    }

    /**
     * 使用@CacheEvict移除key
     * @param id
     * @return
     */
    @Override
    @CacheEvict(key = "#id")
    public Integer delete(Integer id) {
        return roleMapper.deleteByPrimaryKey(id);
    }

    @Override
    public Integer update(Role role) {
        return roleMapper.updateByPrimaryKeySelective(role);
    }

    /**
     * 使用@Cacheable標注的方法,Spring在每次執行前都會檢查Cache中是否存在相同key的緩存元素,
     * 如果存在就不再執行該方法,而是直接從緩存中獲取結果進行返回,否則才會執行並將返回結果存入指定的緩存中。
     *
     * @param id
     * @return
     */
    @Override
    @Cacheable(key = "#id")
    public Role findById(Integer id) {
        return roleMapper.selectByPrimaryKey(id);
    }

    /**
     * 使用@CachePut 也可以聲明一個方法支持緩存功能。
     * 與@Cacheable不同的是使用@CachePut標注的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,
     * 而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。
     *
     * @return
     * keyGenerator將方法名作為key,存進緩存,例如findAllRole[],刪除key時需要添加單引號
     */
    @Override
    @CachePut(keyGenerator = "keyGenerator")
    public CommonResult findAllRole() {
        List<Role> list = roleMapper.selectAll();
        return CommonResult.success(list);
    }
}

7、測試-->通過CacheManager緩存(2)

package com.donleo.cache.service.impl;

import com.donleo.cache.common.CommonResult;
import com.donleo.cache.mapper.IRoleMapper;
import com.donleo.cache.model.Role;
import com.donleo.cache.service.IRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author liangd
 * date 2020-12-10 09:49
 * code
 * .@CacheConfig抽取緩存的公共配置,在這里配置了cacheNames就不需要再每個方法上面指定value屬性了
 */
@Service
@CacheConfig(cacheNames = "role")
public class RoleServiceImpl implements IRoleService {

    @Autowired
    private IRoleMapper roleMapper;

    /**
     * .@Cacheable標注的方法執行之前先來檢查緩存中有沒有這個數據,默認按照參數的值作為key去查詢緩存,
     * 如果沒有就運行方法並將結果放入緩存;以后再來調用就可以直接使用緩存中的數據;
     *
     * .@Cacheable 將方法的運行結果進行緩存,以后再要相同的數據,直接從緩存中獲取,不用調用方法;
     * <p>
     * CacheManager管理多個Cache組件的,對緩存的真正CRUD操作在Cache組件中,每一-個緩存組件有自己唯- --一個名字;
     * <p>
     * <p>
     * 原理:
     * 1、自動配置類 CacheAutoConfiguration
     * 2、緩存的配置類
     * org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
     * org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默認】
     * org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
     * <p>
     * 3、哪個配置類默認生效:SimpleCacheConfiguration;
     * <p>
     * 4、給容器中注冊了一個CacheManager:ConcurrentMapCacheManager
     * 5、可以獲取和創建ConcurrentMapCache類型的緩存組件;他的作用將數據保存在ConcurrentMap中;
     * <p>
     * 運行流程:
     * <p>
     * .@Cacheable:
     * 1、方法運行之前,先去查詢Cache(緩存組件),按照cacheNames指定的名字獲取;
     *  (CacheManager先獲取相應的緩存),第一次獲取緩存如果沒有Cache組件會自動創建。
     * 2、去Cache中查找緩存的內容,使用一個key,默認就是方法的參數;
     *      key是按照某種策略生成的;默認是使用keyGenerator生成的,默認使用SimpleKeyGenerator生成key;
     *      SimpleKeyGenerator生成key的默認策略;
     *          如果沒有參數;key=new SimpleKey();
     *          如果有一個參數:key=參數的值
     *          如果有多個參數:key=new SimpleKey(params);
     * 3、沒有查到緩存就調用目標方法;
     * 4、將目標方法返回的結果,放進緩存中
     * <p>
     * <p>
     * 核心:
     * 1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】組件
     * 2)、key使用keyGenerator生成的,默認是SimpleKeyGenerator
     * <p>
     * 幾個屬性:
     * a)cacheNames/value:指定緩存組件的名字
     *      cacheNames = {"role"}可以使用多個參數,是數組的形式,可以指定多個緩存
     * b)key:緩存數據使用的key,可以用他來指定。默認是使用方法參數的值
     *      編寫SpEl:   #id  #a0,#po,#argrs[0]  "0"代表參數的索引
     *      #result  方法執行后的返回值
     *      #root.methodName   方法名
     *      key = "#root.methodName+'['+#id+']'"
     * c)keyGenerator: key的生成器、可以自己指定key的生成器的組件Id
     *      key/keyGenerator 二選一
     *      keyGenerator = "myKeyGenerator"
     * d)cacheManager:指定緩存管理器或者cacheResolver:獲取解析器
     *      cacheManager/cacheResolver 二選一
     * e)condition:指定符合緩存的條件
     *      condition = "#id>0 and #root.methodName eq 'aaa'" 可以多條件判斷
     * f)unless: 否定緩存,當unless的條件為true,方法結果不會被緩存,可以獲取結果進行判斷
     *      unless = "#result==null",結果為null,就不緩存
     * g)sync:是否使用異步模式
     *      默認false   同步
     *      為true時,unless不支持
     */

    @Override
    @Cacheable(value = {"role"}, key = "#id",
            condition = "#id>0", unless = "#result==null")
    public Role findById(Integer id) {
        return roleMapper.selectById(id);
    }

    /**
     * .@CachePut既調用方法、又更新數據,達到同步更新緩存
     * <p>
     * 運行時機:
     * 1、先調用目標方法
     * 2、將目標方法的結果緩存起來
     *
     * 條件:存取Id的key要保持一致
     *     key = "#role.id"     傳入員工的Id
     *     key = "#result.id"   使用返回員工的Id
     * 注意: @Cacheable不能使用#result
     *      因為 @Cacheable在目標方法執行之前需要得到這個key,所以不能用#result
     */
    @Override
    @CachePut(value = "role", key = "#result.id")
    public Role update(Role role) {
        roleMapper.updateById(role);
        return role;
    }

    /**
     * .@CacheEvict 緩存清除
     *
     *  key:指定要清除的數據
     *  allEntries:指定清除這個緩存庫的所有數據,默認為false
     *  beforeInvocation:在執行方法之前清除,默認為false,在方法之后執行
     *
     */
    @Override
    @CacheEvict(/*value = "role",*/key = "#id")
    public Integer delete(Integer id) {
        return roleMapper.deleteById(id);
    }

    /**
     * .@Caching 定義復雜緩存規則
     */
    @Override
    @Caching(
            cacheable = {
                    @Cacheable(key = "#role.roleName")
            },
            put = {
                    @CachePut(key = "#role.id"),
                    @CachePut(key = "#role.roleCode")
            }
    )
    public CommonResult add(Role role) {
        role.setId(null);
        try {
            roleMapper.insert(role);
        } catch (Exception e) {
            return CommonResult.failed();
        }
        return CommonResult.success(role.getId());
    }

    @Override
    public CommonResult findAllRole() {
        List<Role> roleList = roleMapper.selectList(null);
        return CommonResult.success(roleList);
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM