一、自定义缓存注解
为了方便区分需要缓存的dao方法,对于需要缓存的方法可以加上自定义的注解来标识。
自定义注解如下:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义方法注解
* 注解到对应的Dao查询方法上,可缓存方法返回的结果,下次查询直接用缓存中的数据
* 注解参数为该查询用到的表的名称,多个表名用逗号分隔
* 所注解的方法中的每个参数,必须覆盖hashCode方法,建议使用HashCodeBuilder.reflectionHashCode(this);
*/
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache {
String value() default "";
}
那怎么知道哪些加上了缓存注解呢,所以需要扫描注解。
import java.io.IOException;
import java.lang.reflect.Method;
import javax.annotation.PostConstruct;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import com.newsee.constant.CacheMapping;
/**
* 扫描Cache注解
*
*/
@Component
public final class ScanningCacheAnnotation {
private static final String PACKAGE_NAME = "com.newsee.dao";
private static final String RESOURCE_PATTERN = "/**/*.class";
@PostConstruct
public void scanning() throws IOException, SecurityException,
ClassNotFoundException {
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(PACKAGE_NAME)
+ RESOURCE_PATTERN;
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resourcePatternResolver.getResources(pattern);
for (Resource resource : resources) {
if (resource.isReadable()) {
String className = getClassName(resource.getURL().toString());
Class cls = ScanningCacheAnnotation.class.getClassLoader()
.loadClass((className));
for (Method method : cls.getMethods()) {
Cache cache = method.getAnnotation(Cache.class);
if (cache != null) {//带有Cache注解
String functionName = method.getDeclaringClass()
.getName() + "." + method.getName();
//把带有缓存注解的方法名放入一个静态的Map中,方便以后取用
CacheMapping.addDaoFunctionIsCache(functionName,
Boolean.TRUE);
String tables[] = cache.value().split(",");
for (String table : tables) {
CacheMapping.addTableNameDaoFunctionMap(table,
functionName);
}
}
}
}
}
}
private String getClassName(String resourceUrl) {
String url = resourceUrl.replace("/", ".");
url = url.replace("\\", ".");
url = url.split("com.newsee")[1];
url = url.replace(".class", "");
return "com.newsee" + url.trim();
}
}
在spring 容器初始化 bean 和销毁前所做的操作定义方式有三种:
第一种:通过@PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作
第二种是:通过 在xml中定义init-method 和 destory-method方法
第三种是: 通过bean实现InitializingBean和 DisposableBean接口
以上扫描就是使用了@PostConstruct注解,同时类上有@Component注解自动注册成bean,所以在初始化这个bean时就会执行被@PostConstruct注解的方法。
二、mybatis拦截器和redis缓存
现在已经知道了哪些dao方法被加上了缓存注解了,就可以在方法执行之前去缓存里查一下有没有,没有再去数据库里查并缓存起来。如下:
@Intercepts({
@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }),
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }),
@Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }),
@Signature(type = StatementHandler.class, method = "parameterize", args = { Statement.class }) })
public class MybatisInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
...//这里调用了getFromCacheAndCreateCache
}
/**
* 从缓存取数据,没有则数据库查询并缓存
*
* @param invocation
* @return
* @throws Throwable
*/
private Object getFromCacheAndCreateCache(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = null;
if (invocation.getArgs().length > 1)
parameter = invocation.getArgs()[1];
Boolean isCache = CacheMapping.getDaoFunctionIsCache(mappedStatement.getId());
if (isCache != null && isCache) {
//该方法有缓存注解
String cacheKey = mappedStatement.getId();
if (parameter != null) {
//如果方法有参数,根据参数的hashCode值拼接到Key中
if (parameter instanceof HashMap) {
HashMap map = (HashMap) parameter;
Iterator iter = map.keySet().iterator();
Long hashCode = 0L;
while (iter.hasNext()) {
String key = iter.next().toString();
hashCode += map.get(key).hashCode();
}
cacheKey += hashCode;
} else
cacheKey += parameter.hashCode();
}
ShardedJedis shardedJedis = null;
try {
//获取redis连接,ShardedJedisPool在redisAPI里redis.clients.jedis.ShardedJedisPool
ShardedJedisPool shardedJedisPool = (ShardedJedisPool) SpringContextUtil.getBean("shardedJedisPool");
//获取redis中的缓存数据
shardedJedis = shardedJedisPool.getResource();
//根据key获取value
String resultStr = shardedJedis.get(cacheKey);
PageInfoAuto pageInfoAuto = getPageInfoAuto(parameter);
if (resultStr != null && resultStr.length() > 0) {
//如果有值说明缓存有数据,返回缓存中的值
logger.info("{} 从缓存获取数据", mappedStatement.getId());
if (pageInfoAuto != null) {
String totalCount = shardedJedis.get(cacheKey + "_count");
if (totalCount != null)
pageInfoAuto.setTotalCount(Long.parseLong(totalCount));
}
return SerializableUtils.deserialize(resultStr);
} else {
//没有则去数据库查
Object obj = doMybatis(invocation, mappedStatement, parameter);
//查出来的结果放到缓存中
shardedJedis.set(cacheKey, SerializableUtils.serialize(obj));
if (pageInfoAuto != null && pageInfoAuto.getTotalCount() != null)
shardedJedis.set(cacheKey + "_count", pageInfoAuto.getTotalCount().toString());
return obj;
}
} catch (Exception e) {
logger.error("Dao 缓存异常", e);
return doMybatis(invocation, mappedStatement, parameter);
} finally {
if (shardedJedis != null)
shardedJedis.close();
}
} else
//没有缓存注解的方法直接去数据库查
return doMybatis(invocation, mappedStatement, parameter);
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
以上用到了Mybatis的拦截器,为我们提供了一个Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。我们先来看一下这个接口的定义:
package org.apache.ibatis.plugin;
import java.util.Properties;
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
我们可以看到在该接口中一共定义有三个方法,intercept、plugin和setProperties。plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法,当然也可以调用其他方法。setProperties方法是用于在Mybatis配置文件中指定一些属性的。
定义自己的Interceptor最重要的是要实现plugin方法和intercept方法,在plugin方法中我们可以决定是否要进行拦截进而决定要返回一个什么样的目标对象。而intercept方法就是要进行拦截的时候要执行的方法。
对于plugin方法而言,其实Mybatis已经为我们提供了一个实现。Mybatis中有一个叫做Plugin的类,里面有一个静态方法wrap(Object target,Interceptor interceptor),通过该方法可以决定要返回的对象是目标对象还是对应的代理。
详细关于Mybatis拦截器可以参考http://www.tuicool.com/articles/7bYjUn 通过上面例子中的代码可以看出,缓存是放在redis中的。Redis是一个Key-Value数据库,提供了多种语言的API,使用Java操作Redis需要jedis-2.8.0.jar(或其他版本)。所以就算工程的tomcat关了,缓存依然在,只要redis服务没关就行!
那缓存又要如何清除呢?
对于这点,本系统也是在mybatis拦截器中做的,拦截每一次的insert、update操作,如果执行该操作的方法有缓存注解,则清除缓存。