1. 程式人生 > >springboot 2.X+redis+springcache 之 資料快取

springboot 2.X+redis+springcache 之 資料快取

Redis簡介

Redis支援資料的持久化,可以將記憶體中的資料儲存在磁碟中,重啟的時候可以再次載入進行使用。
Redis不僅僅支援簡單的key-value型別的資料,同時還提供list,set,zset,hash等資料結構的儲存。
Redis支援資料的備份,即master-slave模式的資料備份。 

第一步:
pom.xml檔案:

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

第二步:
application.properties檔案:

spring.redis.database=0
# redis server host
spring.redis.host=127.0.0.1
# redis password
spring.redis.password=
# connection port
spring.redis.port=6379
# time out
spring.redis.timeout=10000
# 配置快取過期時間
spring.redis.expiretime=1800

# pool settings:
# 連線池中的最大空閒連線
spring.redis.pool.max-idle=8 # 連線池中的最小空閒連線 spring.redis.pool.min-idle=0 # 連線池最大連線數(使用負值表示沒有限制) spring.redis.pool.max-active=8 # 連線池最大阻塞等待時間(使用負值表示沒有限制) spring.redis.pool.max-wait=-1 # set Key prefix. # spring.cache.redis.key-prefix=dev # Entry expiration in milliseconds. By default the entries never expire.
spring.cache.redis.time-to-live=1d # Whether to use the key prefix when writing to Redis. spring.cache.redis.use-key-prefix=true

第三步:

編寫RedisConfig檔案:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;

//@EnableCaching 【重點】不能加,不然@Cacheable無效
@Configuration
//載入該字首的配置資訊,提供set方法即可自動注入
@ConfigurationProperties(prefix = "spring.cache.redis")
public class RedisConfig extends CachingConfigurerSupport{

    private static final Logger log = LoggerFactory.getLogger(RedisConfig.class);

    private Duration timeToLive = Duration.ZERO;
    public void setTimeToLive(Duration timeToLive) {
        this.timeToLive = timeToLive;
    }

    /**
     * 自定義生成redis-key
     *
     * @return
     */
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                StringBuilder sb = new StringBuilder();
                sb.append(o.getClass().getName()).append(".");
                sb.append(method.getName()).append(".");
                for (Object obj : objects) {
                    sb.append(obj.toString());
                }
                log.info("------> 自定義生成redis-key完成,keyGenerator=" + sb.toString());
                return sb.toString();
            }
        };
    }

    /* @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        //【重點】【new RedisCacheManager()在 springboot2.x 裡無效】
        RedisCacheManager manager = new RedisCacheManager(redisTemplate);
        manager.setUsePrefix(true);
        RedisCachePrefix cachePrefix = new RedisPrefix("prefix");
        manager.setCachePrefix(cachePrefix);
        // 整體快取過期時間
        manager.setDefaultExpiration(3600L);
        // 設定快取過期時間。key和快取過期時間,單位秒
        Map<String, Long> expiresMap = new HashMap<>();
        expiresMap.put("user", 1000L);
        manager.setExpires(expiresMap);
        return manager;
    }*/

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(this.timeToLive)
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
                .disableCachingNullValues();

        RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();

        log.info("------> 自定義RedisCacheManager載入完成");
        return redisCacheManager;
    }

    @Bean(name = "redisTemplate")
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        redisTemplate.setKeySerializer(keySerializer());
        redisTemplate.setHashKeySerializer(keySerializer());
        redisTemplate.setValueSerializer(valueSerializer());
        redisTemplate.setHashValueSerializer(valueSerializer());

        log.info("------> 自定義RedisTemplate載入完成");
        return redisTemplate;
    }

    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    private RedisSerializer<Object> valueSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}

springboot2.X RedisCacheManager已經沒有了單引數的構造方法 ,參考文件

第四步:

註解需要快取的地方


@Service("UserService")
//對應ehcache.xml檔案裡面的<cache name="user">
@CacheConfig(cacheNames = "user")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    //無論怎樣,都將方法的返回結果放到快取當中。
    //ehcache3 會自動處理要快取的key-value裡面的key
    @CachePut
    public void save(User user) {
        System.out.println("新增功能,更新快取,直接寫庫, id=" + user);
        userRepository.save(user);
    }

    @Override
    @CachePut
    public void update(User user) {
        System.out.println("更新功能,更新快取,直接寫庫, id=" + user);
        userRepository.save(user);
    }

    @Override
    //將一條或者多條資料從快取中刪除。
    @CacheEvict
    public void remove(Long userId) throws Exception {
        System.out.println("刪除功能,刪除快取,直接寫庫, id=" + userId);
        userRepository.deleteById(userId);
    }

    @Override
    @CacheEvict
    public void batchRemove(Long[] userIds) throws Exception {
        for (Long userId : userIds) {
            userRepository.deleteById(userId);
        }
    }

    @Override
    public List<User> getUserList() {
        return userRepository.findAll();
    }

    @Override
    public User findByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    @Override
    //在方法執行前Spring先是否有快取資料,如果有直接返回。如果沒有資料,呼叫方法並將方法返回值存放在快取當中。
    @Cacheable
    public User findById(long id) {
        //可以在這裡打一個斷點,發現第二次查詢不會被斷點卡到為配置成功
        System.out.println("查詢功能,快取找不到,直接讀庫, id=" + id);
        return userRepository.findById(id);
    }
}

第五步:

配置啟動檔案:

@SpringBootApplication
@EnableCaching//開啟快取
@PropertySource("application.properties")
public class XXXApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(XXXApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(XXXApplication.class, args);
    }
}

可能出現的問題:

1、 型別轉換出現問題:看看是不是工程裡面加入了熱部署,註釋掉熱部署即可

java.lang.ClassCastException:
com.XXX.system.UserDO cannot be cast to com.XXX.system.UserDO

2、這裡涉及redis的常見操作:

檢視key的型別
type key
檢視所有key
keys *
刪除一個key
DEL key
清空一個DB
FLUSHDB
清空所有
FLUSHALL
切換到其他DB
select 1

可能的問題:

WRONGTYPE Operation against a key holding the wrong kind of value

原因是:型別不一致,使用redis命令不當造成的。

例如:

127.0.0.1:6379> keys *
1) "user~keys"
2) "user_1"
127.0.0.1:6379> get user~keys
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> type user~keys
zset
127.0.0.1:6379> zrange user~keys 0 10
1) "user_1"

參考文件: