예전 CommonDaoPattern 이라고 하여, 프로세스는 다음과 같습니다.
- ehcache를 검색합니다. 2-1. ehcache에 데이터가 없을 시 레디스를 검색합니다. 2-2. ehcache에 데이터가 있을 시 ehcache의 데이터를 return 합니다. 3-1. redis에 데이터가 없을 시 DB를 검색합니다. 3-2. redis에 데이터가 있을 시 ehcache에 해당 데이터를 넣고 데이터를 return 합니다. 4-1. DB에 검색한 데이터가 없을 시 5분간 해당 primary key를 캐시에 담아 return null 시킵니다. 4-2. DB에 검색한 데이터가 있을 시, 레디스랑 ehcache에 해당 데이터를 담습니다.
public class CommonDaoPattern<T1> {
private static final Logger LOG = LoggerFactory.getLogger(CommonDaoPattern.class);
protected static final Random random = new Random();
private CommonEHCacheManager<T1> cacheManager;
private CommonRedisManager<T1> redisManager;
private static final Cache emptyKeyFromDB;
static {
emptyKeyFromDB = CacheManager.create().getCache(EHCacheConstants.EMPTY_KEY_CACHE);
}
public CommonDaoPattern(CommonEHCacheManager<T1> cacheManager, CommonRedisManager<T1> redisManager) {
this.cacheManager = cacheManager;
this.redisManager = redisManager;
}
public CommonDaoPattern(CommonEHCacheManager<T1> cacheManager) {
this.cacheManager = cacheManager;
}
public CommonDaoPattern(CommonRedisManager<T1> redisManager) {
this.redisManager = redisManager;
}
public CommonDaoPattern() {
}
public void setCacheManager(CommonEHCacheManager<T1> cacheManager) {
this.cacheManager = cacheManager;
}
protected CommonEHCacheManager<T1> getCacheManager() {
return this.cacheManager;
}
public void setRedisManager(CommonRedisManager<T1> redisManager) {
this.redisManager = redisManager;
}
protected CommonRedisManager<T1> getRedisManager() {
return this.redisManager;
}
protected Cache getEmptyKeyFromDB() {
return emptyKeyFromDB;
}
protected boolean isDBSearch() {
boolean result = false;
try {
result = PropertyHandler.getBoolean("USE_LOCAL_QUERYING");
} catch (Exception e) {
if (LOG.isDebugEnabled())
LOG.debug("{}", e.getStackTrace().toString());
}
return result;
}
public T1 retrieveData(String key) {
return retrieveData(key, null);
}
public <T2> T1 retrieveData(String key, T2 t2) {
T1 returnObject = null;
try {
SEARCH_TYPE searchType = SEARCH_TYPE.NOTHING;
if (!commonValidation(key) || !doValidation(key, t2)) {
if (LOG.isDebugEnabled())
LOG.debug("validation is failed... please check >>>key:{}, DBdata:{}", key, t2);
return null;
}
// 1. get from EHcache
if (cacheManager != null) {
searchType = SEARCH_TYPE.CACHE;
returnObject = cacheManager.selFromCache(key);
}
// 2. get from REDIS
if (redisManager != null && returnObject == null) {
searchType = SEARCH_TYPE.REDIS;
returnObject = redisManager.get(key);
}
// 3. fetch from DB
if (returnObject == null) {
searchType = SEARCH_TYPE.DB;
try {
returnObject = t2 == null ? selFromDB() : selFromDB(t2);
if (returnObject == null)
returnObject = t2 == null ? selFromDB(key) : selFromDB(key, t2);
if (returnObject == null) {
String emptyKey = _generateEmptyKey(key);
if (emptyKey != null) {
emptyKeyFromDB.put(new Element(emptyKey, null));
if (LOG.isDebugEnabled())
LOG.debug("empty key is put into EHCache>>>{}", emptyKey);
}
}
} catch (Exception e) {
LOG.error("key>>> {}, {}", key, t2, e);
}
}
// 4. restore data to ehcache, Redis
if ((searchType == SEARCH_TYPE.REDIS || searchType == SEARCH_TYPE.DB) && cacheManager != null && returnObject != null) {
cacheManager.storeIntoCacheWithKey(key, returnObject);
}
if (searchType == SEARCH_TYPE.DB && redisManager != null && returnObject != null) {
redisManager.set(key, returnObject);
}
} catch (Exception e) {
e.printStackTrace();
LOG.error(ErrorLog.getStack(e, String.format("key>>>%s, %s", key, t2)));
}
return returnObject;
}
public void loadData(String key, T1 t1) {
try {
// 1. load to EHcache
if (cacheManager != null) {
cacheManager.storeIntoCacheWithKey(key, t1);
LOG.debug("complete load to CACHE");
}
// 2. load to REDIS
if (redisManager != null) {
redisManager.set(key, t1);
LOG.debug("complete load to REDIS");
}
} catch (Exception e) {
LOG.error(ErrorLog.getStack(e, ""));
}
}
/**
* DB에서 데이터를 조회한뒤 캐시와 레디스에 로딩한다.
*
* @param key
* @return
*/
public <T2> void loadDataFromDB(String key, T2 t2) {
try {
T1 t1 = selFromDB(t2);
// 1. load to EHcache
if (cacheManager != null) {
cacheManager.storeIntoCacheWithKey(key, t1);
LOG.debug("complete load to CACHE");
}
// 2. load to REDIS
if (redisManager != null) {
redisManager.set(key, t1);
LOG.debug("complete load to REDIS");
}
} catch (Exception e) {
LOG.error("error key={}", key, e);
}
}
public <T2> void loadRedisDataFromDB(String key, T2 t2) {
try {
T1 t1 = selFromDB(key, t2);
if (redisManager != null) {
redisManager.set(key, t1);
LOG.debug("complete load to REDIS");
}
} catch (Exception e) {
LOG.error("error key={}", key, e);
}
}
protected boolean doValidation(String key, Object t2) {
return true;
}
// key 체크는 필수로 한다. empty key 가 있는지 체크한다.
protected boolean commonValidation(String key) {
if (StringUtils.isEmpty(key))
return false;
if (emptyKeyFromDB.isKeyInCache(_generateEmptyKey(key)))
return false;
return true;
}
// 디비 로직을 쓴다면 반드시 오버라이딩해서 구현.
public T1 selFromDB() throws SQLException {
return null;
}
// 디비 로직을 쓴다면 반드시 오버라이딩해서 구현.
public T1 selFromDB(Object obj) throws SQLException {
return null;
}
public T1 selFromDB(String key) throws SQLException {
return null;
}
public T1 selFromDB(String key, Object obj) throws SQLException {
return null;
}
protected String _generateEmptyKey(String key) {
if (cacheManager != null) {
return cacheManager.getCacheName() + GlobalConstants.GUBUN + key;
} else if (redisManager != null) {
return redisManager.generateKey(key);
}
return null;
}
}
해당 클래스를 처리하기 위해 selFromDB 를 overide 받아 쿼리를 작성하는 형태로 구현하였습니다. 3Depth 기능을 스프링부트에서 구현한다고 한다면, 다음과 같이 구성할 수 있습니다.
import org.springframework.cache.CacheManager;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
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.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class CacheConfig {
@Bean(name = "redisCacheManager")
public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(defaultConf())
.withInitialCacheConfigurations(confMap())
.build();
}
@Bean(name = "ehCacheManager")
@Primary
public CacheManager cacheManager() {
return new EhCacheCacheManager(ehCacheCacheManager().getObject());
}
@Bean
public EhCacheManagerFactoryBean ehCacheCacheManager() {
EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
cmfb.setConfigLocation(new ClassPathResource("/config/ehcache.xml"));
cmfb.setShared(true);
return cmfb;
}
private RedisCacheConfiguration defaultConf() {
return RedisCacheConfiguration.defaultCacheConfig()
// .serializeKeysWith(fromSerializer(new StringRedisSerializer()))
// .serializeValuesWith(fromSerializer(new GenericJackson2JsonRedisSerializer()))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofMinutes(1));
}
private Map<String, RedisCacheConfiguration> confMap() {
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
cacheConfigurations.put("findBySspNo", defaultConf().entryTtl(Duration.ofMinutes(30L)));
cacheConfigurations.put("findByDspList", defaultConf().entryTtl(Duration.ofMinutes(1L)));
cacheConfigurations.put("findBycodeTpIdAndCodeId", defaultConf().entryTtl(Duration.ofMinutes(1L)));
cacheConfigurations.put("findByGoogleSspNo", defaultConf().entryTtl(Duration.ofMinutes(30L)));
return cacheConfigurations;
}
}
위와 같이 redisCacheManager, ehCacheManager 을 구현한 후, @Cacheable(cacheManager = “ehCacheManager”) 를 @Cacheable(cacheManager = “redisManager”) 로 감싸주면 됩니다.