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


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

    1. @Intercepts({
    2. @Signature(
    3. type = Executor.class,
    4. method = "query",
    5. args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    6. @Signature(
    7. type = Executor.class,
    8. method = "query",
    9. args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
    10. })
    11. 第一个,表示不支持缓存的query。
    12. 第二个,表示支持缓存的query。
    13. a.某些数据变化不频繁,但查询非常频繁。缓存可以减少数据库查询次数,提高响应速度。
    14. b.在分页查询中,缓存可以显著提高性能,尤其是当用户频繁浏览不同页面时。
    15. c.对于复杂查询,生成的 SQL 可能涉及多个表的联接,执行时间较长。缓存可以显著减少这种查询的执行次数。

    具体区别

    1. 访问频率和实时性

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

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

    有哪些方法,可以判断是否应用缓存:
    1.通过sql语句标识:

    1. @Intercepts({
    2. @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
    3. })
    4. public class CacheInterceptor implements Interceptor {
    5. @Override
    6. public Object intercept(Invocation invocation) throws Throwable {
    7. MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
    8. String sqlId = mappedStatement.getId();
    9. // 假设我们有一个特定的SQL ID需要使用缓存
    10. if ("com.example.mapper.UserMapper.selectUsers".equals(sqlId)) {
    11. // 使用缓存逻辑
    12. CacheKey cacheKey = ...; // 创建 CacheKey
    13. BoundSql boundSql = ...; // 获取 BoundSql
    14. // 执行带缓存的查询
    15. return executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
    16. } else {
    17. // 直接访问数据库
    18. return invocation.proceed();
    19. }
    20. }
    21. @Override
    22. public Object plugin(Object target) {
    23. return Plugin.wrap(target, this);
    24. }
    25. @Override
    26. public void setProperties(Properties properties) {
    27. // 可选:设置一些属性
    28. }
    29. }

    2.通过注解或配置

    1. @Intercepts({
    2. @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
    3. })
    4. public class CacheInterceptor implements Interceptor {
    5. @Override
    6. public Object intercept(Invocation invocation) throws Throwable {
    7. MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
    8. Object parameter = invocation.getArgs()[1];
    9. RowBounds rowBounds = (RowBounds) invocation.getArgs()[2];
    10. ResultHandler resultHandler = (ResultHandler) invocation.getArgs()[3];
    11. Executor executor = (Executor) invocation.getTarget();
    12. // 读取自定义属性
    13. Boolean useCache = (Boolean) mappedStatement.getConfiguration().getVariables().get("useCache");
    14. if (useCache != null && useCache) {
    15. // 使用缓存逻辑
    16. CacheKey cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, mappedStatement.getBoundSql(parameter));
    17. BoundSql boundSql = mappedStatement.getBoundSql(parameter);
    18. return executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
    19. } else {
    20. // 直接访问数据库
    21. return invocation.proceed();
    22. }
    23. }
    24. @Override
    25. public Object plugin(Object target) {
    26. return Plugin.wrap(target, this);
    27. }
    28. @Override
    29. public void setProperties(Properties properties) {
    30. // 可选:设置一些属性
    31. }
    32. }

    3.通过业务逻辑判断

    1. @Intercepts({
    2. @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
    3. })
    4. public class CacheInterceptor implements Interceptor {
    5. @Override
    6. public Object intercept(Invocation invocation) throws Throwable {
    7. MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
    8. Object parameter = invocation.getArgs()[1];
    9. RowBounds rowBounds = (RowBounds) invocation.getArgs()[2];
    10. ResultHandler resultHandler = (ResultHandler) invocation.getArgs()[3];
    11. Executor executor = (Executor) invocation.getTarget();
    12. // 根据业务逻辑判断
    13. if (shouldUseCache(mappedStatement, parameter)) {
    14. // 使用缓存逻辑
    15. CacheKey cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, mappedStatement.getBoundSql(parameter));
    16. BoundSql boundSql = mappedStatement.getBoundSql(parameter);
    17. return executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
    18. } else {
    19. // 直接访问数据库
    20. return invocation.proceed();
    21. }
    22. }
    23. private boolean shouldUseCache(MappedStatement mappedStatement, Object parameter) {
    24. // 根据业务逻辑判断是否使用缓存
    25. // 示例:如果参数包含某个特定值,则使用缓存
    26. if (parameter instanceof Map) {
    27. Map paramMap = (Map) parameter;
    28. return "useCache".equals(paramMap.get("cacheFlag"));
    29. }
    30. return false;
    31. }
    32. @Override
    33. public Object plugin(Object target) {
    34. return Plugin.wrap(target, this);
    35. }
    36. @Override
    37. public void setProperties(Properties properties) {
    38. // 可选:设置一些属性
    39. }
    40. }

    两个query方法的区别:

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

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

    1. import org.apache.ibatis.annotations.Options;
    2. import org.apache.ibatis.annotations.Select;
    3. public interface UserMapper {
    4. @Select("SELECT * FROM your_table WHERE your_conditions")
    5. @Options(useCache = false)
    6. List selectRealTimeData();
    7. }
    1. public abstract class BaseExecutor implements Executor {
    2. @Override
    3. public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    4. BoundSql boundSql = ms.getBoundSql(parameter);
    5. CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    6. return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    7. }
    8. @Override
    9. public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    10. // 检查二级缓存
    11. if (ms.isUseCache() && resultHandler == null) {
    12. Cache cache = ms.getCache();
    13. if (cache != null) {
    14. // 从二级缓存中获取结果
    15. @SuppressWarnings("unchecked")
    16. List list = (List) cache.getObject(key);
    17. if (list != null) {
    18. return list;
    19. }
    20. }
    21. }
    22. // 如果缓存没有命中,执行数据库查询
    23. List result = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    24. // 将结果存入二级缓存
    25. if (ms.isUseCache() && resultHandler == null && cache != null) {
    26. cache.putObject(key, result);
    27. }
    28. return result;
    29. }
    30. protected abstract List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
    31. }

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

    1.全局配置

    1. "cacheEnabled" value="true"/>

    2.映射文件配置

    1. "com.example.mapper.UserMapper">

    3.自定义缓存配置

    1. "com.example.mapper.UserMapper">
    2. eviction="LRU"
    3. flushInterval="60000"
    4. size="512"
    5. readOnly="true"/>

    4.使用注解配置

    1. import org.apache.ibatis.annotations.CacheNamespace;
    2. import org.apache.ibatis.annotations.Select;
    3. import org.apache.ibatis.cache.decorators.LruCache;
    4. @CacheNamespace(
    5. eviction = LruCache.class, // 缓存回收策略
    6. flushInterval = 60000, // 刷新间隔,单位:毫秒
    7. size = 512, // 缓存大小
    8. readWrite = false // 是否可读写
    9. )
    10. public interface UserMapper {
    11. @Select("SELECT * FROM users")
    12. List selectUsers();
    13. }

    提取有效信息:

    1. private Object extractRouteParameterValue(Invocation invocation, Set routerPropertyNames) {
    2. Object routeValue = null;
    3. try {
    4. Object[] args = invocation.getArgs();
    5. MappedStatement mappedStatement = (MappedStatement) args[0];
    6. Object parameterObject = args[1];
    7. BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
    8. List parameterMappings = boundSql.getParameterMappings();
    9. TypeHandlerRegistry typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    10. Configuration configuration = mappedStatement.getConfiguration();
    11. for (ParameterMapping parameterMapping : parameterMappings) {
    12. String rawPropertyName = parameterMapping.getProperty();
    13. String actualPropertyName = resolvePropertyName(rawPropertyName);
    14. if (!routerPropertyNames.contains(actualPropertyName.toLowerCase())) {
    15. continue;
    16. }
    17. // copy from DefaultParameterHandler.setParameter方法
    18. // ParameterMode.IN 输入, OUT、INOUT 是在存储过程中使用,暂时无视
    19. if (parameterMapping.getMode() != ParameterMode.OUT) {
    20. String propertyName = parameterMapping.getProperty();
    21. if (boundSql.hasAdditionalParameter(propertyName)) {
    22. routeValue = boundSql.getAdditionalParameter(propertyName);
    23. } else if (parameterObject == null) {
    24. routeValue = null;
    25. } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    26. routeValue = parameterObject;
    27. } else {
    28. MetaObject metaObject = configuration.newMetaObject(parameterObject);
    29. routeValue = metaObject.getValue(propertyName);
    30. }
    31. }
    32. if (routeValue != null && hasText(routeValue.toString())) {
    33. return routeValue;
    34. }
    35. throw new DataSourceRoutingException(String.format("未检测到有效的数据库路由,请检测是否传入:(%s)", boundSql.getSql()));
    36. }
    37. } catch (DataSourceRoutingException dataSourceRoutingException) {
    38. throw dataSourceRoutingException;
    39. } catch (RuntimeException e) {
    40. throw new DataSourceRoutingException(String.format("数据库路由解析异常, invocation method:{%s}, args:{%s}, routerPropertyNames:{%s}",
    41. invocation.getMethod().toGenericString(), Arrays.toString(invocation.getArgs()), routerPropertyNames), e);
    42. }
    43. return null;
    44. }

  • 相关阅读:
    Redis解决优惠券秒杀
    ubuntu双系统安装以及启动时卡死解决办法
    网站小程序分类目录网源码系统+会员登录注册功能 带完整搭建教程
    5个实用的SQLite数据库可视化工具(GUI)
    如何用Excel软件制作最小二乘法①
    跟着我一起通关java面试吧(30)
    pcie reset系列之 内核框架
    CF1899B 250 Thousand Tons of TNT
    【毕业设计】基于单片机的智慧农业管理系统 -大棚管理系统 自动灌溉系统
    MindRecord-Windows下中文路径问题Unexpected error. Failed to open file
  • 原文地址:https://blog.csdn.net/sxtopc/article/details/140099500