package jef.database.cache;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import jef.database.DbUtils;
import jef.database.IQueryableEntity;
import jef.database.ORMConfig;
import jef.database.SelectProcessor;
import jef.database.SqlProcessor;
import jef.database.dialect.DatabaseDialect;
import jef.database.jsqlparser.expression.JdbcParameter;
import jef.database.jsqlparser.expression.JpqlParameter;
import jef.database.jsqlparser.expression.Table;
import jef.database.jsqlparser.statement.delete.Delete;
import jef.database.jsqlparser.statement.insert.Insert;
import jef.database.jsqlparser.statement.truncate.Truncate;
import jef.database.jsqlparser.statement.update.Update;
import jef.database.jsqlparser.visitor.VisitorAdapter;
import jef.database.meta.AbstractMetadata;
import jef.database.meta.ITableMetadata;
import jef.database.meta.MetaHolder;
import jef.database.query.SqlContext;
import jef.database.wrapper.clause.BindSql;
import jef.database.wrapper.clause.QueryClause;
import jef.database.wrapper.variable.Variable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;
/**
* The default level 1 Cache implementation.
*
* @author jiyi
*
*/
@SuppressWarnings("rawtypes")
public class CacheImpl implements Cache {
SqlProcessor preparedSqlProcessor;
SelectProcessor selectp;
private ORMConfig config = ORMConfig.getInstance();
private static Logger logger = LoggerFactory.getLogger(CacheImpl.class);
/**
* 缓存计数器
*/
private AtomicLong hit = new AtomicLong();
/**
* 缓存计数器
*/
private AtomicLong miss = new AtomicLong();
/**
* 缓存失效周期,单位秒
*/
private int expireInterval;
/**
* 缓存名称
*/
private String name;
private DatabaseDialect profile;
/**
*
* 缓存实际上是三级Map。 第一级是以查询用的表(或多表)为key。 第二级是以where语句的模版为key
* 第三级是以where语句中的参数为key。
*
* 定时失效机制如何添加:拟添加在第三层 清洗规则中,可以以任意一级为条件进行清洗
*
*/
private final Map<String, Map<KeyDimension, DimCache>> cache = new ConcurrentHashMap<String, Map<KeyDimension, DimCache>>();
/**
* 构造
*
* @param sql
* SQL处理器
* @param selectp
* 查询处理器
* @param expireTime
* 缓存过期事件
*/
public CacheImpl(SqlProcessor sql, SelectProcessor selectp, int expireInterval, String name) {
this.preparedSqlProcessor = sql;
this.selectp = selectp;
this.expireInterval = expireInterval;
this.name = name;
this.profile = preparedSqlProcessor.getProfile();
}
public boolean contains(Class cls, Object primaryKey) {
AbstractMetadata meta = MetaHolder.getMeta(cls);
KeyDimension dim = meta.getPKDimension(profile);
Map<KeyDimension, DimCache> tableCache = cache.get(dim.getTableDefinition());
if (tableCache == null || tableCache.isEmpty())
return false;
DimCache dc = tableCache.get(dim);
if (dc == null)
return false;
List<Serializable> pks = toPrimaryKey(primaryKey);
return dc.load(pks) != null;
}
@SuppressWarnings("unchecked")
public static List<Object> toParamList(List<Variable> bind) {
if (bind == null)
return Collections.EMPTY_LIST;
Object[] array = new Object[bind.size()];
int n = 0;
for (Variable b : bind) {
array[n++] = b.getConstantValue();
}
return Arrays.asList(array);
}
/**
* 精确清除缓存。没有什么实际意义。不建议使用!!!。 因为EF的缓存空间是一个多维度的空间,并且维度之间还存在关系。定点清除没有任何意义。
*/
public void evict(Class cls, Object primaryKey) {
throw new UnsupportedOperationException();
// AbstractMetadata meta = MetaHolder.getMeta(cls);
// KeyDimension dim = meta.getPKDimension(profile);
//
// Map<KeyDimension, DimCache> tableCache =
// cache.get(dim.getTableDefinition());
// if (tableCache == null || tableCache.isEmpty())
// return;
// @SuppressWarnings("deprecation")
// DimCache dc = tableCache.get(dim);
// if (dc == null)
// return;
//
// List<Serializable> pks = toPrimaryKey(primaryKey);
// dc.remove(pks);
}
public void evict(Class cls) {
AbstractMetadata meta = MetaHolder.getMeta(cls);
KeyDimension dim = meta.getPKDimension(profile);
Map<KeyDimension, DimCache> tableSpace = this.cache.get(dim.getTableDefinition());
for (DimCache cache : tableSpace.values()) {
cache.clear();
}
}
public void evictAll() {
for (Map<KeyDimension, DimCache> space : cache.values()) {
space.clear();
}
}
/**
* for JPA Only
*
* @param key
*/
public void evict(CacheKey key) {
Map<KeyDimension, DimCache> tableCache = cache.get(key.getStoreSpace());
if (tableCache == null || tableCache.isEmpty())
return;
DimCache dc = tableCache.get(key.getDimension());
if (dc == null)
return;
dc.remove(key.getParams());
}
public <T> void onLoad(CacheKey key, List<T> result, Class<T> clz) {
// 结果集太大不缓存
if (key == null)
return;
if (result.size() > 5000)
return;
DimCache dc;
{
Map<KeyDimension, DimCache> tableCache = getCreateTableCache(key.getStoreSpace());
dc = tableCache.get(key.getDimension());
if (dc == null) {
dc = expireInterval > 0 ? new DimCacheExpImpl(expireInterval) : new DimCacheImpl();
tableCache.put(key.getDimension(), dc);
}
dc.put(key.getParams(), ImmutableList.copyOf(result));
}
if (key.getAffectedKey() != null) {
for (String indexKey : key.getAffectedKey()) {
// DC串门算法。通过DC串门,让关联空间也能发现这个DC,从而清除DC
Map<KeyDimension, DimCache> tableCache = getCreateTableCache(indexKey);
tableCache.put(key.getDimension(), dc);
}
}
if (config.cacheDebug) {
logger.info("{}-Cache Store:{}, Size={}", name, key, result.size());
}
}
public List load(CacheKey key) {
if (key == null)
return null;
Map<KeyDimension, DimCache> tableCache = cache.get(key.getStoreSpace());
boolean debug = config.cacheDebug;
if (tableCache == null || tableCache.isEmpty()) {
miss.getAndIncrement();
if (debug)
logger.info("{}-Cache Miss: {}", name, key);
return null;
}
DimCache dc = tableCache.get(key.getDimension());
if (dc == null) {
miss.getAndIncrement();
if (debug)
logger.info("{}-Cache Miss: {}", name, key);
return null;
}
List list = dc.load(key.getParams());
if (debug) {
if (list == null) {
miss.getAndIncrement();
logger.info("{}-Cache Miss: {}", name, key);
} else {
hit.incrementAndGet();
logger.info("{}-Cache Hit: {}", name, key);
}
}
return list;
}
public void onInsert(IQueryableEntity obj, String table) {
if (obj == null)
return;
/*
* 2015-6-15 由于在存入对象时,没有延迟加载钩子,造成读取到缓存中的对象时,延迟加载功能未生效。
* 由于Entity有状态(延迟加载钩子),缓存中如何处理?
*
* 办法1:先停止插入时的缓存。 解决办法2:级联关系不缓存,也就是说,每次单表查询完成后,无论是否命中缓存都要重新计算级联关系。
*
* 目前两个方案都启用。
* 采用方案1由于存入缓存的对象句柄依然在外部,意味着用户可以随意修改该对象用作其他用途,因此将这样的对象直接缓存下来是危险的。
*/
AbstractMetadata meta = MetaHolder.getMeta(obj);
KeyDimension dim = null;
if (!meta.getPKFields().isEmpty()) {
List<Serializable> pks = DbUtils.getPKValueSafe(obj);
if (pks == null)
return;
if (table != null) {
dim = meta.getPKDimension(profile).newKeyDimensionOf(table, profile);
} else {
dim = meta.getPKDimension(profile);
}
CacheKey pkCache = new SqlCacheKey(dim, pks);
Map<KeyDimension, DimCache> tableCache = getCreateTableCache(pkCache.getStoreSpace());
refreshCache(tableCache, pkCache, null);
} else {
Map<KeyDimension, DimCache> tableCache = cache.get(meta.getTableName(false).toUpperCase());
if (tableCache != null)
refreshCacheExcept(tableCache, null);
}
}
public void evict(IQueryableEntity obj) {
if (obj == null)
return;
ITableMetadata meta = MetaHolder.getMeta(obj);
String baseTableName = meta.getTableName(false);
Map<KeyDimension, DimCache> tableCache = cache.get(baseTableName);
if (tableCache == null || tableCache.isEmpty())
return;
if (obj.hasQuery()) {
QueryClause ir = selectp.toQuerySql(obj.getQuery(), null, false);
evict(ir.getCacheKey());
return;
}
BindSql sql = preparedSqlProcessor.toWhereClause(obj.getQuery(), new SqlContext(null, obj.getQuery()), null, null,false);
obj.clearQuery();
DimCache dc = tableCache.get(KeyDimension.forSingleTable(baseTableName, sql.getSql(), null, profile));
if (dc == null)
return;
dc.remove(toParamList(sql.getBind()));
}
public void onDelete(String table, String where, List<Object> object) {
CacheKey key = new SqlCacheKey(KeyDimension.forSingleTable(table, where, null, profile), object);
Map<KeyDimension, DimCache> tableCache = this.cache.get(key.getStoreSpace());
if (tableCache == null || tableCache.isEmpty()) {
return;
}
refreshCache(tableCache, key, null);
}
public void onUpdate(String table, String where, List<Object> object) {
CacheKey key = new SqlCacheKey(KeyDimension.forSingleTable(table, where, null, profile), object);
Map<KeyDimension, DimCache> tableCache = this.cache.get(key.getStoreSpace());
if (tableCache == null || tableCache.isEmpty()) {
return;
}
refreshCache(tableCache, key, null);
}
/**
* 缓存刷新策略 同维度。key所指定的缓存更新或失效 异维度,全部失效
*
* @param tableCache
* 目标空间
* @param key
* 被保留的维度
* @param obj
* 在被保留维度中放入缓存的对象
*/
private void refreshCache(Map<KeyDimension, DimCache> tableCache, CacheKey key, IQueryableEntity obj) {
DimCache cache = tableCache.remove(key.getDimension());
for (DimCache other : tableCache.values()) {
// 其他维度一律失效。必须用此种方法,才能清除掉串门的缓存
other.clear();
}
if (cache == null) {
if (obj != null) {// 添加缓存
cache = expireInterval > 0 ? new DimCacheExpImpl(expireInterval) : new DimCacheImpl();
cache.put(key.getParams(), Arrays.asList(obj));
if (config.cacheDebug)
logger.info("{}-Cache Store: {}", name, key);
}
// else{} 本来就没有,什么也不用做
} else {
if (obj != null) {// 更新缓存
cache.put(key.getParams(), Arrays.asList(obj));
if (config.cacheDebug)
logger.info("{}-Cache Store: {}", name, key);
} else { // 删除缓存
cache.remove(key.getParams());
}
}
// 计算完成后,回写缓存
if (cache != null)
tableCache.put(key.getDimension(), cache);
}
/**
* 清除除了指定维度之外的全部缓存
*
* @param tableCache
* @param key
*/
private void refreshCacheExcept(Map<KeyDimension, DimCache> tableCache, KeyDimension key) {
if (key == null) {
for (Map.Entry<KeyDimension, DimCache> other : tableCache.entrySet()) {
other.getValue().clear();
}
} else {
for (Map.Entry<KeyDimension, DimCache> other : tableCache.entrySet()) {
// 其他维度一律失效。必须用此种方法,才能清除掉串门的缓存
if (!other.getKey().equals(key)) {
other.getValue().clear();
}
}
}
}
private Map<KeyDimension, DimCache> getCreateTableCache(String table) {
Map<KeyDimension, DimCache> tableCache = this.cache.get(table);
if (tableCache == null) {
tableCache = new ConcurrentHashMap<KeyDimension, DimCache>();
this.cache.put(table, tableCache);
}
return tableCache;
}
public boolean isDummy() {
return false;
}
public void process(Truncate st, List<Object> list) {
Table t = st.getTable();
String tableName = t.getName().toUpperCase();
Map<KeyDimension, DimCache> tableCache = this.cache.get(tableName);
if (tableCache != null) {
refreshCacheExcept(tableCache, null);
}
}
public void process(Delete st, List<Object> list) {
if (st.getTable() instanceof Table) {
Table t = (Table) st.getTable();
KeyDimension dim = new KeyDimension(t, st.getWhere(), null);
CacheKey key = new SqlCacheKey(dim, list);
Map<KeyDimension, DimCache> tableCache = this.cache.get(key.getStoreSpace());
if (tableCache == null)
return;
// 删除了该表中的若干数据
// 该表相关缓存中,除了相同维度,且未被删除的数据之外,全部清除。
refreshCache(tableCache, key, null);
}
}
public void process(Insert st, List<Object> list) {
if (st.getTable() instanceof Table) {
Table t = (Table) st.getTable();
Map<KeyDimension, DimCache> tableCache = this.cache.get(t.getName().toUpperCase());
if (tableCache == null)
return;
AbstractMetadata meta = MetaHolder.lookup(t.getSchemaName(), t.getName());
if (meta == null) {
refreshCacheExcept(tableCache, null);
} else {
KeyDimension key = meta.getPKDimension(profile);
refreshCacheExcept(tableCache, key);
}
}
}
public void process(Update st, List<Object> list) {
if (st.getTable() instanceof Table) {
Table t = (Table) st.getTable();
Map<KeyDimension, DimCache> tableCache = this.cache.get(t.getName().toUpperCase());
if (tableCache == null)
return;
AbstractMetadata meta = MetaHolder.lookup(t.getSchemaName(), t.getName());
if (meta == null) {
refreshCacheExcept(tableCache, null);
return;
}
KeyDimension dim = new KeyDimension(t, st.getWhere(), null);
final AtomicInteger count = new AtomicInteger();
st.getWhere().accept(new VisitorAdapter() {
@Override
public void visit(JdbcParameter jdbcParameter) {
count.incrementAndGet();
}
@Override
public void visit(JpqlParameter parameter) {
count.incrementAndGet();
}
});
CacheKey key = new SqlCacheKey(dim, list.subList(list.size() - count.get(), list.size()));
refreshCache(tableCache, key, null);
}
}
@SuppressWarnings("unchecked")
private List<Serializable> toPrimaryKey(Object primaryKey) {
if (primaryKey instanceof List) {
return (List<Serializable>) primaryKey;
} else {
return Arrays.asList((Serializable) primaryKey);
}
}
public long getHitCount() {
return hit.get();
}
public long getMissCount() {
return miss.get();
}
@Override
public <T> T unwrap(Class<T> cls) {
return (T)this;
}
}