All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gu.sql2java.manager.cache.ColumnCache Maven / Gradle / Ivy

The newest version!
package gu.sql2java.manager.cache;

import java.util.Arrays;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.UncheckedExecutionException;

import gu.sql2java.BaseBean;
import gu.sql2java.Constant;
import gu.sql2java.RowMetaData;
import gu.sql2java.TableListener;
import gu.sql2java.exception.ObjectRetrievalException;
import gu.sql2java.exception.RuntimeDaoException;
import gu.sql2java.guava.DeepCacheBuilder;
import gu.sql2java.manager.BaseTableManager;

import static com.google.common.base.Preconditions.*;
import static gu.sql2java.SimpleLog.*;
import static gu.sql2java.manager.Managers.getBaseTableManager;
import static com.google.common.base.MoreObjects.*;

/**
 * 基于 {@link LoadingCache}实现表数据缓存,并可以通过{@link TableListener}实现缓存数据自动更新
* 支持一个或多个column组成的唯一索引 * @author guyadong * * @param 数据库记录对象类型(Java Bean) */ public class ColumnCache implements IKeyCache,RemovalListener,Constant { protected final RowMetaData metaData; private final LoadingCache cache; private final ConcurrentMap cacheMap; /** 当前更新策略 */ protected final UpdateStrategy updateStrategy; protected final Long maximumSize; protected final long duration; protected final TimeUnit unit; private final int[] keyIds; protected final BaseTableManager manager; protected final String indexName; protected static boolean debug = false; /** * 构造函数 * @param metaData meta data for table * @param indexName index name , as primary key if {@code null} * @param updateStrategy cache update strategy,{@link Constant#DEFAULT_STRATEGY} be used if {@code null} * @param maximumSize maximum capacity of cache ,{@link Constant#DEFAULT_CACHE_MAXIMUMSIZE } be used if {@code null} or <=0,see also {@link CacheBuilder#maximumSize(long)} * @param duration cache data expired time,{@link Constant#DEFAULT_DURATION} be used if {@code null} or <=0,see also {@link CacheBuilder#expireAfterAccess(long, TimeUnit)} * @param unit time unit for {@code duration},{@link Constant#DEFAULT_TIME_UNIT} be used if {@code null},see also {@link CacheBuilder#expireAfterAccess(long, TimeUnit)} */ ColumnCache(RowMetaData metaData,String indexName,UpdateStrategy updateStrategy, Long maximumSize, Long duration, TimeUnit unit) { this.metaData = checkNotNull(metaData,"metaData is null"); this.manager = getBaseTableManager(metaData.tablename); this.indexName = Strings.emptyToNull(indexName); if(this.indexName == null){ this.keyIds = metaData.primaryKeyIds; }else{ this.keyIds = metaData.indexIdArray(indexName); } this.updateStrategy = firstNonNull(updateStrategy, DEFAULT_STRATEGY); this.maximumSize = (maximumSize != null && maximumSize > 0) ? maximumSize : DEFAULT_CACHE_MAXIMUMSIZE; this.duration = (duration != null && duration > 0) ? duration : DEFAULT_DURATION; this.unit = firstNonNull(unit, DEFAULT_TIME_UNIT); cache = DeepCacheBuilder.newBuilder() .maximumSize(this.maximumSize) .expireAfterAccess(this.duration, this.unit) .removalListener(this) .build( new CacheLoader() { @Override public B load(Object[] keys) throws Exception { return loadfromDatabase(keys); }}); cacheMap = cache.asMap(); if(debug){ log("ColumnCache FOR %s(%s) of %s(%s)", firstNonNull(indexName, "PK"), Joiner.on(",").join(metaData.columnNamesOf(keyIds)), metaData.tablename, updateStrategy); } } /** * @param objects * @return first index of element that not null if exists,or return -1 if not found,or -2 if objects is null */ private static int indexOfFirstNull(Object...objects) { if(objects != null){ for(int i = 0; i < objects.length; ++i){ if(null == objects[i]){ return i; } } return -1; } return -2; } /** * @param objects object array * @return true if objects is null or any element in objects is null */ private static boolean hasNull(Object...objects) { return indexOfFirstNull(objects) != -1; } /** * check the keys's value is valid for index or primary key * @param keys array of key value * @throws ObjectRetrievalException has null element in keys */ private void checkNonNullKey(Object...keys)throws ObjectRetrievalException{ checkArgument(keys != null && keys.length == keyIds.length, "MISMATCHED length of 'keys' with column count of %s", firstNonNull(indexName, "PK")); int index = indexOfFirstNull(keys); if(index != -1){ throw new ObjectRetrievalException(String.format("value of %s is null", metaData.columnNames.get(keyIds[index]))); } } /** * 从数据库中加载外键指定的记录,没有找到指定的记录则抛出异常{@link ObjectRetrievalException}
* @param keys * @return B * @throws RuntimeDaoException * @throws ObjectRetrievalException */ protected B loadfromDatabase(Object[] keys) throws RuntimeDaoException, ObjectRetrievalException{ ImmutableMap.Builder builder = ImmutableMap.builder(); for(int i = 0 ; i < keyIds.length; ++i){ builder.put(keyIds[i], keys[i]); } B bean = manager.createBean().copyNoFilter(builder.build()); if(debug){ log("LOAD BY %s%s of %s",firstNonNull(indexName, "PK"),Arrays.toString(keys),metaData.tablename); } try { return manager.loadUniqueUsingTemplateChecked(bean); } catch (ObjectRetrievalException e) { throw new ObjectRetrievalException(logString("Not found element for {}{} of {}", firstNonNull(indexName, "PK"),Arrays.toString(keys),metaData.tablename)); } } @Override public B getBean(Object... keys)throws ObjectRetrievalException{ checkNonNullKey(keys); try { return cache.get(keys); }catch(ExecutionException | UncheckedExecutionException e){ if(null != e.getCause()){ Throwables.throwIfInstanceOf(e.getCause(), ObjectRetrievalException.class); Throwables.throwIfUnchecked(e.getCause()); } Throwables.throwIfUnchecked(e); throw new RuntimeException(e); } } @Override public B getBeanUnchecked(Object... keys){ try{ return getBean(keys); }catch(ObjectRetrievalException e){ return null; } } @Override public boolean hasValidKey(B bean){ if(bean != null){ for(Object key : bean.asValueArray(keyIds)){ if(key == null){ return false; } } return true; } return false; } @Override public B remove(B bean){ if(bean != null){ Object[] keys = bean.asValueArray(keyIds); return cacheMap.remove(keys); } return null; } @Override public B removeCached(Object... keys) { if(keys != null && keys.length > 0) { return cacheMap.remove(keys); } return null; } @Override public void update(B bean, UpdateStrategy updateStrategy){ if(bean != null){ updateStrategy = firstNonNull(updateStrategy, UpdateStrategy.always); Object[] keys = bean.asValueArray(keyIds); if(!hasNull(keys)){ switch (updateStrategy) { case replace: cacheMap.replace(keys, bean); break; case remove: cacheMap.remove(keys); break; case refresh: cacheMap.put(keys,loadfromDatabase(keys)); break; case always: default: cacheMap.put(keys, bean); break; } if(debug){ log("UPDATE(%s) RECORD %s%s of %s", updateStrategy, firstNonNull(indexName, "PK"), Arrays.toString(keys), metaData.tablename); } } } } @Override public void update(B bean){ update(bean, updateStrategy); } @Override public void onRemoval(RemovalNotification notification) { if(debug){ log("CACHE REMOVE:Key:{}({}) for {}", firstNonNull(indexName, "PK"), Arrays.toString(notification.getKey()), metaData.tablename); } } /** * @return native manager */ public BaseTableManager getManager() { return manager; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((indexName == null) ? 0 : indexName.hashCode()); result = prime * result + Arrays.hashCode(keyIds); result = prime * result + ((metaData == null) ? 0 : metaData.hashCode()); result = prime * result + ((updateStrategy == null) ? 0 : updateStrategy.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof ColumnCache)) { return false; } ColumnCache other = (ColumnCache) obj; if (indexName == null) { if (other.indexName != null) { return false; } } else if (!indexName.equals(other.indexName)) { return false; } if (!Arrays.equals(keyIds, other.keyIds)) { return false; } if (metaData == null) { if (other.metaData != null) { return false; } } else if (!metaData.equals(other.metaData)) { return false; } if (updateStrategy != other.updateStrategy) { return false; } return true; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("ColumnCache [tablename="); builder.append(metaData.tablename); builder.append(", updateStrategy="); builder.append(updateStrategy); builder.append(", keyIds="); builder.append(metaData.columnNamesOf(keyIds)); builder.append(", indexName="); builder.append(firstNonNull(indexName,"PK")); builder.append(", maximumSize="); builder.append(maximumSize); builder.append(", duration="); builder.append(duration); builder.append(", unit="); builder.append(unit); builder.append("]"); return builder.toString(); } /** * set debug flag that determine if output log message,default : false * @param debug flag for debug message output */ public static void setDebug(boolean debug) { ColumnCache.debug = debug; } }