1. 程式人生 > >mybatis通過配置檔案方式整合redis快取,替換mybatis二級快取

mybatis通過配置檔案方式整合redis快取,替換mybatis二級快取

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快取時間之內不會在查詢資料庫

以上是本人實際工作中總結,如有錯誤請大家指正,歡迎大家積極評論。