mybatis通过redis取代二级缓存,二级缓存的缺点不再赘述。
mybatis默认缓存是PerpetualCache,可以查看一下它的源码,发现其是Cache接口的实现;那么我们的缓存只要实现该接口即可。
该接口有以下方法需要实现:
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
ReadWriteLock getReadWriteLock();
下面开始实现代码
==============================华丽的分割线========================
1,RedisCache实现类
import org.apache.ibatis.cache.Cache;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
* Cache adapter for Redis.
*
* @author Eduardo Macarron
*/
public final class RedisCache implements Cache {
private static JedisPool pool;
private final ReadWriteLock readWriteLock = new DummyReadWriteLock();
private final String id;
private final Integer expireSeconds;
public RedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
final RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
pool = new JedisPool(redisConfig, redisConfig.getHost(), redisConfig.getPort(), redisConfig.getConnectionTimeout(),
redisConfig.getSoTimeout(), redisConfig.getPassword(), redisConfig.getDatabase(), redisConfig.getClientName());
expireSeconds = redisConfig.getSettings().get(id) * 60;
}
@Override
public void clear() {
execute(new RedisCallback() {
@Override
public Object doWithRedis(Jedis jedis) {
// jedis.del(id.toString());
throw new UnsupportedOperationException("not support redis-cache getsize method.");
// return null;
}
});
}
private Object execute(RedisCallback callback) {
final Jedis jedis = pool.getResource();
try {
return callback.doWithRedis(jedis);
} finally {
jedis.close();
}
}
@Override
public String getId() {
return this.id;
}
@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()));
return SerializeUtil.unserialize(jedis.get(key.toString().getBytes()));
}
});
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
@Override
public int getSize() {
return (Integer) execute(new RedisCallback() {
@Override
public Object doWithRedis(Jedis jedis) {
throw new UnsupportedOperationException("not support redis-cache getsize method.");
}
});
}
@Override
public void putObject(final Object key, final Object value) {
execute(new RedisCallback() {
@Override
public Object doWithRedis(Jedis jedis) {
System.out.println("缓存----------------:" + key);
jedis.set(key.toString().getBytes(), SerializeUtil.serialize(value));
if (expireSeconds > 0) {
jedis.expire(key.toString().getBytes(), expireSeconds);
}
// jedis.hset(id.toString().getBytes(), key.toString().getBytes(),
// SerializeUtil.serialize(value));
return null;
}
});
}
@Override
public Object removeObject(final Object key) {
return execute(new RedisCallback() {
@Override
public Object doWithRedis(Jedis jedis) {
// return jedis.hdel(id.toString(), key.toString());
// return jedis.del(key.toString());
throw new UnsupportedOperationException("not support redis-cache getsize method.");
}
});
}
@Override
public String toString() {
return "Redis {" + id + "}";
}
}
2,
DummyReadWriteLock
类
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
/**
* @author Iwao AVE!
*/
class DummyReadWriteLock implements ReadWriteLock {
static class DummyLock implements Lock {
@Override
public void lock() {
// Not implemented
}
@Override
public void lockInterruptibly() throws InterruptedException {
// Not implemented
}
@Override
public Condition newCondition() {
return null;
}
@Override
public boolean tryLock() {
return true;
}
@Override
public boolean tryLock(long paramLong, TimeUnit paramTimeUnit) throws InterruptedException {
return true;
}
@Override
public void unlock() {
// Not implemented
}
}
private final Lock lock = new DummyLock();
@Override
public Lock readLock() {
return lock;
}
@Override
public Lock writeLock() {
return lock;
}
}
3,RedisCallback 接口
import redis.clients.jedis.Jedis;
public interface RedisCallback {
Object doWithRedis(Jedis jedis);
}
4,RedisConfig 类
import java.util.Hashtable;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;
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;
private final Hashtable<String, Integer> settings = new Hashtable<String, Integer>();
public String getClientName() {
return clientName;
}
public int getConnectionTimeout() {
return connectionTimeout;
}
public int getDatabase() {
return database;
}
public String getHost() {
return host;
}
public String getPassword() {
return password;
}
public int getPort() {
return port;
}
public Hashtable<String, Integer> getSettings() {
return settings;
}
public int getSoTimeout() {
return soTimeout;
}
public void setClientName(String clientName) {
if ("".equals(clientName)) {
clientName = null;
}
this.clientName = clientName;
}
public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public void setDatabase(int database) {
this.database = database;
}
public void setHost(String host) {
if (host == null || "".equals(host)) {
host = Protocol.DEFAULT_HOST;
}
this.host = host;
}
public void setPassword(String password) {
if ("".equals(password)) {
password = null;
}
this.password = password;
}
public void setPort(int port) {
this.port = port;
}
public void setSoTimeout(int soTimeout) {
this.soTimeout = soTimeout;
}
}
5,RedisConfigurationBuilder 类
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import org.apache.ibatis.cache.CacheException;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
/**
* Converter from the Config to a proper {@link RedisConfig}.
*
* @author Eduardo Macarron
*/
final class RedisConfigurationBuilder {
/**
* This class instance.
*/
private static final RedisConfigurationBuilder INSTANCE = new RedisConfigurationBuilder();
private static final String SYSTEM_PROPERTY_REDIS_PROPERTIES_FILENAME = "redis.properties.filename";
private static final String REDIS_RESOURCE = "redis.properties";
/**
* Return this class instance.
*
* @return this class instance.
*/
public static RedisConfigurationBuilder getInstance() {
return INSTANCE;
}
private final String redisPropertiesFilename;
/**
* Hidden constructor, this class can't be instantiated.
*/
private RedisConfigurationBuilder() {
redisPropertiesFilename = System.getProperty(SYSTEM_PROPERTY_REDIS_PROPERTIES_FILENAME, REDIS_RESOURCE);
}
private boolean isInteger(String s) {
return isInteger(s, 10);
}
private boolean isInteger(String s, int radix) {
if (s.isEmpty()) {
return false;
}
for (int i = 0; i < s.length(); i++) {
if (i == 0 && s.charAt(i) == '-') {
if (s.length() == 1) {
return false;
} else {
continue;
}
}
if (Character.digit(s.charAt(i), radix) < 0) {
return false;
}
}
return true;
}
/**
* Parses the Config and builds a new {@link RedisConfig}.
*
* @return the converted {@link RedisConfig}.
*/
public RedisConfig parseConfiguration() {
return parseConfiguration(getClass().getClassLoader());
}
/**
* Parses the Config and builds a new {@link RedisConfig}.
*
* @param the
* {@link ClassLoader} used to load the {@code memcached.properties}
* file in classpath.
* @return the converted {@link RedisConfig}.
*/
public RedisConfig parseConfiguration(ClassLoader classLoader) {
final Properties config = new Properties();
final InputStream input = classLoader.getResourceAsStream(redisPropertiesFilename);
if (input != null) {
try {
config.load(input);
} catch (final IOException e) {
throw new RuntimeException("An error occurred while reading classpath property '" + redisPropertiesFilename
+ "', see nested exceptions", e);
} finally {
try {
input.close();
} catch (final IOException e) {
// close quietly
}
}
}
final RedisConfig jedisConfig = new RedisConfig();
jedisConfig.setHost("localhost");
setConfigProperties(config, jedisConfig);
return jedisConfig;
}
private void setConfigProperties(Properties properties, RedisConfig jedisConfig) {
if (properties != null) {
final MetaObject metaCache = SystemMetaObject.forObject(jedisConfig);
for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
final String name = (String) entry.getKey();
final String value = (String) entry.getValue();
if (metaCache.hasSetter(name)) {
final Class<?> type = metaCache.getSetterType(name);
if (String.class == type) {
metaCache.setValue(name, value);
} else if (int.class == type || Integer.class == type) {
metaCache.setValue(name, Integer.valueOf(value));
} else if (long.class == type || Long.class == type) {
metaCache.setValue(name, Long.valueOf(value));
} else if (short.class == type || Short.class == type) {
metaCache.setValue(name, Short.valueOf(value));
} else if (byte.class == type || Byte.class == type) {
metaCache.setValue(name, Byte.valueOf(value));
} else if (float.class == type || Float.class == type) {
metaCache.setValue(name, Float.valueOf(value));
} else if (boolean.class == type || Boolean.class == type) {
metaCache.setValue(name, Boolean.valueOf(value));
} else if (double.class == type || Double.class == type) {
metaCache.setValue(name, Double.valueOf(value));
} else {
throw new CacheException("Unsupported property type: '" + name + "' of type " + type);
}
} else if (isInteger(value)) {
jedisConfig.getSettings().put(name, Integer.parseInt(value));
}
}
}
}
}
6,SerializeUtil 类
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.apache.ibatis.cache.CacheException;
public final class SerializeUtil {
public static byte[] serialize(Object object) {
ObjectOutputStream oos = null;
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(object);
final byte[] bytes = baos.toByteArray();
return bytes;
} catch (final Exception e) {
throw new CacheException(e);
}
}
public static Object unserialize(byte[] bytes) {
if (bytes == null) {
return null;
}
ByteArrayInputStream bais = null;
try {
bais = new ByteArrayInputStream(bytes);
final ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (final Exception e) {
throw new CacheException(e);
}
}
}
7,配置文件(此处需要Goods实体,不再赘述),配置文件中缓存可以开关,如果某个select不需要缓存,则加上 useCache="false" 属性,默认不写的话值是true
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.yjl.service.dao.GoodsDao">
<cache type="com.yjl.framework.caching.mybatis.RedisCache" />
<resultMap id="BaseResultMap" type="com.yjl.bean.Goods">
<id column="goods_id" property="goodsId" jdbcType="INTEGER" />
<result column="center_goods_id" property="centerGoodsId"
jdbcType="INTEGER" />
<result column="store_id" property="storeId" jdbcType="INTEGER" />
<result column="brand_id" property="brandId" jdbcType="INTEGER" />
</resultMap>
<select id="getGoodsById" resultMap="goodsDetail" parameterType="java.lang.Integer">
select goods_id,center_goods_id,store_id,brand_id from goods where goods_id = #{goodsId,jdbcType=INTEGER} limit 1
</select>
</mapper>
8,redis.properties(此处可以根据具体的namespace设置相应的缓存时间)
host=localhost
port=6379
connectionTimeout=5000
soTimeout=4000
password=
database=0
com.yjl.service.dao.MealDao=5
com.yjl.service.dao.GoodsDao=10
代码部分已经完成,下面开始测试是否缓存
===========================华丽的分割线===========================
下面开始测试
可以看出只有第一次会查询数据库,在redis缓存时间之内不会在查询数据库
以上是本人实际工作中总结,如有错误请大家指正,欢迎大家积极评论。