gu.sql2java.redis.cache.RedisCache Maven / Gradle / Ivy
The newest version!
package gu.sql2java.redis.cache;
import gu.simplemq.Channel;
import gu.simplemq.SimpleLog;
import gu.simplemq.redis.JedisPoolLazy;
import gu.simplemq.redis.RedisFactory;
import gu.simplemq.redis.RedisTable;
import gu.sql2java.BaseBean;
import gu.sql2java.IndexMetaData;
import gu.sql2java.RowMetaData;
import gu.sql2java.TableListener.Adapter;
import gu.sql2java.TableManager;
import gu.sql2java.exception.RuntimeDaoException;
import gu.sql2java.manager.Managers;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static gu.sql2java.redis.cache.RedisCaches.getCacheKeyPrefix;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
/**
* 基于REDIS的数据库表记录缓存
* 实现与数据库内容实时同步
* @author guyadong
*
* @param
*/
public class RedisCache extends Adapter{
private Timer timer;
protected final RedisTable table;
protected final Channel channel;
protected final RowMetaData metaData;
/**
* Java Bean到JSON转换器实例
*/
private Function jsonFormatter;
private int[] jsonFields = new int[0];
private boolean include = false;
private Set columns= Sets.newHashSet();
private String keyName;
/**
* 构造方法
* @param keyPrefix KEY的统一前缀,为{@code null}或空使用默认值{@link RedisCaches#getCacheKeyPrefix() }
* @param beanClass 数据库记录对象
* @param columnName 用作REDIS key的字段名,为{@code null}或空默认使用主键作为REDIS key的字段名
*/
RedisCache(String keyPrefix,Class beanClass, String columnName) {
if(isNullOrEmpty(keyPrefix)){
keyPrefix = getCacheKeyPrefix();
}
metaData = RowMetaData.getMetaData(beanClass);
if(isNullOrEmpty(columnName)){
checkArgument(1 == metaData.primaryKeyCount,"privary key must only one");
keyName = metaData.primaryKeyNames[0];
}else if(Iterables.tryFind(Arrays.asList(metaData.primaryKeyNames[0]),columnName::equals).isPresent()){
/** columnName为主键字段名时,只能有一个主键 */
checkArgument(1 == metaData.primaryKeyCount,"privary key must only one");
keyName = columnName;
}else {
Optional opt = Iterables.tryFind(metaData.getUniqueIndices().values(),m->{
return m.readableName.equals(columnName)
|| m.name.equals(columnName)
|| Iterables.tryFind(m.columns,columnName::equals).isPresent();
});
checkArgument(opt.isPresent() && 1 == opt.get().columns.size() ,
"INVALID columnName %s,only one column and UNIQUE index required",columnName);
keyName = opt.get().columns.get(0);
}
String channelName = RedisCaches.channelNameOf(keyPrefix,beanClass.getSimpleName(), keyName);
channel = new Channel<>(channelName, JSONObject.class);
table = RedisFactory.getTable(channel, JedisPoolLazy.getDefaultInstance());
table.setKeyHelper(b->String.valueOf(b.get(keyName)));
jsonFormatter = this::formatAsJson;
}
/**
* 构造方法
* @param beanClass 数据库记录对象
* @param columnName 用作REDIS key的字段名,为{@code null}或空默认使用主键作为REDIS key的字段名
*/
RedisCache(Class beanClass, String columnName) {
this(null,beanClass,columnName);
}
private JSONObject asJson(V bean){
return jsonFormatter.apply(bean);
}
/**
* 设置Java Bean到JSON转换器实例,不指定使用默认转换所有字段的实例
* @param jsonFormater
* @return 转换的JSON对象
*/
public RedisCache setJsonFormater(Function jsonFormater) {
if(null != jsonFormater){
this.jsonFormatter = jsonFormater;
}
return this;
}
/**
* 将所有数据库加载到REDIS
* @param reg 是否注册侦听器
* @return 当前对象
*/
private void loadAllIntoCache(boolean reg){
TableManager manager = Managers.managerOf(metaData.tablename);
SimpleLog.log("load all rows of {} INTO {} CACHE",metaData.tablename,keyName);
manager.loadAll(bean->{
table.set(asJson(bean), false);
});
if(reg) {
manager.registerListener(this);
}
}
/**
* 启动缓存
* 将所有数据库加载到REDIS,并注册侦听器
* @return 当前对象
*/
public RedisCache start(){
int count = table.clear();
/** 先删除REDIS上已经有记录,再重新从数据库加载所有记录 */
SimpleLog.log("clean {} CACHE {} rows",keyName,count);
loadAllIntoCache(true);
return this;
}
@Override
public final void afterInsert(V bean) throws RuntimeDaoException {
table.set(asJson(bean), false);
}
@Override
public final void afterUpdate(V bean) throws RuntimeDaoException {
table.set(asJson(bean), false);
}
@Override
public final void afterDelete(V bean) throws RuntimeDaoException {
table.removeValues(asJson(bean));
}
/**
* 配置数据库对象转换为JSON时类型为JSON的字段ID
* @param jsonFields JSON字段,为{@code null}忽略
* @return 当前对象
*/
public RedisCache jsonFields(int... jsonFields) {
if(null != jsonFields){
this.jsonFields = jsonFields;
}
return this;
}
/**
* 配置数据库对象转换为JSON时类型为JSON的字段名列表
* @param jsonFields JSON字段,为{@code null}忽略
* @return 当前对象
*/
public RedisCache jsonFields(String... jsonFields) {
if(null != jsonFields){
List ids = Lists.transform(Arrays.asList(jsonFields),metaData::columnIDOf);
ids = Lists.newArrayList(Iterables.filter(ids, id->id>=0));
int[] array = Ints.toArray(ids);
this.jsonFields = array;
}
return this;
}
/**
* 配置数据库对象转换为JSON时类型为JSON的字段名列表
* @param jsonFields JSON字段,为{@code null}忽略
* @return 当前对象
*/
public RedisCache jsonFields(IterablejsonFields) {
if(null != jsonFields){
Iterable ids = Iterables.transform(jsonFields,metaData::columnIDOf);
ArrayList list = Lists.newArrayList(Iterables.filter(ids, id->id>=0));
int[] array = Ints.toArray(list);
this.jsonFields = array;
}
return this;
}
/**
* 配置数据库对象转换为JSON时的输出字段
* @param include 为{@code true}时{@code columns}为需要输出的字段白名单,
* 只有在名单中的字段才会被输出,否则为输出字段黑名单,在名单中的字段不会被输出
* @param columns 白名单/黑名单字段名列表
* @return 当前对象
*/
public RedisCache columns(boolean include,Iterable columns) {
this.include = include;
if(null != columns){
this.columns = Sets.newHashSet(Iterables.filter(columns,s->!isNullOrEmpty(s)));
if(include){
this.columns.addAll(Arrays.asList(metaData.primaryKeyNames));
}
}
return this;
}
/**
* 配置数据库对象转换为JSON时的输出字段
* @param include 为{@code true}时{@code columns}为需要输出的字段白名单,
* 只有在名单中的字段才会被输出,否则为输出字段黑名单,在名单中的字段不会被输出
* @param columns 白名单/黑名单字段名列表
* @return 当前对象
*/
public RedisCache columns(boolean include,String ... columns) {
return columns(include,null == columns ? Collections.emptyList():Arrays.asList(columns));
}
/**
* 开启主动更新缓存机制
* 定期执行{@link #loadAllIntoCache(boolean)} 更新缓存数据
* @param period 更新周期
* @param timeUnit 时间单位
* @return 当前对象
* @since 3.20.1
*/
public synchronized RedisCache updatePeriodically(long period,TimeUnit timeUnit){
long periodMills = TimeUnit.MILLISECONDS.convert(period, checkNotNull(timeUnit,"timeUnit is null"));
if(null == timer) {
timer = new Timer("timer-update-cache-"+metaData.tablename,true);
}else {
timer.cancel();
}
timer.schedule(new TimerTask() {
@Override
public void run() {
loadAllIntoCache(false);
}}, periodMills,periodMills);
return this;
}
/**
* 格式化一个数据库记录
* 如果指定了的JSON字段,则String类型JSON字段解析成JSON对象
*
* @param bean 数据库记录对象
* @param jsonFields JSON字段,为{@code null}忽略
* @return JSONObject对象
*/
private JSONObject formatAsJson(V bean){
JSONObject m;
if(null != bean){
m = new JSONObject(bean.asNameValueMap(true,include,columns));
if(null != jsonFields){
Ints.asList(jsonFields).forEach( f ->{
String c = metaData.columnNameOf(f);
if(null != c){
if((include ? columns.contains(c) : !columns.contains(c))){
m.put(c, BaseBeanSupport.asJson(bean, f));
}
}
});
}
}else {
m = new JSONObject();
}
return m;
}
}