mybatis二级缓存不能及时同步问题

 本方法没有具体介绍mybatis二级缓存不及时同步的具体问题,只是提供解决方法,关于具体问题分析请看问题分析

    这段时间刚刚接触到mybatis二级缓存,以前只是知道但是没有用过,基本上都是用redis做业务缓存,并没有用mybatis自己的二级缓存。结合上一篇在别的博客看到的,解决二级缓存问题由于关联查询,导致不同命名空间下的缓存不能及时更新的现象,我在我们的项目代码中也发现了这样的问题。这个问题其实mybatis已经给出了建议,就是在mybatis如果涉及到关联查询时候,尽量不要使用它自己带的二级缓存。

下面情请看:人肉在业务代码中强行添加更新缓存的方法,这种方法我觉得,在分模块开发的项目中是很不好的,每个开发人员只开发自己的业务模块,并不完全了解,我当前的更新操作会对别人缓存产生影响,只能靠测试发现数据显示有问题时候才来找,然后人肉添加一行在此处没有任何业务逻辑的更新缓存的代码,反正我觉得很恶心。

下面是我在解决这个问题的方法,我并没有用 https://blog.csdn.net/lcx390549721/article/details/80626639

1.   mapper配置   MemberMapper.xml

        <!-- 清除缓存 -->  
<select id="cleanCache" resultType="int" useCache="false" flushCache="true">
select count(*) as count  from member where 1=2

</select> 

     MemberDao.java

            public int cleanCache();

    MemberService.java

            /**
 * 清除缓存
 * @author 123
 * @date 2018-5-08
 * @return
 */
public int cleanCache(){
return memberDao.cleanCache();

}

2. 配置拦截所有的sql   mybatisConfig.xml

        <plugins>  
                 <plugin interceptor="com.abc.interceptor.SqlLogInterceptor"></plugin>  

        </plugins> 

   SqlLogInterceptor.java  传入sql,启动线程分情况处理

        package com.abc.interceptor.SqlLogInterceptor;
        import java.sql.Date;
        import java.text.DateFormat;
        import java.util.List;
        import java.util.Locale;
        import java.util.Properties;


        import org.apache.ibatis.executor.Executor;
        import org.apache.ibatis.mapping.BoundSql;
        import org.apache.ibatis.mapping.MappedStatement;
        import org.apache.ibatis.mapping.ParameterMapping;
        import org.apache.ibatis.plugin.Interceptor;
        import org.apache.ibatis.plugin.Intercepts;
        import org.apache.ibatis.plugin.Invocation;
        import org.apache.ibatis.plugin.Plugin;
        import org.apache.ibatis.plugin.Signature;
        import org.apache.ibatis.reflection.MetaObject;
        import org.apache.ibatis.session.Configuration;
        import org.apache.ibatis.session.ResultHandler;
        import org.apache.ibatis.session.RowBounds;
        import org.apache.ibatis.type.TypeHandlerRegistry;


        import com.abc.cache.TableHandlerImpl;
        import com.abc.myutil.FileUtil;


@Intercepts({
@Signature(type = Executor.class, method = "update", args = {
MappedStatement.class, Object.class }),
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class }) })
public class SqlLogInterceptor implements Interceptor {


@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation
.getArgs()[0];
Object parameter = null;
if (invocation.getArgs().length > 1) {
parameter = invocation.getArgs()[1];
}


BoundSql boundSql = mappedStatement.getBoundSql(parameter);
Configuration configuration = mappedStatement.getConfiguration();


Object returnValue = null;
// 获取sql语句
String sql = showSql(configuration, boundSql);
FileUtil.writeFile(sql,"d:/src/log.txt"); 
//另起线程处理缓存问题

TableHandlerImpl tablePickerImpl = new TableHandlerImpl(sql);
             tablePickerImpl.start();
      
// 执行结果
returnValue = invocation.proceed();
return returnValue;
}


private String showSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql
.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (parameterMappings.size() > 0 && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration
.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?",
getParameterValue(parameterObject));


} else {
MetaObject metaObject = configuration
.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql
.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
}
}
}
}
return sql;
}


private String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(
DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(obj) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}


}
return value;
}


@Override
public Object plugin(Object target) {
// TODO Auto-generated method stub
return Plugin.wrap(target, this);
}


@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub


}


    }

3.  维护一个线程安全的set ,保存所有发生过关联查询关系的表   TableHandlerImpl .java

package com.abc.cache;
import java.util.List;
public abstract class TableHandler  extends Thread{
    //通过查询SQL获取表和表之间的关系,并将这种关系保存在一个链表中
abstract void genRela(List<String> tableName);
    
    //根据更新语句中对应的表名,去查询和他们有关联的表并进行级联清除缓存
    abstract void cleanCacheCascade(String tableName) throws Exception;
    
    //根据sql获取表名和sql类型,此为解析
    abstract List<String> getTableNameAndTypeFormSql(String sql);
}

    Set<ArrayList<String>> tableRelation = Collections.synchronizedSet(tableRelation1); //维护一个线程安全的Set,需要持久化

package com.abc.cache;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import com.abc.myutil.ServiceUtils;
public class TableHandlerImpl extends TableHandler {


private static Logger logger = LoggerFactory
.getLogger(TableHandlerImpl.class);


// 此信息需要做持久化
public static Set<ArrayList<String>> tableRelation1 = new HashSet<ArrayList<String>>();
public static Set<ArrayList<String>> tableRelation = Collections.synchronizedSet(tableRelation1); // 返回了一个线 程安全的Set
public String sql;
public TableHandlerImpl(String sql) {
this.sql = sql;
}
@Override
public void genRela(List<String> tableNames) {
if(tableNames.size()>2){
tableRelation.add((ArrayList)tableNames);
}
}
@Override
public void cleanCacheCascade(String tableName) {
// 根据表名获取,所有的与此表有关系的表
Iterator<ArrayList<String>> iterator = tableRelation.iterator();
while(iterator.hasNext()) {
ArrayList<String> next = iterator.next();
if (next.contains(tableName)) {
for (Object object : next) {
String tableName1 = (String) object;
if(tableName1.equals(tableName) || tableName1.equals("select")){
continue;
}
try {
// 根据表名获取service  比如你的表名是t_member   返回值就是 MemberService
String genService = ServiceUtils.underline2Camel(
tableName1, true);
WebApplicationContext wac = ContextLoader
.getCurrentWebApplicationContext();
Object bean = wac.getBean(genService);// memberService
Class class1 = bean.getClass();
Method m;

Method[] methods = class1.getMethods();

                                                //通过反射,动态执行清除缓存的方法

for (Method method : methods) {
if (method.getName().equals("cleanCache")) {
method.invoke(bean, null);
logger.info("由于更新了表" + tableName
+ "触发曾经与此表有过关联查询的表" + tableName1
+ "所对应的缓存的刷新");
}
}
} catch (Exception e) {
logger.error(tableName1 + ":清除缓存失败");
;
}
}
}
}
}
@Override
public List<String> getTableNameAndTypeFormSql(String sql) {
List<String> tableNameAndType = new ArrayList<String>();
if(sql!=null){
if (sql.startsWith("select") || sql.startsWith("SELECT")) {
tableNameAndType.add("select");
String[] split = sql.split(" ");
for (int i = 0; i < split.length; i++) {
if(split[i].equals("from")||split[i].equals("FROM")||split[i].equals("JOIN")||split[i].equals("join")){
tableNameAndType.add(split[i+1]!=null?split[i+1]:"");
}
}
return  tableNameAndType;
}else if(sql.startsWith("update") ||sql.startsWith("UPDATE")){
tableNameAndType.add("update");
String[] split = sql.split(" ");
for (int i = 0; i < split.length; i++) {
if(split[i].equals("update")||split[i].equals("UPDATE")){
tableNameAndType.add(split[i+1]!=null?split[i+1]:"");
break;
}
}
return  tableNameAndType;

}else if(sql.startsWith("insert") ||sql.startsWith("INSERT")){
tableNameAndType.add("update");
String[] split = sql.split(" ");
for (int i = 0; i < split.length; i++) {
if(split[i].equals("into")||split[i].equals("INTO")){
tableNameAndType.add(split[i+1]!=null?split[i+1]:"");
break;
}
}
return  tableNameAndType;

}else if(sql.startsWith("delete") ||sql.startsWith("DELETE")){
tableNameAndType.add("update");
String[] split = sql.split(" ");
for (int i = 0; i < split.length; i++) {
if(split[i].equals("from")||split[i].equals("FROM")){

tableNameAndType.add(split[i+1]!=null?split[i+1]:"");

break;

}
}
return  tableNameAndType;
}else{
return null;
}
}
return null;
}

// 被拦截后,开始进行操作
public void doHandler() {
// 先进行解析
List<String> tableNameAndTypeFormSql = getTableNameAndTypeFormSql(sql);
if (tableNameAndTypeFormSql != null
&& tableNameAndTypeFormSql.size() > 0) {
Iterator<String> iterator = tableNameAndTypeFormSql.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next.equals("select")) {
genRela(tableNameAndTypeFormSql);
}
if (next.equals("update")) {
cleanCacheCascade(tableNameAndTypeFormSql.get(1));
}
}
}


// 根据解析类型,判断是进行清缓存的操作,还是进行保存关系的操作


}
@Override
public void run() {
doHandler();
}

}

主要思路:记录所有执行过的关联查询的表到一个set集合,同时在每一次更新操作时候都要去遍历这个set,如果发现有记录,就要把所有的和这个表发生过关联关系的其他表做更新缓存操作。

此方法需要项目代码完全符合Java的编码规范,经过测试后是完全有效果的,至于为什么要启用另外的线程来处理缓存,以及为什么要使用线程安全的set集合,欢迎大家讨论。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值