redis作為現在最優秀的key-value數據庫,非常適合提供項目的緩存服務。把redis作為mybatis的查詢緩存也是很常見的做法。在網上發現N多人是自己做的Cache,其實在mybatis的git下有一個子項目mybatis-redis;這個項目提供了redis作為mybatis查詢緩存的一個實現,下面先分析一下這個項目的實現原理,再提出幾個項目的問題:
代碼實現
該項目和大家普遍實現Mybatis的緩存方案大同小異,無非是實現Cache接口,并使用jedis操作緩存;不過該項目在設計細節上有一些區別;下面簡要分析一下源碼:
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創建,創建的方式很簡單,就是調用RedisCache的帶有String參數的構造方法,即RedisCache(String id);而在RedisCache的構造方法中,調用了RedisConfigurationBuilder來創建RedisConfig對象,并使用RedisConfig來創建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創建的,簡單看下這個類的主要方法:
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類創建完成JedisPool;
在RedisCache中實現了一個簡單的模板方法,用來操作Redis:
private Object execute(RedisCallback callback) {
Jedis jedis = pool.getResource();
try {
return callback.doWithRedis(jedis);
} finally {
jedis.close();
}
}
模板接口為RedisCallback,這個接口中就只需要實現了一個doWithRedis方法而已:
public interface RedisCallback {
Object doWithRedis(Jedis jedis);
}
接下來看看Cache中最重要的兩個方法:putObject和getObject,通過這兩個方法來查看mybatis-redis儲存數據的格式:
@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在存儲數據的時候,是使用的hash結構,把cache的id作為這個hash的key(cache的id在mybatis中就是mapper的namespace);這個mapper中的查詢緩存數據作為hash的field,需要緩存的內容直接使用SerializeUtil存儲,SerializeUtil和其他的序列化類差不多,負責對象的序列化和反序列化;
使用方式
整個mybatis-redis的關鍵代碼就這些,通過對代碼的分析,我們很容易得到mybatis-redis的使用方式:
1,在項目中添加一個redis.properties配置;
2,直接在mapper中使用<cache type="org.mybatis.caches.redis.RedisCache" />即可;
分析
通過代碼,我們可以看到在實際應用當中可能存在的一些問題:
1.默認情況下,mybatis會為每一個mapper創建一個RedisCache,而JedisPool是在RedisCache的構造方法中創建的,這就意味著會為每一個mapper創建一個JedisPool,使用意圖和開銷上面都會有問題;
2.在很多情況下,我們的應用中也會獨立使用到redis,這樣也無法讓RedisCache直接使用我們項目中可能已經存在的JedisPool;并且會造成兩個配置文件(除非我們應用也使用redis.properties);
3.RedisCache是使用hash來緩存一個Mapper中的查詢,所以我們只能通過mybatis的cache配置來控制對象的生存時間,空閑時間等屬性;而無法獨立的去配置每一個緩存區域(即每一個hash);
來源:
http://bbs.520it.com/forum.php?mod=viewthread&tid=286&extra=page%3D4