【原創(chuàng)文章畔乙,轉載請注明原文章地址叉弦,謝謝!】
redis作為現(xiàn)在最優(yōu)秀的key-value數(shù)據(jù)庫筋岛,非常適合提供項目的緩存服務。把redis作為mybatis的查詢緩存也是很常見的做法睬塌。在網上發(fā)現(xiàn)N多人是自己做的Cache,其實在mybatis的git下有一個子項目mybatis-redis;這個項目提供了redis作為mybatis查詢緩存的一個實現(xiàn)揩晴,下面先分析一下這個項目的實現(xiàn)原理勋陪,再提出幾個項目的問題:
代碼實現(xiàn)##
該項目和大家普遍實現(xiàn)Mybatis的緩存方案大同小異,無非是實現(xiàn)Cache接口硫兰,并使用jedis操作緩存诅愚;不過該項目在設計細節(jié)上有一些區(qū)別;下面簡要分析一下源碼:
public final class RedisCache implements Cache {
public RedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
pool = new JedisPool(redisConfig, redisConfig.getHost(), redisConfig.getPort(),
redisConfig.getConnectionTimeout(), redisConfig.getSoTimeout(), redisConfig.getPassword(),
redisConfig.getDatabase(), redisConfig.getClientName());
}
RedisCache在mybatis啟動的時候劫映,由MyBatis的CacheBuilder創(chuàng)建违孝,創(chuàng)建的方式很簡單,就是調用RedisCache的帶有String參數(shù)的構造方法泳赋,即RedisCache(String id)雌桑;而在RedisCache的構造方法中,調用了RedisConfigurationBuilder來創(chuàng)建RedisConfig對象祖今,并使用RedisConfig來創(chuàng)建JedisPool校坑。
RedisConfig類繼承了JedisPoolConfig,并提供了host,port等屬性的包裝千诬,簡單看一下RedisConfig的屬性:
public class RedisConfig extends JedisPoolConfig {
private String host = Protocol.DEFAULT_HOST;
private int port = Protocol.DEFAULT_PORT;
private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
private int soTimeout = Protocol.DEFAULT_TIMEOUT;
private String password;
private int database = Protocol.DEFAULT_DATABASE;
private String clientName;
}
RedisConfig對象是由RedisConfigurationBuilder創(chuàng)建的耍目,簡單看下這個類的主要方法:
public RedisConfig parseConfiguration(ClassLoader classLoader) {
Properties config = new Properties();
InputStream input = classLoader.getResourceAsStream(redisPropertiesFilename);
if (input != null) {
try {
config.load(input);
} catch (IOException e) {
throw new RuntimeException(
"An error occurred while reading classpath property '"
+ redisPropertiesFilename
+ "', see nested exceptions", e);
} finally {
try {
input.close();
} catch (IOException e) {
// close quietly
}
}
}
RedisConfig jedisConfig = new RedisConfig();
setConfigProperties(config, jedisConfig);
return jedisConfig;
}
核心的方法就是parseConfiguration方法,該方法從classpath中讀取一個redis.properties文件:
host=localhost
port=6379
connectionTimeout=5000
soTimeout=5000
password=
database=0
clientName=
并將該配置文件中的內容設置到RedisConfig對象中徐绑,并返回邪驮;
接下來,就是RedisCache使用RedisConfig類創(chuàng)建完成JedisPool傲茄;
在RedisCache中實現(xiàn)了一個簡單的模板方法毅访,用來操作Redis:
private Object execute(RedisCallback callback) {
Jedis jedis = pool.getResource();
try {
return callback.doWithRedis(jedis);
} finally {
jedis.close();
}
}
模板接口為RedisCallback,這個接口中就只需要實現(xiàn)了一個doWithRedis方法而已:
public interface RedisCallback {
Object doWithRedis(Jedis jedis);
}
接下來看看Cache中最重要的兩個方法:putObject和getObject烫幕,通過這兩個方法來查看mybatis-redis儲存數(shù)據(jù)的格式:
@Override
public void putObject(final Object key, final Object value) {
execute(new RedisCallback() {
@Override
public Object doWithRedis(Jedis jedis) {
jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));
return null;
}
});
}
@Override
public Object getObject(final Object key) {
return execute(new RedisCallback() {
@Override
public Object doWithRedis(Jedis jedis) {
return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(), key.toString().getBytes()));
}
});
}
可以很清楚的看到俺抽,mybatis-redis在存儲數(shù)據(jù)的時候,是使用的hash結構较曼,把cache的id作為這個hash的key(cache的id在mybatis中就是mapper的namespace)磷斧;這個mapper中的查詢緩存數(shù)據(jù)作為hash的field,需要緩存的內容直接使用SerializeUtil存儲捷犹,SerializeUtil和其他的序列化類差不多弛饭,負責對象的序列化和反序列化;
使用方式##
整個mybatis-redis的關鍵代碼就這些萍歉,通過對代碼的分析侣颂,我們很容易得到mybatis-redis的使用方式:
1,在項目中添加一個redis.properties配置枪孩;
2憔晒,直接在mapper中使用<cache type="org.mybatis.caches.redis.RedisCache" />即可藻肄;
分析##
通過代碼,我們可以看到在實際應用當中可能存在的一些問題:
- 默認情況下拒担,mybatis會為每一個mapper創(chuàng)建一個RedisCache嘹屯,而JedisPool是在RedisCache的構造方法中創(chuàng)建的,這就意味著會為每一個mapper創(chuàng)建一個JedisPool从撼,使用意圖和開銷上面都會有問題州弟;
- 在很多情況下,我們的應用中也會獨立使用到redis低零,這樣也無法讓RedisCache直接使用我們項目中可能已經存在的JedisPool婆翔;并且會造成兩個配置文件(除非我們應用也使用redis.properties);
- RedisCache是使用hash來緩存一個Mapper中的查詢掏婶,所以我們只能通過mybatis的cache配置來控制對象的生存時間啃奴,空閑時間等屬性;而無法獨立的去配置每一個緩存區(qū)域(即每一個hash)气堕;