Spring configuration cache (concurrentHashMap, guava cache, redis implementation) with source code

  In the application, the data generally exist in the database (disk media), for certain data to be accessed frequently, if every time access to the database, not only to the network io, also affected by the database query; and the current will usually frequently used and infrequently changing data into the cache, the query efficiency of data than a database, because the cache is generally stored KV, and the data from the cache is the presence of "memory", the memory access data from quite fast of.

  For frequently accessed, need to cache data, we generally do so:

  1, when receiving the inquiry request, go query cache, the cache if the query to the data, then the data directly, as found in the response data;

  2, if the cache is no data to be queried to find, then, such as database queries from other places out, if found in the data from the database, the data will be placed in the cache, then the data is returned, the next time directly from the cache query;

  Here is not to further explore the "Cache penetration" problem, are interested can learn about yourself.

  In this paper, respectively ConcurrentHashMap, Guava Cache, Redis carried out according illustrates how to use the Spring Framework, complete code has been uploaded to GitHub: https://github.com/searchingbeyond/ssm 

 

First, the use ConcurrentHashMap

1.1 Features Description

  ConcurrentHashMap JDK is carrying, so no extra jar package;

  Use ConcurrentHashMap, is the direct use of the data stored in memory, and there is no concept of data expired , and there is no limit data capacity , so they do not take the initiative to clean up the data, the data will always not be reduced.

  In addition, ConcurrentHashMap in a multithreaded situation is safe, do not use HashMap memory cache data because HashMap prone to problems during multithreaded operations.

 

1.2, create a user class

  The following is a user class code:

package cn.ganlixin.ssm.model.entity;

import lombok.Data;

@Data
public class UserDO {
    private Integer id;
    private String name;
    private Integer age;
    private Integer gender;
    private String addr;
    private Integer status;
}

  

1.3, create a spring cache implementation class

  UserCache create a class (the class name at random), to achieve org.springframework.cache.Cache interface and then override the need to implement the interface methods, mainly for getName, get, put, evict the four methods of overwriting.

  Note that when I data in the cache user, specify caching rules: key using a user's id, value is json serialized character user object.

package cn.ganlixin.ssm.cache.origin;

import cn.ganlixin.ssm.constant.CacheNameConstants;
import cn.ganlixin.ssm.model.entity.UserDO;
Import cn.ganlixin.ssm.util.common.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class UserCache implements Cache {

    // use to store data as ConcurrentHashMap
    private Map<String, String> storage = new ConcurrentHashMap<>();

    // getName Gets the name of the cache, data access time is used to distinguish between actions for which cache
    @Override
    public String getName() {
        return CacheNameConstants.USER_ORIGIN_CACHE; // I use a class to hold constant the name cache
    }

    // put method is to perform the data cache
    @Override
    public void put(Object key, Object value) {
        if (Objects.isNull(value)) {
            return;
        }

        // Note that when I was in the cache, the cache is the value after the object serialization (of course, you can modify the storage directly deposit UserDO class also line)
        storage.put(key.toString(), JsonUtils.encode(value, true));
    }

    // get method, that is, the query cache operations, attention returns the value of a package
    @Override
    public ValueWrapper get(Object key) {
        String k = key.toString();
        String value = storage.get(k);
        
        // Note that the data returned to the data is received and stored when consistent, to deserialize the data back.
        return StringUtils.isEmpty(value) ? null : new SimpleValueWrapper(JsonUtils.decode(value, UserDO.class));
    }

    // evict method is used to remove a cache entry
    @Override
    public void evict(Object key) {
        storage.remove(key.toString());
    }

    / * ---------------------------- The following methods ignore no matter -------------- --- * /

    @Override
    public Object getNativeCache() { return null; }

    @Override
    public void clear() { }

    @Override
    public <T> T get(Object key, Class<T> type) { return null; }

    @Override
    public <T> T get(Object key, Callable<T> valueLoader) { return null; }
}

  

1.4, create a service

  UserMapper posted here is not to write code, and direct interfaces to see to understand:

package cn.ganlixin.ssm.service;

import cn.ganlixin.ssm.model.entity.UserDO;

public interface UserService {

    UserDO findUserById(Integer id);

    Boolean removeUser(Integer id);

    Boolean addUser(UserDO user);

    Boolean modifyUser(UserDO user);
}

  Achieve UserService, code is as follows:

package cn.ganlixin.ssm.service.impl;

import cn.ganlixin.ssm.constant.CacheNameConstants;
import cn.ganlixin.ssm.mapper.UserMapper;
import cn.ganlixin.ssm.model.entity.UserDO;
import cn.ganlixin.ssm.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

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

@Service
@ Slf4j
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    @Cacheable(value = CacheNameConstants.USER_ORIGIN_CACHE, key = "#id")
    public UserDO findUserById(Integer id) {
        try {
            log.info ( "DB id as a query from a user of {}", id);
            return userMapper.selectById(id);
        } catch (Exception e) {
            log.error ( "failed to query the user data, id: {}, e: {}", id, e);
        }

        return null;
    }

    @Override
    @CacheEvict (
            value = CacheNameConstants.USER_ORIGIN_CACHE,
            key = "#id",
            condition = "#result != false"
    )
    public Boolean removeUser(Integer id) {
        if (Objects.isNull(id) || id <= 0) {
            return false;
        }

        try {
            int cnt = userMapper.deleteUserById(id);
            return cnt > 0;
        } catch (Exception e) {
            log.error ( "delete user data failed, id: {}, e: {}", id, e);
        }

        return false;
    }

    @Override
    public Boolean addUser(UserDO user) {
        if (Objects.isNull(user)) {
            log.error ( "add user exception parameter must not be null");
            return false;
        }

        try {
            return userMapper.insertUserSelectiveById(user) > 0;
        } catch (Exception e) {
            log.error ( "add user fails, data: {}, e: {}", user, e);
        }

        return false;
    }

    @Override
    @CacheEvict (
            value = CacheNameConstants.USER_ORIGIN_CACHE,
            key = "#user.id",
            condition = "#result != false"
    )
    public Boolean modifyUser(UserDO user) {
        if (Objects.isNull(user) || Objects.isNull(user.getId()) || user.getId() <= 0) {
            log.error ( "update user exception, invalid parameters, data: {}", user);
            return false;
        }

        try {
            return userMapper.updateUserSelectiveById(user) > 0;
        } catch (Exception e) {
            log.error ( "add user fails, data: {}, e: {}", user, e);
        }

        return false;
    }
}

 

1.5、@Cachable、@CachePut、@CacheEvict

  There are methodological statement above @ Cachable, @ CachePut, @ CacheEvict notes, usage is as follows:

  Methods @Cachable annotation, first check the cache there, if it has been cached query data from the cache and returned to the caller; if the investigation found no cache data on the implementation of the annotated method (usually from the DB query), then DB query results from the cache, and returns the results to the caller;

  Methods @CachePut annotation, query cache is not whether there is data to be queried, but each time the implementation of the annotated method, then the result will be the return value of the cache first, and then returned to the caller;

  @CacheEvict annotated method, each will first perform the annotated method, then the cache to clear the cache entries;

  The three notes has several parameters, namely, value, key, condition, the meaning of these parameters are as follows:

  value, which is used to specify the data into the cache, such as the above data is cached in UserCache;

  key, represents a key into the cache, i.e. key UserCache the PUT method;

  condition, data representing cache condition, when the condition is not cached data is true;

  The last value cache entry, this means that the value of KV V, in fact, only @Cachable and @CachePut only need to pay attention to value (ie value put method) cache entry, the return value of the cache entry is annotated method .

 

1.6, create a controller for testing

  code show as below:

package cn.ganlixin.ssm.controller;

import cn.ganlixin.ssm.enums.ResultStatus;
import cn.ganlixin.ssm.model.Result;
import cn.ganlixin.ssm.model.entity.UserDO;
import cn.ganlixin.ssm.service.UserService;
import org.springframework.web.bind.annotation.*;

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

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    @GetMapping(value = "/getUserById")
    public Result<UserDO> getUserById(Integer id) {
        UserDO data = userService.findUserById(id);

        if (Objects.isNull(data)) {
            return new Result<>(ResultStatus.DATA_EMPTY.getCode(), ResultStatus.DATA_EMPTY.getMsg(), null);
        }

        return new Result<>(ResultStatus.OK.getCode(), ResultStatus.OK.getMsg(), data);
    }

    @PostMapping(value = "removeUser")
    public Result<Boolean> removeUser(Integer id) {
        Boolean res = userService.removeUser(id);
        return res ? new Result<>(ResultStatus.OK.getCode(), ResultStatus.OK.getMsg(), true)
                : new Result<>(ResultStatus.FAILED.getCode(), ResultStatus.FAILED.getMsg(), false);
    }

    @PostMapping(value = "addUser")
    public Result<Boolean> addUser(@RequestBody UserDO user) {
        Boolean res = userService.addUser(user);

        return res ? new Result<>(ResultStatus.OK.getCode(), ResultStatus.OK.getMsg(), true)
                : new Result<>(ResultStatus.FAILED.getCode(), ResultStatus.FAILED.getMsg(), false);
    }

    @PostMapping(value = "modifyUser")
    public Result<Boolean> modifyUser(@RequestBody UserDO user) {
        Boolean res = userService.modifyUser(user);

        return res ? new Result<>(ResultStatus.OK.getCode(), ResultStatus.OK.getMsg(), true)
                : new Result<>(ResultStatus.FAILED.getCode(), ResultStatus.FAILED.getMsg(), false);
    }

}

  

 

 

Second, using the Guava Cache realization

  Use Guava Cache realize, in fact, just replace ConcurrentHashMap, other logic is the same.

2.1 Features Description

  Guava is a google open source integrated package, use especially wide, Cache also in place for the use of Guava Cache, if not used, can refer to: the Guava Cache use

  Use Guava Cache, you can set the expiration time and the cache of cache capacity .

 

2.2, to achieve spring cache Interface

  Still use the previous example, to re-create a Cache implementation class, here on the "Book" cache, so the cache name BookCache.

package cn.ganlixin.ssm.cache.guava;

import cn.ganlixin.ssm.constant.CacheNameConstants;
import cn.ganlixin.ssm.model.entity.BookDO;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Objects;
import java.util.concurrent.Callable;
java.util.concurrent.TimeUnit import;

/**
 * Books data cache
 */
@Component
public class BookCache implements org.springframework.cache.Cache {

    // The following is Guava Cache to cache
    private Cache<String, BookDO> storage;

    @PostConstruct
    private void init() {
        storage = CacheBuilder.newBuilder()
                // set the cache capacity of 100
                .maximumSize(100)
                // set the initial capacity to 16
                .initialCapacity(16)
                // set the expiration time is 10 minutes after the write cache expires
                .refreshAfterWrite(10, TimeUnit.MINUTES)
                .build();
    }

    @Override
    public String getName() {
        return CacheNameConstants.BOOK_GUAVA_CACHE;
    }

    @Override
    public ValueWrapper get(Object key) {
        if (Objects.isNull(key)) {
            return null;
        }

        BookDO data = storage.getIfPresent(key.toString());
        return Objects.isNull(data) ? null : new SimpleValueWrapper(data);
    }

    @Override
    public void evict(Object key) {
        if (Objects.isNull(key)) {
            return;
        }

        storage.invalidate(key.toString());
    }

    @Override
    public void put(Object key, Object value) {
        if (Objects.isNull(key) || Objects.isNull(value)) {
            return;
        }

        storage.put(key.toString(), (BookDO) value);
    }

    / * ----------------------- ignore the following method ----------------- * /

    @Override
    public <T> T get(Object key, Class<T> type) { return null; }

    @Override
    public Object getNativeCache() { return null; }

    @Override
    public <T> T get(Object key, Callable<T> valueLoader) { return null; }

    @Override
    public void clear() { }
}

  

 

Third, the use Redis achieve

3.1 Features Description

  Because ConcurrentHashMap and Guava Cache are cached in the data directly on the server host, it is clear that the number and host cache data is directly related to the amount of memory, is generally not used to cache a particularly large amount of data;

  The relatively large amount of data, we generally cache with Redis.

  Use Redis integration Spring Cache, and in fact ConcurrentHashMap and Guava Cache same, but in the realization of the Cache interface class, use the Redis storage interface.

 

3.2, create a class Redis cluster operations

  Redis own proposals to build a test cluster, you can refer to:

  redis configuration is as follows (the application.properties)

Node cluster information #redis
redis.cluster.nodes=192.168.1.3:6379,192.168.1.4:6379,192.168.1.5:6379
# Redis connection pool configuration
redis.cluster.pool.max-active=8
redis.cluster.pool.max-idle=5
redis.cluster.pool.min-idle=3

  

  code show as below:

package cn.ganlixin.ssm.config;

import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;

java.util.Set amount;
import java.util.stream.Collectors;

@Configuration
public class RedisClusterConfig {

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

    @Value("${redis.cluster.nodes}")
    private Set<String> redisNodes;

    @Value("${redis.cluster.pool.max-active}")
    private int maxTotal;

    @Value("${redis.cluster.pool.max-idle}")
    private int maxIdle;

    @Value("${redis.cluster.pool.min-idle}")
    private int minIdle;

    // Initialize the configuration redis
    @Bean
    public JedisCluster redisCluster() {

        if (CollectionUtils.isEmpty(redisNodes)) {
            throw new RuntimeException();
        }

        // set redis cluster node information
        Set<HostAndPort> nodes = redisNodes.stream().map(node -> {
            String[] nodeInfo = node.split(":");
            if (nodeInfo.length == 2) {
                return new HostAndPort(nodeInfo[0], Integer.parseInt(nodeInfo[1]));
            } else {
                return new HostAndPort(nodeInfo[0], 6379);
            }
        }).collect(Collectors.toSet());

        // Configure connection pool
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(maxTotal);
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMinIdle(minIdle);

        // Create jediscluster, incoming node list and connection pool configuration
        JedisCluster cluster = new JedisCluster(nodes, jedisPoolConfig);
        log.info("finish jedis cluster initailization");

        return cluster;
    }
}

  

 3.3, create a spring cache implementation class

  Only when it comes to data manipulation, you can use the above jedisCluster, there is data redis here, I set to Music, so called music cache:

package cn.ganlixin.ssm.cache.redis;

import cn.ganlixin.ssm.constant.CacheNameConstants;
import cn.ganlixin.ssm.model.entity.MusicDO;
Import cn.ganlixin.ssm.util.common.JsonUtils;
import com.google.common.base.Joiner;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisCluster;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.Callable;

@Component
public class MusicCache implements Cache {

    // use custom redisCluster
    @Resource
    private JedisCluster redisCluster;

    /**
     * Build redis cache key
     *
     * @Param type type
     * @Param params parameter (variable length)
     * @Return build key
     */
    private String buildKey(String type, Object... params) {
        // set their own way to build
        return Joiner.on("_").join(type, params);
    }

    @Override
    public String getName() {
        return CacheNameConstants.MUSIC_REDIS_CACHE;
    }

    @Override
    public void put(Object key, Object value) {
        if (Objects.isNull(value)) {
            return;
        }

        // define their own data types and formats
        redisCluster.set(buildKey("music", key), JsonUtils.encode(value, true));
    }

    @Override
    public ValueWrapper get(Object key) {
        if (Objects.isNull(key)) {
            return null;
        }

        // define their own data types and formats
        String music = redisCluster.get(buildKey("music", key));
        return StringUtils.isEmpty(music) ? null : new SimpleValueWrapper(JsonUtils.decode(music, MusicDO.class));
    }

    @Override
    public void evict(Object key) {
        if (Objects.isNull(key)) {
            return;
        }

        redisCluster.del(buildKey("music", key));
    }

    @Override
    public <T> T get(Object key, Class<T> type) { return null; }

    @Override
    public <T> T get(Object key, Callable<T> valueLoader) { return null; }

    @Override
    public void clear() { }

    @Override
    public Object getNativeCache() { return null; }
}

  

to sum up

  Use spring cache at convenient in that use several annotations @ Cachable, @ CachePut, @ CacheEvict, etc., can make processing data becomes more convenient, but in fact, it is not very convenient, because we need to store the data format setting, while also depending on the circumstances the choice of whether to use the cache (ConcurrentHashMap, Guava cache, Redis?);

  In fact, the use of @ Cachable, @ CachePut, @ CacheEvict there are many limitations of place, such as deleting an item of data, I want to clear the cache more, because this one more data associated with the data, at this time or in the realization of the spring cache these methods operate on the interface, but this involves additional operations in a cache in a cache service.

  For the case said above, do not recommend the use of spring cache, but the cache should be implemented to handle their own hand, this can be done clarity; but the general situation, spring cache has been able to perform competently.

 

Guess you like

Origin www.cnblogs.com/-beyond/p/12436704.html