java 資料庫快取例項
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
最近工作中接觸到了資料庫快取,這裡講解一下公司系統的快取處理方式。
一、自定義快取註解
為了方便區分需要快取的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 "";
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
那怎麼知道哪些加上了快取註解呢,所以需要掃描註解。
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();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
在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) {
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
以上用到了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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
我們可以看到在該介面中一共定義有三個方法,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操作,如果執行該操作的方法有快取註解,則清除快取。