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