Mybatis中支持缓存的query与不支持缓存的query

mybatis拦截器中,通常添加两个query的签名方法,如下:

java 复制代码
@Intercepts({
    @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})

第一个,表示不支持缓存的query。

第二个,表示支持缓存的query。

a.某些数据变化不频繁,但查询非常频繁。缓存可以减少数据库查询次数,提高响应速度。

b.在分页查询中,缓存可以显著提高性能,尤其是当用户频繁浏览不同页面时。

c.对于复杂查询,生成的 SQL 可能涉及多个表的联接,执行时间较长。缓存可以显著减少这种查询的执行次数。

具体区别

  1. 访问频率和实时性

    • 不需要缓存:每次查询都直接访问数据库,适用于数据变化频繁或需要最新数据的场景。
    • 需要缓存:查询结果可以被缓存,适用于数据变化不频繁但查询频繁的场景。
  2. 性能和资源使用

    • 不需要缓存:每次都访问数据库,可能会增加数据库负载。
    • 需要缓存:利用缓存减少数据库访问次数,显著提高性能和响应速度。

有哪些方法,可以判断是否应用缓存:

1.通过sql语句标识:

java 复制代码
@Intercepts({
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class CacheInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        String sqlId = mappedStatement.getId();

        // 假设我们有一个特定的SQL ID需要使用缓存
        if ("com.example.mapper.UserMapper.selectUsers".equals(sqlId)) {
            // 使用缓存逻辑
            CacheKey cacheKey = ...; // 创建 CacheKey
            BoundSql boundSql = ...; // 获取 BoundSql
            // 执行带缓存的查询
            return executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        } else {
            // 直接访问数据库
            return invocation.proceed();
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可选:设置一些属性
    }
}

2.通过注解或配置

java 复制代码
<select id="selectUsers" resultType="User" useCache="true">
    SELECT * FROM users
</select>



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

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];
        RowBounds rowBounds = (RowBounds) invocation.getArgs()[2];
        ResultHandler resultHandler = (ResultHandler) invocation.getArgs()[3];
        Executor executor = (Executor) invocation.getTarget();

        // 读取自定义属性
        Boolean useCache = (Boolean) mappedStatement.getConfiguration().getVariables().get("useCache");

        if (useCache != null && useCache) {
            // 使用缓存逻辑
            CacheKey cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, mappedStatement.getBoundSql(parameter));
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            return executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        } else {
            // 直接访问数据库
            return invocation.proceed();
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可选:设置一些属性
    }
}

3.通过业务逻辑判断

java 复制代码
@Intercepts({
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class CacheInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = invocation.getArgs()[1];
        RowBounds rowBounds = (RowBounds) invocation.getArgs()[2];
        ResultHandler resultHandler = (ResultHandler) invocation.getArgs()[3];
        Executor executor = (Executor) invocation.getTarget();

        // 根据业务逻辑判断
        if (shouldUseCache(mappedStatement, parameter)) {
            // 使用缓存逻辑
            CacheKey cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, mappedStatement.getBoundSql(parameter));
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            return executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        } else {
            // 直接访问数据库
            return invocation.proceed();
        }
    }

    private boolean shouldUseCache(MappedStatement mappedStatement, Object parameter) {
        // 根据业务逻辑判断是否使用缓存
        // 示例:如果参数包含某个特定值,则使用缓存
        if (parameter instanceof Map) {
            Map<String, Object> paramMap = (Map<String, Object>) parameter;
            return "useCache".equals(paramMap.get("cacheFlag"));
        }
        return false;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可选:设置一些属性
    }
}

两个query方法的区别:

其实,mapper文件中useCache参数会用来构建MappedStatement对象。即ms.isUseCache()被用来判断是否走缓存逻辑。

或者 通过@Options注解方式配置useCache参数:

java 复制代码
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;

public interface UserMapper {
    
    @Select("SELECT * FROM your_table WHERE your_conditions")
    @Options(useCache = false)
    List<YourResultType> selectRealTimeData();
}
java 复制代码
public abstract class BaseExecutor implements Executor {

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        // 检查二级缓存
        if (ms.isUseCache() && resultHandler == null) {
            Cache cache = ms.getCache();
            if (cache != null) {
                // 从二级缓存中获取结果
                @SuppressWarnings("unchecked")
                List<E> list = (List<E>) cache.getObject(key);
                if (list != null) {
                    return list;
                }
            }
        }
        // 如果缓存没有命中,执行数据库查询
        List<E> result = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        // 将结果存入二级缓存
        if (ms.isUseCache() && resultHandler == null && cache != null) {
            cache.putObject(key, result);
        }
        return result;
    }

    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
}

其中,mybatis中,启用二级缓存的配置方式:

1.全局配置

java 复制代码
<configuration>
    <!-- 其他配置 -->
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
</configuration>

2.映射文件配置

java 复制代码
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 启用二级缓存 -->
    <cache/>

    <!-- 其他映射配置 -->
    <select id="selectUsers" resultType="User">
        SELECT * FROM users
    </select>
</mapper>

3.自定义缓存配置

java 复制代码
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 启用二级缓存,并设置自定义属性 -->
    <cache
        eviction="LRU"       <!-- 缓存回收策略:LRU(默认) -->
        flushInterval="60000" <!-- 刷新间隔,单位:毫秒 -->
        size="512"           <!-- 缓存大小 -->
        readOnly="true"/>    <!-- 只读缓存 -->

    <!-- 其他映射配置 -->
    <select id="selectUsers" resultType="User">
        SELECT * FROM users
    </select>
</mapper>

4.使用注解配置

java 复制代码
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.cache.decorators.LruCache;

@CacheNamespace(
    eviction = LruCache.class,   // 缓存回收策略
    flushInterval = 60000,       // 刷新间隔,单位:毫秒
    size = 512,                  // 缓存大小
    readWrite = false            // 是否可读写
)
public interface UserMapper {
    
    @Select("SELECT * FROM users")
    List<User> selectUsers();
}

提取有效信息:

java 复制代码
private Object extractRouteParameterValue(Invocation invocation, Set<String> routerPropertyNames) {

        Object routeValue = null;
        try {
            Object[] args = invocation.getArgs();
            MappedStatement mappedStatement = (MappedStatement) args[0];
            Object parameterObject = args[1];
            BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            TypeHandlerRegistry typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
            Configuration configuration = mappedStatement.getConfiguration();

            for (ParameterMapping parameterMapping : parameterMappings) {
                String rawPropertyName = parameterMapping.getProperty();
                String actualPropertyName = resolvePropertyName(rawPropertyName);

                if (!routerPropertyNames.contains(actualPropertyName.toLowerCase())) {
                    continue;
                }
                // copy from DefaultParameterHandler.setParameter方法
                //  ParameterMode.IN 输入, OUT、INOUT 是在存储过程中使用,暂时无视
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        routeValue = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        routeValue = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        routeValue = parameterObject;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        routeValue = metaObject.getValue(propertyName);
                    }
                }
                if (routeValue != null && hasText(routeValue.toString())) {
                    return routeValue;
                }
                throw new DataSourceRoutingException(String.format("未检测到有效的数据库路由,请检测是否传入:(%s)", boundSql.getSql()));
            }
        } catch (DataSourceRoutingException dataSourceRoutingException) {
            throw dataSourceRoutingException;
        } catch (RuntimeException e) {
            throw new DataSourceRoutingException(String.format("数据库路由解析异常, invocation method:{%s}, args:{%s}, routerPropertyNames:{%s}",
                    invocation.getMethod().toGenericString(), Arrays.toString(invocation.getArgs()), routerPropertyNames), e);
        }
        return null;
    }
相关推荐
漫漫不慢.20 小时前
算法练习-二分查找
java·开发语言·算法
还是鼠鼠20 小时前
《黑马商城》Elasticsearch基础-详细介绍【简单易懂注释版】
java·spring boot·spring·elasticsearch·搜索引擎·spring cloud·全文检索
如竟没有火炬20 小时前
LRU缓存——双向链表+哈希表
数据结构·python·算法·leetcode·链表·缓存
牧羊人_myr20 小时前
Maven核心功能与项目构建详解
java·maven
量子物理学20 小时前
Eclipse Mosquitto 在小内存下怎么修改配置文件
java·服务器·eclipse
程序员鱼皮21 小时前
让老弟做个数据同步,结果踩了 7 个大坑!
java·后端·计算机·程序员·编程·职场
阿湯哥21 小时前
Redis数据库隔离业务缓存对查询性能的影响分析
数据库·redis·缓存
麦兜*21 小时前
Redis 7.2 新特性实战:Client-Side Caching(客户端缓存)如何大幅降低延迟?
数据库·spring boot·redis·spring·spring cloud·缓存·tomcat
Iris76121 小时前
MyBatis一对多关系映射方式
java
程序员清风21 小时前
滴滴二面:MySQL执行计划中,Key有值,还是很慢怎么办?
java·后端·面试