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

news/2024/7/8 7:34:56 标签: mybatis, 缓存, java

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;
    }


http://www.niftyadmin.cn/n/5536763.html

相关文章

pnpm介绍

PNPM 是一个 JavaScript 包管理器&#xff0c;类似于 npm 和 Yarn。它的全称是 "Performant npm"&#xff0c;主要设计目标是优化包的安装和管理过程&#xff0c;以提升速度和效率。PNPM 的主要特点包括&#xff1a; 符号链接&#xff08;Symlink&#xff09;&#x…

怎样将word默认Microsoft Office,而不是WPS

设置——>应用——>默认应用——>选择"word"——>将doc和docx都选择Microsoft Word即可

uniapp小程序使用uni.switchTab跳转首页,首页的tabbar消失了

问题描述&#xff1a; uniapp小程序在个人中心页调用登录接口&#xff0c;登录成功后使用uni.switchTab跳转首页&#xff0c;首页的tabbar消失了 解决&#xff1a; 如果是原生的tabbar 使用uni.showTabBar(OBJECT)显示 tabBar

2023年下半年软考网络规划设计师论文真题

论文一 论虚拟化网络架构的规划与建设 随着信息技术的发展,网络以及软件厂商的产品、企业网络的规划按照NaaS模型进行演进已经成为一种共识。在NaaS的理念下,企业的IT专业人员将能够从选项菜单中订购网络基础设施组件,根据业务需求进行设计,并在短时间内交付和运行整个网…

万字长文|关于 OpenAI 接口开发你应该知道的一切

这篇文章中个人结合自己的实践经验把 OpenAI 官方文档解读一遍。但是原文档涉及内容众多&#xff0c;包括微调&#xff0c;嵌入&#xff08;Embeddings&#xff09;等众多主题&#xff0c;我这里重点挑选自己开发高频使用到的&#xff0c;需要详细了解的可以自行前往官网阅读。…

OCR技术主要用于自动化文本数据的录入

OCR是“Optical Character Recognition”的缩写&#xff0c;中文意思是光学字符识别。这是一种技术&#xff0c;允许电子设备如扫描仪或数码相机读取文档中的文本&#xff0c;通过检测和分析文本的暗和亮的模式来识别字符的形状&#xff0c;然后将这些形状转换为可被计算机处理…

Redis 的过期策略

Redis有几种不同的过期策略&#xff0c;用于管理键的过期和自动删除&#xff1a; 定时删除&#xff08;TTL&#xff09;&#xff1a; 最常见的过期策略是设置键的过期时间&#xff08;TTL&#xff0c;Time To Live&#xff09;。当键设置了过期时间后&#xff0c;Redis会在键过…

【解决方案】笔记本电脑屏幕亮度调节失效(Dell G15 5510 使用Fn调节)

目前解决方案&#xff1a;使用驱动总裁&#xff08;其他的驱动安装软件应该也可以&#xff0c;个人觉得这个好用&#xff09;&#xff0c;更新显卡驱动即可。如图所示本人更新了Intel UHD Graphics核显驱动&#xff0c;功能回复正常。 使用Fn快捷键调节亮度如图所示&#xff0…