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

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

@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语句标识:

@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.通过注解或配置

<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.通过业务逻辑判断

@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参数:

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();
}
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.全局配置

<configuration>
    <!-- 其他配置 -->
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
</configuration>

2.映射文件配置

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

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

3.自定义缓存配置

<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.使用注解配置

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

提取有效信息:

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

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis关联映射是指通过MyBatis框架实现对象之间的关系映射,包括一对一、一对多、多对一和多对多等关系。关联映射可以通过配置文件和注解的方式来实现。 MyBatis缓存机制是指MyBatis框架自带的缓存功能,可以提高数据库查询的性能。MyBatis缓存可以分为一级缓存和二级缓存。一级缓存是指在同一个SqlSession,对于相同的查询语句,MyBatis会将查询结果缓存起来,下次再执行相同的查询时,直接从缓存获取结果,而不需要再次访问数据库。二级缓存是指在多个SqlSession,对于相同的查询语句,MyBatis会将查询结果缓存起来,当其他SqlSession执行相同的查询时,可以直接从缓存获取结果,而不需要再次访问数据库。 关于Spring JdbcTemplate类几个常用方法的作用,我无法在提供的引用内容找到明确的回答。但是一般来说,Spring JdbcTemplate类提供了一系列方法来执行数据库的增删改查操作。常用的方法包括:execute()方法用于执行SQL语句,query()方法用于执行查询操作并返回结果集,update()方法用于执行更新操作并返回受影响的行数,batchUpdate()方法用于执行批处理操作等。这些方法可以帮助开发人员简化数据库操作的代码,并提高开发效率。 但是,如果您对Spring JdbcTemplate类具体方法的作用有进一步的疑问,我建议您参考《Java EE企业级应用开发教程 第2版(Spring Spring MVC MyBatis)》这本书,其可能会有更详细的解释和示例。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值