SpringBoot系列記錄(十六)——SpringBoot整合Redis使用FastJson序列化

與Redis相關知識可查看《Redis系列記錄》

一、相關配置

1.1 pom.xml

<!-- spring-boot-starter-data-redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>

        <!-- 連接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>

1.2 application.properties

spring.cache.type=redis
# Redis數據庫索引(默認爲0)
spring.redis.database=0  
# Redis服務器地址
spring.redis.host=localhost
# Redis服務器連接端口
spring.redis.port=6379  
# Redis服務器連接密碼(默認爲空)
#spring.redis.password=123456
# 連接池最大連接數(使用負值表示沒有限制)
spring.redis.lettuce.max-active=200  
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.lettuce.max-wait=-1  
# 連接池中的最大空閒連接
spring.redis.lettuce.max-idle=10 
# 連接池中的最小空閒連接
spring.redis.lettuce.min-idle=0  
# 連接超時時間(毫秒)
spring.redis.timeout=1000ms

logging.level.com.example.redisdemo=debug

1.3 RedisConfig

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public RedisSerializer fastJson2JsonRedisSerialize(){
        return new FastJson2JsonRedisSerialize<Object>(Object.class);
    }

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory,RedisSerializer fastJson2JsonRedisSerialize){
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //設置Key的序列化採用StringRedisSerializer
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //設置值的序列化採用FastJsonRedisSerializer
        redisTemplate.setValueSerializer(fastJson2JsonRedisSerialize);
        redisTemplate.setHashValueSerializer(fastJson2JsonRedisSerialize);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 生成一個默認配置,通過config對象即可對緩存進行自定義配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // 設置緩存的默認過期時間,也是使用Duration設置
        config = config.entryTtl(Duration.ofMinutes(5))
                // 設置 key爲string序列化
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 設置value爲fastJson序列化
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJson2JsonRedisSerialize()))
                // 不緩存空值
                .disableCachingNullValues();
        // 使用自定義的緩存配置初始化一個cacheManager
        return RedisCacheManager
                .builder(redisConnectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
    }
}

1.4  FastJson2JsonRedisSerialize

public class FastJson2JsonRedisSerialize<T> implements RedisSerializer<T> {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        //如果遇到反序列化autoType is not support錯誤,請添加並修改一下包名到bean文件路徑
        //ParserConfig.getGlobalInstance().addAccept("com.example.redisdemo.domain");
    }

    public FastJson2JsonRedisSerialize(Class clazz){
        super();
        this.clazz = clazz;
    }


    /**
     * 序列化
     * @param t
     * @return
     * @throws SerializationException
     */
    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (null == t){
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    /**
     * 反序列化
     * @param bytes
     * @return
     * @throws SerializationException
     */
    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (null == bytes || bytes.length <= 0){
            return null;
        }
         String str = new String(bytes,DEFAULT_CHARSET);
        return (T) JSON.parseObject(str,clazz);
    }
}

二、測試

2.1 User

public class User implements Serializable {

    private static final long serialVersionUID = 8391377903506428591L;
    private Integer id;

    private String username;

    private String password;

    public User(){
        super();
    }

    public User(Integer id, String username, String password){
        this.id = id;
        this.username = username;
        this.password = password;
    }
   getter... setter... toString()...
}

2.2 UserService   UserServiceImpl

public interface UserService {

    public User addUser(User user);

    public User getUser(Integer id);

    public void delUser(Integer id);
}

 這裏不用Mapper了,使用Map來模擬一下數據庫操作,

@CacheConfig(cacheNames = "user")
@Service("userService")
public class UserServiceImpl implements UserService {

    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
    private static final Map<String,Object> usersMap = new HashMap<>();

    @Override
    @CachePut(key = "#user.id")
    public User addUser(User user) {
        usersMap.put(user.getId().toString(),user);
        logger.info("存入數據庫【User】= {}",user);
        return user;
    }

    @Override
    @Cacheable(key = "#id",unless = "#result == null ")
    public User getUser(Integer id){
        User user = (User) usersMap.get(id.toString());
        logger.info("從數據庫獲取【User】= {}",user);
        return user;
    }

    @Override
    @CacheEvict(key = "#id")
    public void delUser(Integer id) {
        usersMap.remove(id.toString());
        logger.info("從數據庫刪除【User】");
    }
}

2.3 Test

    @Test
    void testOne() {
         userService.addUser(new User(1,"aa","123"));
         User user = userService.getUser(1);
         logger.debug("【User】={}",user);
    }

2.4 結果

 這裏可以看到只打印了添加日誌和最後的查詢結果,並沒有打印查詢日誌,所以證明緩存生效。

三、相關知識點與坑

3.1 報com.alibaba.fastjson.JSONException: autoType is not support

解決方法:在FastJson2JsonRedisSerialize 類中添加   這兩句代碼都可以解決反序列化問題

  static {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        //如果遇到反序列化autoType is not support錯誤,請添加並修改一下包名到bean文件路徑
        //ParserConfig.getGlobalInstance().addAccept("com.example.redisdemo.domain");
    }

3.2 Cache 'redisCache' does not allow 'null' values

解決方法:添加unless = "#result == null"

 @Cacheable(key = "#id",unless = "#result == null ")

3.3 @CacheConfig

此註解是爲了抽取緩存註解中公共的配置

3.4 @Cacheable

可用於類或方法上;
在目標方法執行前,會根據key先去緩存中查詢看是否有數據,
有就直接返回緩存中的key對應的value值。不再執行目標方法;
無則執行目標方法,並將方法的返回值作爲value,並以鍵值對的形式存入緩存

3.5 @CachePut

應用到寫數據的方法上,如新增/修改方法,
在執行完目標方法後,並將方法的返回值作爲value,並以鍵值對的形式存入緩存中

3.6 @CacheEvict

應用到刪除數據的方法上,
在執行完目標方法後,清除緩存中對應key的數據(如果緩存中有對應key的數據緩存的話)

3.7 @EnableCaching

 開關性註解,在項目啓動類或某個配置類上使用此註解後,則表示允許使用註解的方式進行緩存操作

 

華麗分隔符

================================================================================================

上文中使用註解來完成緩存操作,下面再來一種方式

在測試類中添加: 

@Resource(name = "redisTemplate")
    private ValueOperations<String,Object> vo;

  @Test
    void testFour(){
        vo.set("java",new User(1,"bb","111"));
        User user = (User) vo.get("java");
        System.out.println(user);
    }

 執行: 也是好用的

 那麼爲什麼redisTemplate可以直接注入給五種數據類型的XXOperations使用呢?原來有屬性編輯器

 

此篇內容後期還有擴充,未完待續...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章