Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
gu.sql2java.manager.cache.ColumnCache Maven / Gradle / Ivy
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;
}
}