001package gu.sql2java; 002 003import java.util.Arrays; 004import java.util.concurrent.ConcurrentMap; 005import java.util.concurrent.ExecutionException; 006import java.util.concurrent.TimeUnit; 007 008import com.google.common.base.Joiner; 009import com.google.common.base.Strings; 010import com.google.common.base.Throwables; 011import com.google.common.cache.CacheBuilder; 012import com.google.common.cache.DeepCacheBuilder; 013import com.google.common.cache.CacheLoader; 014import com.google.common.cache.LoadingCache; 015import com.google.common.cache.RemovalListener; 016import com.google.common.cache.RemovalNotification; 017import com.google.common.collect.ImmutableMap; 018import com.google.common.util.concurrent.UncheckedExecutionException; 019 020import gu.sql2java.exception.ObjectRetrievalException; 021import gu.sql2java.exception.RuntimeDaoException; 022 023import static com.google.common.base.Preconditions.*; 024import static gu.sql2java.SimpleLog.*; 025import static com.google.common.base.MoreObjects.*; 026 027 028/** 029 * 基于 {@link LoadingCache}实现表数据缓存,并可以通过{@link TableListener}实现缓存数据自动更新<br> 030 * 支持一个或多个column组成的唯一索引 031 * @author guyadong 032 * 033 * @param <B> 数据库记录对象类型(Java Bean) 034 */ 035public class ColumnCache<B extends BaseBean> implements IKeyCache<B>,RemovalListener<Object[],B>,Constant { 036 protected final RowMetaData metaData; 037 private final LoadingCache<Object[], B> cache; 038 private final ConcurrentMap<Object[], B> cacheMap; 039 /** 当前更新策略 */ 040 protected final UpdateStrategy updateStrategy; 041 protected final Long maximumSize; 042 protected final long duration; 043 protected final TimeUnit unit; 044 private final int[] keyIds; 045 protected final BaseTableManager<B> manager; 046 protected final String indexName; 047 protected static boolean debug = false; 048 /** 049 * 构造函数 050 * @param metaData 表元数据 051 * @param indexName 索引名, 为null时视为primary key 052 * @param updateStrategy 缓存更新策略 053 * @param maximumSize 最大缓存容量,参见 {@link CacheBuilder#maximumSize(long)} 054 * @param duration 失效时间,参见 {@link CacheBuilder#expireAfterWrite(long, TimeUnit)} 055 * @param unit {@code duration}的时间单位 056 */ 057 ColumnCache(RowMetaData metaData,String indexName,UpdateStrategy updateStrategy, long maximumSize, long duration, TimeUnit unit) { 058 this.metaData = checkNotNull(metaData,"metaData is null"); 059 this.manager = BaseTableManager.getTableManager(metaData.tablename); 060 this.indexName = Strings.emptyToNull(indexName); 061 if(this.indexName == null){ 062 this.keyIds = metaData.primaryKeyIds; 063 }else{ 064 this.keyIds = metaData.indexIdArray(indexName); 065 } 066 067 this.updateStrategy = firstNonNull(updateStrategy, DEFAULT_STRATEGY); 068 this.maximumSize = maximumSize > 0 ? maximumSize : DEFAULT_CACHE_MAXIMUMSIZE; 069 this.duration = duration > 0 ? duration : DEFAULT_DURATION; 070 this.unit = firstNonNull(unit, DEFAULT_TIME_UNIT); 071 cache = DeepCacheBuilder.newBuilder() 072 .maximumSize(this.maximumSize) 073 .expireAfterWrite(this.duration, this.unit) 074 .removalListener(this) 075 .build( 076 new CacheLoader<Object[],B>() { 077 @Override 078 public B load(Object[] keys) throws Exception { 079 return loadfromDatabase(keys); 080 }}); 081 cacheMap = cache.asMap(); 082 if(debug){ 083 log("ColumnCache FOR %s(%s) of %s(%s)", 084 firstNonNull(indexName, "PK"), 085 Joiner.on(",").join(metaData.columnNamesOf(keyIds)), 086 metaData.tablename, 087 updateStrategy); 088 } 089 } 090 /** 091 * @param objects 092 * @return first index of element that not null if exists,or return -1 if not found,or -2 if objects is null 093 */ 094 private static int indexOfFirstNull(Object...objects) { 095 if(objects != null){ 096 for(int i = 0; i < objects.length; ++i){ 097 if(null == objects[i]){ 098 return i; 099 } 100 } 101 return -1; 102 } 103 return -2; 104 } 105 /** 106 * @param objects object array 107 * @return true if objects is null or any element in objects is null 108 */ 109 private static boolean hasNull(Object...objects) { 110 return indexOfFirstNull(objects) != -1; 111 } 112 /** 113 * check the keys's value is valid for index or primary key 114 * @param keys array of key value 115 * @throws ObjectRetrievalException has null element in keys 116 */ 117 private void checkNonNullKey(Object...keys)throws ObjectRetrievalException{ 118 checkArgument(keys != null && keys.length == keyIds.length, 119 "MISMATCHED length of 'keys' with column count of %s", 120 firstNonNull(indexName, "PK")); 121 int index = indexOfFirstNull(keys); 122 if(index != -1){ 123 throw new ObjectRetrievalException(String.format("value of %s is null", metaData.columnNames.get(keyIds[index]))); 124 } 125 } 126 /** 127 * 从数据库中加载外键指定的记录,没有找到指定的记录则抛出异常{@link ObjectRetrievalException}<br> 128 * @param keys 129 * @return B 130 * @throws RuntimeDaoException 131 * @throws ObjectRetrievalException 132 */ 133 private B loadfromDatabase(Object[] keys) throws RuntimeDaoException, ObjectRetrievalException{ 134 135 ImmutableMap.Builder<Integer, Object> builder = ImmutableMap.builder(); 136 for(int i = 0 ; i < keyIds.length; ++i){ 137 builder.put(keyIds[i], keys[i]); 138 } 139 B bean = manager.createBean().copy(builder.build()); 140 if(debug){ 141 log("LOAD BY %s%s of %s",firstNonNull(indexName, "PK"),Arrays.toString(keys),metaData.tablename); 142 } 143 try { 144 return manager.loadUniqueUsingTemplateChecked(bean); 145 } catch (ObjectRetrievalException e) { 146 throw new ObjectRetrievalException(logString("Not found element for {}{} of {}", 147 firstNonNull(indexName, "PK"),Arrays.toString(keys),metaData.tablename)); 148 } 149 150 } 151 @Override 152 public B getBean(Object... keys)throws ObjectRetrievalException{ 153 checkNonNullKey(keys); 154 try { 155 return cache.get(keys); 156 }catch(ExecutionException | UncheckedExecutionException e){ 157 if(null != e.getCause()){ 158 Throwables.throwIfInstanceOf(e.getCause(), ObjectRetrievalException.class); 159 Throwables.throwIfUnchecked(e.getCause()); 160 } 161 Throwables.throwIfUnchecked(e); 162 throw new RuntimeException(e); 163 } 164 } 165 166 @Override 167 public B getBeanUnchecked(Object... keys){ 168 try{ 169 return getBean(keys); 170 }catch(ObjectRetrievalException e){ 171 return null; 172 } 173 } 174 175 @Override 176 public void remove(B bean){ 177 if(bean != null){ 178 Object[] keys = bean.asValueArray(keyIds); 179 cacheMap.remove(keys); 180 } 181 } 182 183 @Override 184 public void update(B bean, UpdateStrategy updateStrategy){ 185 if(bean != null){ 186 updateStrategy = firstNonNull(updateStrategy, UpdateStrategy.always); 187 Object[] keys = bean.asValueArray(keyIds); 188 if(!hasNull(keys)){ 189 switch (updateStrategy) { 190 case replace: 191 cacheMap.replace(keys, bean); 192 break; 193 case remove: 194 cacheMap.remove(keys); 195 break; 196 case refresh: 197 // 读取记录时,CacheWrapper 会自动更新当前对象,这里不需要显式更新 198 loadfromDatabase(keys); 199 break; 200 case always: 201 default: 202 cacheMap.put(keys, bean); 203 break; 204 } 205 if(debug){ 206 log("UPDATE(%s) RECORD %s%s of %s", 207 updateStrategy, 208 firstNonNull(indexName, "PK"), 209 Arrays.toString(keys), 210 metaData.tablename); 211 } 212 } 213 } 214 } 215 216 @Override 217 public void update(B bean){ 218 update(bean, updateStrategy); 219 } 220 221 @Override 222 public void onRemoval(RemovalNotification<Object[], B> notification) { 223 if(debug){ 224 log("CACHE REMOVE:Key:{}({}) for {}", 225 firstNonNull(indexName, "PK"), 226 Arrays.toString(notification.getKey()), 227 metaData.tablename); 228 } 229 } 230 /** 231 * @return native manager 232 */ 233 public BaseTableManager<B> getManager() { 234 return manager; 235 } 236 237 @Override 238 public int hashCode() { 239 final int prime = 31; 240 int result = 1; 241 result = prime * result + ((indexName == null) ? 0 : indexName.hashCode()); 242 result = prime * result + Arrays.hashCode(keyIds); 243 result = prime * result + ((metaData == null) ? 0 : metaData.hashCode()); 244 result = prime * result + ((updateStrategy == null) ? 0 : updateStrategy.hashCode()); 245 return result; 246 } 247 @Override 248 public boolean equals(Object obj) { 249 if (this == obj) { 250 return true; 251 } 252 if (obj == null) { 253 return false; 254 } 255 if (!(obj instanceof ColumnCache)) { 256 return false; 257 } 258 ColumnCache<?> other = (ColumnCache<?>) obj; 259 if (indexName == null) { 260 if (other.indexName != null) { 261 return false; 262 } 263 } else if (!indexName.equals(other.indexName)) { 264 return false; 265 } 266 if (!Arrays.equals(keyIds, other.keyIds)) { 267 return false; 268 } 269 if (metaData == null) { 270 if (other.metaData != null) { 271 return false; 272 } 273 } else if (!metaData.equals(other.metaData)) { 274 return false; 275 } 276 if (updateStrategy != other.updateStrategy) { 277 return false; 278 } 279 return true; 280 } 281 @Override 282 public String toString() { 283 StringBuilder builder = new StringBuilder(); 284 builder.append("ColumnCache [tablename="); 285 builder.append(metaData.tablename); 286 builder.append(", updateStrategy="); 287 builder.append(updateStrategy); 288 builder.append(", keyIds="); 289 builder.append(metaData.columnNamesOf(keyIds)); 290 builder.append(", indexName="); 291 builder.append(firstNonNull(indexName,"PK")); 292 builder.append(", maximumSize="); 293 builder.append(maximumSize); 294 builder.append(", duration="); 295 builder.append(duration); 296 builder.append(", unit="); 297 builder.append(unit); 298 builder.append("]"); 299 return builder.toString(); 300 } 301 /** 302 * set debug flag that determine if output log message,default : false 303 * @param debug flag for debug message output 304 */ 305 public static void setDebug(boolean debug) { 306 ColumnCache.debug = debug; 307 } 308}