本方法没有具体介绍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集合,欢迎大家讨论。