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.
com.jeesuite.mybatis.plugin.cache.CacheHandler Maven / Gradle / Ivy
package com.jeesuite.mybatis.plugin.cache;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.result.DefaultResultHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jeesuite.mybatis.MybatisConfigs;
import com.jeesuite.mybatis.core.BaseEntity;
import com.jeesuite.mybatis.core.InterceptorHandler;
import com.jeesuite.mybatis.crud.CrudMethodDefine;
import com.jeesuite.mybatis.crud.builder.SqlTemplate;
import com.jeesuite.mybatis.crud.name.DefaultCrudMethodDefine;
import com.jeesuite.mybatis.crud.name.Mapper3CrudMethodDefine;
import com.jeesuite.mybatis.exception.MybatisHanlerInitException;
import com.jeesuite.mybatis.kit.CacheKeyUtils;
import com.jeesuite.mybatis.kit.ReflectUtils;
import com.jeesuite.mybatis.parser.EntityInfo;
import com.jeesuite.mybatis.parser.MybatisMapperParser;
import com.jeesuite.mybatis.plugin.JeesuiteMybatisInterceptor;
import com.jeesuite.mybatis.plugin.cache.annotation.Cache;
import com.jeesuite.mybatis.plugin.cache.annotation.CacheEvictCascade;
import com.jeesuite.mybatis.plugin.cache.provider.DefaultCacheProvider;
import com.jeesuite.spring.InstanceFactory;
/**
* 自动缓存拦截处理
* @description
* @author vakin
* @date 2015年12月7日
* @Copyright (c) 2015, jwww
*/
public class CacheHandler implements InterceptorHandler {
private static final String TK_MAPPER_EXAMPLE_CLASS_NAME = "tk.mybatis.mapper.entity.Example";
protected static final Logger logger = LoggerFactory.getLogger(CacheHandler.class);
public static final String NAME = "cache";
private static final String PARSE_SQL_ERROR_DEFAULT = "select 'error'";
public final static long IN_1MINS = 60;
public final static long IN_1HOUR = 60 * 60;
public static long defaultCacheExpire = IN_1HOUR;
private static final String STR_PARAM = "param";
private static final String STR_LIST = "list";
private static final String STR_COLLECTION = "collection";
private static final String ID_CACHEKEY_JOIN = ".id:";
private static final String WHERE_REGEX = "(w|W)(here|HERE)";
private static final String QUERY_IDS_SUFFIX = "_ralateIds";
protected static final String SPLIT_PONIT = ".";
public static final String GROUPKEY_SUFFIX = "~keys";
private boolean dynamicCacheTime = false;
private boolean nullValueCache = false;
//null缓存占位符(避免频繁查询不存在对象造成缓存穿透导致频繁查询db)
public static final String NULL_PLACEHOLDER = "~null";
//需要缓存的所有mapper
private static List cacheEnableMappers = new ArrayList<>();
private static Map mapperNameRalateEntityNames = new HashMap<>();
/**
* 更新方法关联的缓存组
*/
private static Map> cacheEvictCascades = new HashMap<>();
private static Map> queryCacheMethods = new HashMap<>();
private static Map updateCacheMethods = new HashMap<>();
private static List groupKeys = new ArrayList<>();
//记录当前线程写入的所有缓存key
private static ThreadLocal> TransactionWriteCacheKeys = new ThreadLocal<>();
protected static CacheProvider cacheProvider;
private CrudMethodDefine methodDefine;
private ScheduledExecutorService clearExpiredGroupKeysTimer;
public void setCacheProvider(CacheProvider cacheProvider) {
CacheHandler.cacheProvider = cacheProvider;
}
private static CacheProvider getCacheProvider() {
if(cacheProvider == null){
synchronized (CacheHandler.class) {
if(cacheProvider == null){
cacheProvider = InstanceFactory.getInstance(CacheProvider.class);
}
if(cacheProvider == null){
cacheProvider = new DefaultCacheProvider();
}
logger.info("Initializing cacheProvider use:{} ",cacheProvider.getClass().getName());
}
}
return cacheProvider;
}
@Override
public Object onInterceptor(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement mt = (MappedStatement)args[0];
if(mt.getSqlCommandType().equals(SqlCommandType.SELECT)){
//按主键查询
QueryMethodCache cacheInfo = getQueryMethodCache(mt.getId());
if(cacheInfo == null)return null;
final String cacheKey = genarateQueryCacheKey(cacheInfo.keyPattern, args[1]);
Object cacheObject = null;
boolean nullPlaceholder = false;
//按主键查询以及标记非引用关系的缓存直接读取缓存
if(cacheInfo.isSecondQueryById() == false){
//从缓存读取
cacheObject = getCacheProvider().get(cacheKey);
nullPlaceholder = nullValueCache && NULL_PLACEHOLDER.equals(cacheObject);
if(nullPlaceholder){
logger.debug("_autocache_ method[{}] find NULL_PLACEHOLDER result from cacheKey:{}",mt.getId(),cacheKey);
}else if(cacheObject != null){
logger.debug("_autocache_ method[{}] find result from cacheKey:{}",mt.getId(),cacheKey);
}
}else{
//新根据缓存KEY找到与按ID缓存的KEY
String refCacheKey = nullValueCache ? getCacheProvider().get(cacheKey) : getCacheProvider().getStr(cacheKey);
if(refCacheKey != null){
if(nullPlaceholder = (nullValueCache && NULL_PLACEHOLDER.equals(refCacheKey))){
cacheObject = NULL_PLACEHOLDER;
}else{
cacheObject = getCacheProvider().get(refCacheKey);
if(cacheObject != null && logger.isDebugEnabled())logger.debug("_autocache_ method[{}] find result from cacheKey:{} ,ref by:{}",mt.getId(),refCacheKey,cacheKey);
}
}
}
if(nullPlaceholder){
cacheObject = new ArrayList<>();
}else if(cacheObject != null && !(cacheObject instanceof Collection)){
cacheObject = new ArrayList<>(Arrays.asList(cacheObject));
}
return cacheObject;
//非按主键删除的方法需求先行查询出来并删除主键缓存
}else if(mt.getSqlCommandType().equals(SqlCommandType.DELETE) && !updateCacheMethods.containsKey(mt.getId())){
String mapperNameSpace = mt.getId().substring(0, mt.getId().lastIndexOf(SPLIT_PONIT));
Executor executor = (Executor) invocation.getTarget();
removeCacheByUpdateConditon(executor, mt, mapperNameSpace, args);
}
return null;
}
@SuppressWarnings("rawtypes")
@Override
public void onFinished(Invocation invocation,Object result) {
Object[] args = invocation.getArgs();
MappedStatement mt = (MappedStatement)args[0];
String mapperNameSpace = mt.getId().substring(0, mt.getId().lastIndexOf(SPLIT_PONIT));
QueryMethodCache cacheInfo = null;
if(mt.getSqlCommandType().equals(SqlCommandType.SELECT)){
if(result == null)return;
if((cacheInfo = getQueryMethodCache(mt.getId())) == null)return;
final String cacheKey = genarateQueryCacheKey(cacheInfo.keyPattern, args[1]);
if(result instanceof List){
List list = (List)result;
if(list.isEmpty()){
if(nullValueCache){
getCacheProvider().set(cacheKey,NULL_PLACEHOLDER, IN_1MINS);
}
return;
}
result = cacheInfo.collectionResult ? result : list.get(0);
}
//按主键查询以及标记非引用关系的缓存直接读取缓存
if(cacheInfo.isSecondQueryById() == false){
if(getCacheProvider().set(cacheKey,result, cacheInfo.getExpire())){
if(logger.isDebugEnabled())logger.debug("_autocache_ method[{}] put result to cache,cacheKey:{}",mt.getId(),cacheKey);
}
//结果为集合的情况,增加key到cacheGroup
if(cacheInfo.groupRalated){
getCacheProvider().putGroup(cacheInfo.cacheGroupKey, cacheKey,cacheInfo.getExpire());
logger.debug("_autocache_ method[{}] add key:[{}] to group key:[{}]",mt.getId(),cacheInfo.cacheGroupKey, cacheKey);
}else{
//
cacheUniqueSelectRef(result, mt, cacheKey);
}
}else{
//之前没有按主键的缓存,增加按主键缓存
String idCacheKey = genarateQueryCacheKey(getQueryByPkMethodCache(mt.getId()).keyPattern,result);
if(idCacheKey != null && cacheKey != null){
if(!getCacheProvider().exists(idCacheKey)){
//缓存idkey->实体
getCacheProvider().set(idCacheKey,result, cacheInfo.getExpire());
}
//缓存fieldkey->idkey
cacheFieldRefKey(cacheKey,idCacheKey, cacheInfo.getExpire());
if(logger.isDebugEnabled())logger.debug("_autocache_ method[{}] put result to cache,cacheKey:{},and add ref cacheKey:{}",mt.getId(),idCacheKey,cacheKey);
}
}
}else{
if(!cacheEnableMappers.contains(mapperNameSpace))return;
//返回0,未更新成功
if(result != null && ((int)result) == 0)return;
boolean insertAction = mt.getSqlCommandType().equals(SqlCommandType.INSERT);
boolean updateAction = mt.getSqlCommandType().equals(SqlCommandType.UPDATE);
boolean deleteAcrion = mt.getSqlCommandType().equals(SqlCommandType.DELETE);
if(updateCacheMethods.containsKey(mt.getId())){
String idCacheKey = null;
UpdateByPkMethodCache updateMethodCache = updateCacheMethods.get(mt.getId());
if(deleteAcrion){
idCacheKey = genarateQueryCacheKey(updateMethodCache.keyPattern,args[1]);
getCacheProvider().remove(idCacheKey);
if(logger.isDebugEnabled())logger.debug("_autocache_ method[{}] remove cacheKey:{} from cache",mt.getId(),idCacheKey);
}else{
idCacheKey = genarateQueryCacheKey(updateMethodCache.keyPattern,args[1]);
if(insertAction || updateAction){
if(result != null){
QueryMethodCache queryByPkMethodCache = getQueryByPkMethodCache(mt.getId());
getCacheProvider().set(idCacheKey,args[1], queryByPkMethodCache.getExpire());
if(logger.isDebugEnabled())logger.debug("_autocache_ method[{}] update cacheKey:{}",mt.getId(),idCacheKey);
//插入其他唯一字段引用
if(insertAction)cacheUniqueSelectRef(args[1], mt, idCacheKey);
//
addCurrentThreadCacheKey(idCacheKey);
}
}
}
}else{//更新的情况,需要按条件动态查询所有记录,然后更新主键缓存
if(updateAction){
Executor executor = (Executor) invocation.getTarget();
removeCacheByUpdateConditon(executor, mt, mapperNameSpace, args);
}
}
//删除同一cachegroup关联缓存
removeCacheByGroup(mt.getId(), mapperNameSpace,false);
}
}
/**
* 按更新的查询条件更新缓存
* @param executor
* @param mt
* @param mapperNameSpace
* @param args
*/
private void removeCacheByUpdateConditon(Executor executor, MappedStatement mt, String mapperNameSpace,
Object[] args) {
try {
Object parameterObject = args[1];
if(parameterObject != null && parameterObject.getClass().getName().equals(TK_MAPPER_EXAMPLE_CLASS_NAME)){
//清除group下所有缓存
removeCacheByGroup(mt.getId(), mapperNameSpace,true);
logger.warn("[tk.mybatis.mapper.entity.Example] Not recommended for use with [@Cache],may be caching cleanup failure",TK_MAPPER_EXAMPLE_CLASS_NAME,mapperNameSpace);
return;
}
EntityInfo entityInfo = MybatisMapperParser.getEntityInfoByMapper(mapperNameSpace);
MappedStatement statement = getQueryIdsMappedStatementForUpdateCache(mt,entityInfo);
if(statement == null)return ;
String querySql = statement.getSqlSource().getBoundSql(parameterObject).getSql();
List> idsResult = null;
if(PARSE_SQL_ERROR_DEFAULT.equals(querySql)){
BoundSql boundSql = mt.getBoundSql(parameterObject);
querySql = "select "+entityInfo.getIdColumn()+" from "+entityInfo.getTableName()+" WHERE " + boundSql.getSql().split(WHERE_REGEX)[1];
BoundSql queryBoundSql = new BoundSql(statement.getConfiguration(), querySql, boundSql.getParameterMappings(), parameterObject);
idsResult = executor.query(statement, parameterObject, RowBounds.DEFAULT, new DefaultResultHandler(),null,queryBoundSql);
}else{
idsResult = executor.query(statement, parameterObject, RowBounds.DEFAULT, null);
}
if(idsResult != null && !idsResult.isEmpty()){
for (Object id : idsResult) {
String cacheKey = entityInfo.getEntityClass().getSimpleName() + ID_CACHEKEY_JOIN + id.toString();
getCacheProvider().remove(cacheKey);
}
if(logger.isDebugEnabled())logger.debug("_autocache_ update Method[{}] executed,remove ralate cache {}.id:[{}]",mt.getId(),entityInfo.getEntityClass().getSimpleName(),idsResult);
}
} catch (Exception e) {
//清除group下所有缓存
removeCacheByGroup(mt.getId(), mapperNameSpace,true);
logger.error("_autocache_ removecache_by_update [{}] error,force clean all group cache",mt.getId());
}
}
private MappedStatement getQueryIdsMappedStatementForUpdateCache(MappedStatement mt,EntityInfo entityInfo) {
String msId = mt.getId() + QUERY_IDS_SUFFIX;
MappedStatement statement = null;
Configuration configuration = mt.getConfiguration();
try {
statement = configuration.getMappedStatement(msId);
if(statement != null)return statement;
} catch (Exception e) {}
synchronized (configuration) {
if(configuration.hasStatement(msId))return configuration.getMappedStatement(msId);
String sql = entityInfo.getMapperSqls().get(mt.getId());
if(StringUtils.isNotBlank(sql)){
if(!sql.toLowerCase().contains(entityInfo.getTableName().toLowerCase())){
return null;
}
sql = "select "+entityInfo.getIdColumn()+" from "+entityInfo.getTableName()+" WHERE " + sql.split(WHERE_REGEX)[1];
sql = String.format(SqlTemplate.SCRIPT_TEMAPLATE, sql);
}else{
sql = PARSE_SQL_ERROR_DEFAULT;
}
SqlSource sqlSource = configuration.getDefaultScriptingLanguageInstance().createSqlSource(configuration, sql, Object.class);
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, msId, sqlSource,SqlCommandType.SELECT);
statementBuilder.resource(mt.getResource());
statementBuilder.fetchSize(mt.getFetchSize());
statementBuilder.statementType(mt.getStatementType());
statementBuilder.parameterMap(mt.getParameterMap());
statement = statementBuilder.build();
List resultMaps = new ArrayList();
String id = msId + "-Inline";
ResultMap.Builder builder = new ResultMap.Builder(configuration, id, entityInfo.getIdType(), new ArrayList(), true);
resultMaps.add(builder.build());
MetaObject metaObject = SystemMetaObject.forObject(statement);
metaObject.setValue("resultMaps", Collections.unmodifiableList(resultMaps));
configuration.addMappedStatement(statement);
return statement;
}
}
/**
* 删除缓存组
* @param mt
* @param mapperNameSpace
* @param removePkCache 是否同时删除按主键的缓存
*/
private void removeCacheByGroup(String msId, String mapperNameSpace,boolean removePkCache) {
//删除cachegroup关联缓存
String entityName = mapperNameRalateEntityNames.get(mapperNameSpace);
getCacheProvider().clearGroup(entityName,removePkCache);
logger.debug("_autocache_ method[{}] remove cache Group:{}",msId,entityName);
//关联缓存
if(cacheEvictCascades.containsKey(msId)){
String cacheGroup;
for (String entity : cacheEvictCascades.get(msId)) {
cacheGroup = entity + GROUPKEY_SUFFIX;
getCacheProvider().clearExpiredGroupKeys(entity + GROUPKEY_SUFFIX);
logger.debug("_autocache_ method[{}] remove Cascade cache Group:[{}]",msId,cacheGroup);
}
}
}
/**
* 缓存其他唯一结果查询方法和主键缓存的引用
* @param object
* @param mt
* @param cacheKey
*/
private void cacheUniqueSelectRef(Object object, MappedStatement mt, String cacheKey) {
Collection mcs = queryCacheMethods.get(mt.getId().substring(0, mt.getId().lastIndexOf(SPLIT_PONIT))).values();
outter:for (QueryMethodCache methodCache : mcs) {
if(methodCache.isPk || methodCache.groupRalated)continue;
try {
Object[] cacheFieldValues = new Object[methodCache.fieldNames.length];
for (int i = 0; i < cacheFieldValues.length; i++) {
if(methodCache.fieldNames[i] == null)break outter;
cacheFieldValues[i] = ReflectUtils.getObjectValue(object, methodCache.fieldNames[i]);
if(cacheFieldValues[i] == null)continue outter;
}
String fieldCacheKey = genarateQueryCacheKey(methodCache.keyPattern , cacheFieldValues);
cacheFieldRefKey(fieldCacheKey,cacheKey, methodCache.getExpire());
if(logger.isDebugEnabled())logger.debug("_autocache_ method[{}] add ref cacheKey:{}",mt.getId(),fieldCacheKey);
} catch (Exception e) {
logger.warn("cacheUniqueSelectRef:"+cacheKey,e);
}
}
}
/**
* 缓存字段查询到idkey
* @param fieldCacheKey
* @param idCacheKey
* @param expired
*/
private void cacheFieldRefKey(String fieldCacheKey,String idCacheKey,long expired){
if(nullValueCache){
getCacheProvider().set(fieldCacheKey, idCacheKey, expired);
}else{
getCacheProvider().setStr(fieldCacheKey, idCacheKey, expired);
}
}
/**
* 生成查询缓存key
* @param cacheInfo
* @param param
* @return
*/
@SuppressWarnings("unchecked")
private String genarateQueryCacheKey(String keyPattern,Object param){
if(param instanceof Map){
Map map = (Map) param;
Object[] args = new String[map.size()/2];
if(map.containsKey(STR_COLLECTION) || map.containsKey(STR_LIST)){
args[0] = CacheKeyUtils.toString(map.containsKey(STR_COLLECTION) ? map.get(STR_COLLECTION) : map.get(STR_LIST));
}else{
for (int i = 0; i < args.length; i++) {
args[i] = CacheKeyUtils.toString(map.get(STR_PARAM + (i+1)));
}
}
return String.format(keyPattern, args);
}else if(param instanceof BaseEntity){
Serializable id = ((BaseEntity)param).getId();
if(id != null && !"0".equals(id.toString())){
return String.format(keyPattern, ((BaseEntity)param).getId());
}else{
return String.format(keyPattern, CacheKeyUtils.toString(param));
}
}else if(param instanceof Object[]){
return String.format(keyPattern, (Object[])param);
}else{
return param == null ? keyPattern : String.format(keyPattern, CacheKeyUtils.toString(param));
}
}
private QueryMethodCache getQueryMethodCache(String mtId){
String key1 = mtId.substring(0, mtId.lastIndexOf(SPLIT_PONIT));
if(queryCacheMethods.containsKey(key1)){
return queryCacheMethods.get(key1).get(mtId);
}
return null;
}
private QueryMethodCache getQueryByPkMethodCache(String mtId){
mtId = mtId.substring(0, mtId.lastIndexOf(SPLIT_PONIT));
if(queryCacheMethods.containsKey(mtId)){
return queryCacheMethods.get(mtId).get(mtId + "." + methodDefine.selectName());
}
return null;
}
@Override
public void start(JeesuiteMybatisInterceptor context) {
nullValueCache = MybatisConfigs.getBoolean(context.getGroupName(), MybatisConfigs.CACHE_NULL_VALUE, false);
dynamicCacheTime = MybatisConfigs.getBoolean(context.getGroupName(), MybatisConfigs.CACHE_DYNAMIC_EXPIRE, false);
defaultCacheExpire = Long.parseLong(MybatisConfigs.getProperty(context.getGroupName(), MybatisConfigs.CACHE_EXPIRE_SECONDS, String.valueOf(IN_1HOUR)));
String crudDriver = MybatisConfigs.getCrudDriver(context.getGroupName());
if("mapper3".equalsIgnoreCase(crudDriver)){
methodDefine = new Mapper3CrudMethodDefine();
}else{
methodDefine = new DefaultCrudMethodDefine();
}
logger.info("crudDriver use:{},nullValueCache:{},defaultCacheExpireSeconds:{},dynamicCacheTime:{}",crudDriver,nullValueCache,defaultCacheExpire,dynamicCacheTime);
List entityInfos = MybatisMapperParser.getEntityInfos(context.getGroupName());
Class baseEntityClass = BaseEntity.class;
QueryMethodCache methodCache = null;
for (EntityInfo ei : entityInfos) {
if(!baseEntityClass.isAssignableFrom(ei.getEntityClass())){
logger.warn("[{}] not extends from [{}],ignore register auto cache!!!!",ei.getEntityClass().getName(),baseEntityClass.getName());
continue;
}
Class> mapperClass = ei.getMapperClass();
//按主键查询方法定义
QueryMethodCache queryByPKMethod = generateQueryByPKMethod(mapperClass, ei.getEntityClass());
if(queryByPKMethod == null)continue;
boolean entityWithAnnotation = ei.getEntityClass().isAnnotationPresent(Cache.class);
Cache annotationCache = null;
String keyPatternForPK = queryByPKMethod.keyPattern;
Map tmpMap = new HashMap<>();
//接口定义的自动缓存方法
List methods = new ArrayList<>(Arrays.asList(mapperClass.getDeclaredMethods()));
Class>[] interfaces = mapperClass.getInterfaces();
if(interfaces != null){
for (Class> superClass : interfaces) {
methods.addAll(Arrays.asList(superClass.getDeclaredMethods()));
}
}
for (Method method : methods) {
String fullMethodName = mapperClass.getName() + SPLIT_PONIT + method.getName();
if(method.isAnnotationPresent(Cache.class)){
annotationCache = method.getAnnotation(Cache.class);
if(tmpMap.containsKey(fullMethodName))continue;
methodCache = generateQueryMethodCacheByMethod(ei, method);
methodCache.setExpire(annotationCache.expire());
tmpMap.put(fullMethodName, methodCache);
logger.info("解析查询方法{}自动缓存配置 ok,keyPattern:[{}]",methodCache.methodName,methodCache.keyPattern);
}else if(method.isAnnotationPresent(CacheEvictCascade.class)){
CacheEvictCascade cascade = method.getAnnotation(CacheEvictCascade.class);
if(cascade.cascadeEntities().length > 0){
List entityNames = new ArrayList<>();
for (Class> clazz : cascade.cascadeEntities()) {
entityNames.add(clazz.getSimpleName());
}
cacheEvictCascades.put(fullMethodName, entityNames);
logger.info("解析查询方法{}自动关联更新缓存配置 ok,cascadeEntities:[{}]",fullMethodName,entityNames);
}
}
}
//无任何需要自动缓存的方法
if(entityWithAnnotation == false && tmpMap.isEmpty()){
continue;
}
//selectAll
QueryMethodCache selectAllMethod = generateSelectAllMethod(mapperClass, ei.getEntityClass());
tmpMap.put(selectAllMethod.methodName, selectAllMethod);
//
if(entityWithAnnotation){
queryByPKMethod.setExpire(ei.getEntityClass().getAnnotation(Cache.class).expire());
selectAllMethod.setExpire(ei.getEntityClass().getAnnotation(Cache.class).expire());
}
//缓存需要自动缓存的mapper
cacheEnableMappers.add(ei.getMapperClass().getName());
//
mapperNameRalateEntityNames.put(ei.getMapperClass().getName(), ei.getEntityClass().getSimpleName());
//主键查询方法
tmpMap.put(queryByPKMethod.methodName, queryByPKMethod);
logger.info("解析查询方法{}自动缓存配置 ok,keyPattern:[{}]",queryByPKMethod.methodName,queryByPKMethod.keyPattern);
queryCacheMethods.put(mapperClass.getName(), tmpMap);
//更新缓存方法
generateUpdateByPkCacheMethod(mapperClass, ei.getEntityClass(), keyPatternForPK);
groupKeys.add(queryByPKMethod.cacheGroupKey);
}
//
if(queryCacheMethods.isEmpty())return;
registerClearExpiredGroupKeyTask();
}
private void registerClearExpiredGroupKeyTask(){
clearExpiredGroupKeysTimer = Executors.newScheduledThreadPool(1);
clearExpiredGroupKeysTimer.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
for (String key : groupKeys) {
try {
getCacheProvider().clearExpiredGroupKeys(key);
} catch (Exception e) {
logger.warn("_autocache_ clearExpiredGroupKeys for {} error!!",key);
}
}
}
}, 5, 60, TimeUnit.MINUTES);
}
/**
* 生成按主键查询缓存定义
* @param mapperClass
* @param entityClass
* @return
*/
private QueryMethodCache generateQueryByPKMethod(Class> mapperClass,Class> entityClass){
QueryMethodCache methodCache = null;
Field[] fields = entityClass.getDeclaredFields();
//主键key前缀
for (Field field : fields) {
if(field.isAnnotationPresent(Id.class)){
methodCache = new QueryMethodCache();
methodCache.isPk = true;
methodCache.collectionResult = false;
methodCache.keyPattern = entityClass.getSimpleName() + ".id:%s";
methodCache.methodName = mapperClass.getName() + "." + methodDefine.selectName();
methodCache.cacheGroupKey = entityClass.getSimpleName() + GROUPKEY_SUFFIX;
}
}
return methodCache;
}
private QueryMethodCache generateSelectAllMethod(Class> mapperClass,Class> entityClass){
QueryMethodCache methodCache = new QueryMethodCache();
methodCache.cacheGroupKey = entityClass.getSimpleName() + GROUPKEY_SUFFIX;
methodCache.methodName = mapperClass.getName() + "." + methodDefine.selectAllName();
methodCache.keyPattern = entityClass.getSimpleName() + ".all";
methodCache.isPk = false;
methodCache.collectionResult = true;
methodCache.groupRalated = true;
return methodCache;
}
private void generateUpdateByPkCacheMethod(Class> mapperClass,Class> entityClass,String keyPatternForPK){
String methodName = null;
//按主键插入
String[] insertNames = methodDefine.insertName().split(",");
for (String name : insertNames) {
methodName = mapperClass.getName() + "." + name;
updateCacheMethods.put(methodName, new UpdateByPkMethodCache(entityClass,methodName, keyPatternForPK, SqlCommandType.INSERT));
}
//按主键更新
String[] updateNames = methodDefine.updateName().split(",");
for (String name : updateNames) {
methodName = mapperClass.getName() + "." + name;
updateCacheMethods.put(methodName, new UpdateByPkMethodCache(entityClass,methodName, keyPatternForPK, SqlCommandType.UPDATE));
}
//按主键删除
methodName = mapperClass.getName() + "." + methodDefine.deleteName();
updateCacheMethods.put(methodName, new UpdateByPkMethodCache(entityClass,methodName, keyPatternForPK, SqlCommandType.DELETE));
}
/**
* 按查询方法生成缓存key前缀
* @param entityClassName
* @param method
* @return
*/
private QueryMethodCache generateQueryMethodCacheByMethod(EntityInfo entityInfo,Method method){
Class> mapperClass = entityInfo.getMapperClass();
Class> entityClass = entityInfo.getEntityClass();
QueryMethodCache methodCache = new QueryMethodCache();
String methodName = mapperClass.getName() + SPLIT_PONIT + method.getName();
methodCache.methodName = methodName;
methodCache.fieldNames = new String[method.getParameterTypes().length];
methodCache.cacheGroupKey = entityClass.getSimpleName() + GROUPKEY_SUFFIX;
//
methodCache.collectionResult = method.getReturnType() == List.class || method.getReturnType() == Set.class;
if(methodCache.collectionResult){
methodCache.groupRalated = true;
}else{
// count等统计查询
methodCache.groupRalated = method.getReturnType().isAnnotationPresent(Table.class) == false;
}
StringBuilder sb = new StringBuilder(entityClass.getSimpleName()).append(SPLIT_PONIT).append(method.getName());
Annotation[][] annotations = method.getParameterAnnotations();
for (int i = 0; i < annotations.length; i++) {
Annotation[] aa = annotations[i];
if(aa.length > 0){
String fieldName = null;
inner:for (Annotation annotation : aa) {
if(annotation.toString().contains(Param.class.getName())){
fieldName = ((Param)annotation).value();
break inner;
}
}
if(!methodCache.groupRalated && MybatisMapperParser.entityHasProperty(entityClass, fieldName)){
methodCache.fieldNames[i] = fieldName;
}
}else{
if(!methodCache.groupRalated){
throw new MybatisHanlerInitException(String.format("unique查询方法[%s] 使用了自动缓存Annotation @Cache,参数必须使用 @Param 绑定属性名称", methodName));
}
}
//
sb.append(i == 0 ? ":" : "_").append("%s");
}
if(!methodCache.groupRalated && methodCache.fieldNames.length == 1 && entityInfo.getIdProperty().equals(methodCache.fieldNames[0])){
throw new MybatisHanlerInitException(String.format("按主键查询方法[%s] 使用了自动缓存Annotation @Cache,请使用默认方法[%s]代替", methodName,methodDefine.selectName()));
}
methodCache.keyPattern = sb.toString();
return methodCache;
}
/**
* 查询缓存方法
*/
private class QueryMethodCache{
public String cacheGroupKey;//缓存组key
public String methodName;
public String keyPattern;
public long expire;//过期时间(秒)
public boolean isPk = false;//主键查询
public boolean collectionResult = false;//查询结果是集合
public boolean groupRalated = false; //是否需要关联group
public String[] fieldNames;//作为查询条件的字段名称
public QueryMethodCache() {}
public void setExpire(long expire) {
this.expire = expire > 0 ? expire : defaultCacheExpire;
}
public long getExpire() {
if(!dynamicCacheTime)return expire;
//缓存时间加上随机,防止造成缓存同时失效雪崩
long rnd = RandomUtils.nextLong(0, IN_1HOUR);
return expire + (rnd > expire ? RandomUtils.nextLong(0, expire) : rnd);
}
/**
* 是否需要通过关联主键二次查询
* @return
*/
public boolean isSecondQueryById(){
return isPk == false && groupRalated == false;
}
}
/**
* 按主键更新(add,update,delete)的方法
*/
private class UpdateByPkMethodCache{
public String keyPattern;
public UpdateByPkMethodCache(Class> entityClass,String methodName, String keyPattern, SqlCommandType sqlCommandType) {
this.keyPattern = keyPattern;
}
}
private void addCurrentThreadCacheKey(String key){
List keys = TransactionWriteCacheKeys.get();
if(keys == null){
keys = new ArrayList<>();
TransactionWriteCacheKeys.set(keys);
}
keys.add(key);
}
/**
* 回滚当前事务写入的缓存
*/
public static void rollbackCache(){
List keys = TransactionWriteCacheKeys.get();
if(keys == null)return;
for (String key : keys) {
getCacheProvider().remove(key);
}
}
@Override
public void close() {
try {
getCacheProvider().close();
} catch (Exception e) {}
try {
clearExpiredGroupKeysTimer.shutdown();
} catch (Exception e) {}
}
@Override
public int interceptorOrder() {
return 0;
}
}