java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Mybatis拦截器和缓存机制

Mybatis插件机制(拦截器)和缓存机制示例详解

作者:南部余额

MyBatis插件机制是MyBatis框架中最常被低估却最强大的扩展能力之一,这篇文章主要介绍了Mybatis插件机制(拦截器)和缓存机制的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

Mybatis 插件机制

官网插件介绍:https://mybatis.org/mybatis-3/zh_CN/configuration.html#plugins

简介

MyBatis 插件(Interceptor)是 MyBatis 提供的扩展机制,允许用户在执行过程中的特定点拦截并自定义核心组件的行为。

核心概念

四大组件

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

Executor接口:

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;
	/**
	 * 执行更新操作(INSERT、UPDATE、DELETE)
	 * <p><b>执行流程:</b></p>
	 * <ol>
	 *   <li>创建 StatementHandler</li>
	 *   <li>调用 ParameterHandler 设置参数</li>
	 *   <li>执行 SQL 更新</li>
	 *   <li>清理相关缓存</li>
	 * </ol>
	 * @param ms MappedStatement 对象,包含 SQL 映射信息
	 * @param parameter 参数对象,可以是 Map 或 POJO
	 * @return 影响的行数
	 * @throws SQLException 如果数据库操作失败
	 */
  int update(MappedStatement ms, Object parameter) throws SQLException;
  /**
   * 执行查询操作(完整参数版本)
   * 这是查询操作的底层实现,包含所有必要的参数。通常由另一个重载的 query 方法调用。
   * @param <E> 返回结果的类型
   * @param ms MappedStatement 对象
   * @param parameter 参数对象
   * @param rowBounds 分页参数,包含 offset 和 limit
   * @param resultHandler 结果处理器,用于自定义结果处理
   * @param cacheKey 缓存键,用于一级缓存
   * @param boundSql 绑定的 SQL 信息
   * @return 查询结果列表
   * @throws SQLException 如果数据库操作失败
   *
   * @see #query(MappedStatement, Object, RowBounds, ResultHandler)
   */
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
      CacheKey cacheKey, BoundSql boundSql) throws SQLException;
   /**
    * 执行查询操作(常用版本)
    *
    * <p><b>执行流程:</b></p>
    * <ol>
    *   <li>检查一级缓存</li>
    *   <li>缓存未命中时查询数据库</li>
    *   <li>创建 StatementHandler 和 ResultSetHandler</li>
    *   <li>执行查询并映射结果</li>
    *   <li>将结果存入一级缓存</li>
    * </ol>
    * @param <E> 返回结果的类型
    * @param ms MappedStatement 对象
    * @param parameter 参数对象
    * @param rowBounds 分页参数
    * @param resultHandler 结果处理器,如果为 null 则使用默认处理器
    * @return 查询结果列表
    * @throws SQLException 如果数据库操作失败
    */
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
      throws SQLException;
  /**
   * 执行查询操作,返回游标(用于大数据量流式处理)
   * <p>与普通查询不同,游标查询不会一次性加载所有数据到内存,
   * 而是逐条处理,适合处理大数据集。</p>
   * @param <E> 返回结果的类型
   * @param ms MappedStatement 对象
   * @param parameter 参数对象
   * @param rowBounds 分页参数
   * @return 查询结果游标
   * @throws SQLException 如果数据库操作失败
   */
  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
   /**
    * 刷新批处理语句
    *
    * <p>执行所有缓存的批处理语句,通常在 BatchExecutor 中使用。
    * 调用此方法会执行所有已添加但未执行的批处理操作。</p>
    * @return 批处理结果列表
    * @throws SQLException 如果数据库操作失败
    * @see org.apache.ibatis.executor.BatchResult
    *
    * @example
    * <pre>
    * // 批处理操作示例
    * for (User user : users) {
    *     sqlSession.insert("insertUser", user);
    * }
    * List<BatchResult> results = sqlSession.flushStatements();
    * </pre>
    */
  List<BatchResult> flushStatements() throws SQLException;
  /**
   * 提交事务
   * <p>如果 required 为 true,则强制提交事务;
   * 如果为 false,则根据事务状态决定是否提交。</p>
   * @param required 是否强制提交
   */
  void commit(boolean required) throws SQLException;
  /**
   * 回滚事务
   * <p>如果 required 为 true,则强制回滚事务;
   * 如果为 false,则根据事务状态决定是否回滚。</p>
   * @param required 是否强制回滚
   */
  void rollback(boolean required) throws SQLException;
  /**
   * 创建缓存键
   *
   * <p>根据 SQL 语句、参数和分页信息生成唯一的缓存键,
   * 用于一级缓存的键值对存储。</p>
   *
   * @param ms MappedStatement 对象
   * @param parameterObject 参数对象
   * @param rowBounds 分页信息
   * @param boundSql 绑定的 SQL
   * @return 缓存键对象
   */
  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
  /**
   * 检查指定缓存键是否已缓存
   *
   * <p>查询一级缓存中是否存在指定 MappedStatement 和缓存键对应的结果。</p>
   *
   * @param ms MappedStatement 对象
   * @param key 缓存键
   * @return 如果已缓存返回 true,否则返回 false
   */
  boolean isCached(MappedStatement ms, CacheKey key);
  /**
   * 清理本地缓存(一级缓存)
   *
   * <p>清除当前 Executor 实例的所有一级缓存。
   * 在更新操作后会自动调用,也可以手动调用。</p>
   */
  void clearLocalCache();
  /**
   * 延迟加载
   *
   * <p>用于处理关联对象的延迟加载。当访问懒加载属性时,
   * MyBatis 会调用此方法从数据库加载关联数据。</p>
   *
   * @param ms 关联对象的 MappedStatement
   * @param resultObject 结果对象(主对象)
   * @param property 要加载的属性名
   * @param key 缓存键
   * @param targetType 目标类型
   */
  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  /**
   * 获取事务对象
   * @return 当前的事务对象
   * @see org.apache.ibatis.transaction.Transaction
   */
  Transaction getTransaction();
  /**
   * 关闭执行器
   *
   * <p>如果 forceRollback 为 true,则在关闭前强制回滚所有未提交的事务;
   * 如果为 false,则根据事务状态决定。</p>
   *
   * @param forceRollback 是否强制回滚
   */
  void close(boolean forceRollback);
  /**
   * 检查执行器是否已关闭
   * @return 如果已关闭返回 true,否则返回 false
   */
  boolean isClosed();
  /**
   * 设置执行器包装器
   * <p>用于插件机制,允许在现有 Executor 外包装额外的逻辑。
   * 这个方法通常由 MyBatis 内部使用。</p>
   * @param executor 包装器执行器
   */
  void setExecutorWrapper(Executor executor);
}

ResultSetHandler接口

主要职责:

/**
 * MyBatis 结果集处理器核心接口
 * <p><b>核心映射流程:</b></p>
 * <ol>
 *   <li>遍历 ResultSet 的每一行</li>
 *   <li>根据 resultMap 创建目标对象实例</li>
 *   <li>将列值映射到对象属性</li>
 *   <li>处理嵌套查询和延迟加载</li>
 *   <li>返回映射后的对象集合</li>
 * </ol>
 */
public interface ResultSetHandler {

  /**
   * 处理结果集并将其转换为 Java 对象列表,默认方法
   * 
   * <p><b>执行流程:</b></p>
   * <ol>
   *   <li>获取 ResultSet 元数据(列信息)</li>
   *   <li>遍历 ResultSet 的每一行数据</li>
   *   <li>根据 ResultMap 创建目标对象实例</li>
   *   <li>将列值设置到对象属性中(支持自动映射和显式映射)</li>
   *   <li>处理关联对象(association)和集合对象(collection)</li>
   *   <li>返回完整映射的对象列表</li>
   * </ol>
   *
   * @param <E> 返回结果的类型
   * @param stmt JDBC Statement 对象,包含执行后的 ResultSet
   * @return 映射后的 Java 对象列表,如果没有结果返回空列表(非 null)
   */
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;

  /**
   * 处理游标结果集,支持流式读取大数据集
   * 
   * <p>与 {@link #handleResultSets(Statement)} 不同,此方法返回 Cursor 对象,
   * 允许逐条处理结果,避免一次性加载所有数据到内存。</p>
   * 
   * <p><b>适用场景:</b></p>
   * <ul>
   *   <li>处理大量数据(避免内存溢出)</li>
   *   <li>需要逐条处理的业务逻辑</li>
   *   <li>数据导出或批量处理</li>
   * </ul>
   *
   * @param <E> 返回结果的类型
   * @param stmt JDBC Statement 对象
   * @return 游标对象,用于迭代访问结果
   */
  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

  /**
   * 处理存储过程的输出参数
   * <p>专门用于处理 CallableStatement 中的输出参数(OUT/INOUT 参数)。
   * 在执行存储过程后,从此方法中获取输出参数的值。</p>
   * <p><b>处理流程:</b></p>
   * <ol>
   *   <li>从 CallableStatement 中获取输出参数元数据</li>
   *   <li>根据 parameterMap 配置定位输出参数</li>
   *   <li>使用 TypeHandler 将 JDBC 类型转换为 Java 类型</li>
   *   <li>将输出参数值设置到参数对象中</li>
   * </ol>
   *
   * @param cs 调用存储过程后的 CallableStatement 对象
   */
  void handleOutputParameters(CallableStatement cs) throws SQLException;
}

ParameterHandler接口

主要职责:

/**
 * MyBatis 参数处理器核心接口
 * <p><b>参数处理流程:</b></p>
 * <ol>
 *   <li>获取 Mapper 方法的参数对象</li>
 *   <li>解析参数类型和值</li>
 *   <li>使用 TypeHandler 进行类型转换</li>
 *   <li>将参数设置到 PreparedStatement 的对应位置</li>
 * </ol>
 */
public interface ParameterHandler {

  /**
   * 获取原始参数对象
   * 
   * <p>返回 Mapper 方法调用时传入的原始参数对象。
   * 参数可能是单个对象、Map、或者被 @Param 注解标注的多参数包装对象。</p>
   *
   * @return 原始参数对象,如果没有参数则返回 null
   * @example
   * // Mapper 方法定义:User selectUser(@Param("id") Long id, @Param("name") String name);
   * // 调用时,getParameterObject() 返回的可能是:
   * // 1. Map 对象:{id=1, name="张三", param1=1, param2="张三"}
   * // 2. 或者经过包装的参数对象
   */
  Object getParameterObject();

  /**
   * 设置参数到 PreparedStatement
   * 
   * <p><b>核心执行流程:</b></p>
   * <ol>
   *   <li>获取 BoundSql 中的参数映射信息</li>
   *   <li>遍历所有参数映射(ParameterMapping)</li>
   *   <li>根据参数名称从参数对象中获取值</li>
   *   <li>使用对应的 TypeHandler 将 Java 类型转换为 JDBC 类型</li>
   *   <li>调用 PreparedStatement.setXxx() 方法设置参数</li>
   * </ol>
   *
   * @param ps JDBC PreparedStatement 对象
   * @throws SQLException 如果数据库操作失败或参数设置错误
   */
  void setParameters(PreparedStatement ps) throws SQLException;
}

StatementHandler接口

主要职责:

/**
 * MyBatis 语句处理器核心接口
 */
public interface StatementHandler {

  /**
   * 准备 SQL 语句,创建 JDBC Statement 对象
   * 
   * <p><b>执行流程:</b></p>
   * <ol>
   *   <li>根据 SQL 类型创建对应的 Statement(PreparedStatement/CallableStatement)</li>
   *   <li>设置查询超时时间</li>
   *   <li>设置获取大小(fetch size)等优化参数</li>
   *   <li>返回准备好的 Statement 对象</li>
   * </ol>
   *
   * @param connection 数据库连接
   * @param transactionTimeout 事务超时时间(秒),可为 null
   * @return 准备好的 JDBC Statement 对象
   * @throws SQLException 如果数据库操作失败
   */
  Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;

  /**
   * 参数化 Statement,设置 SQL 参数
   * <p>调用 ParameterHandler 将 Java 参数设置到 PreparedStatement 中。
   * 这是参数绑定的核心步骤。</p>
   *
   * @param statement JDBC Statement 对象(通常是 PreparedStatement)
   * @throws SQLException 如果参数设置失败
   */
  void parameterize(Statement statement) throws SQLException;

  /**
   * 执行批量操作
   * 
   * <p>将多个 SQL 语句添加到批处理中,但不立即执行。
   * 实际的执行由 {@link org.apache.ibatis.executor.BatchExecutor} 控制。</p>
   *
   * @param statement JDBC Statement 对象
   * @throws SQLException 如果批处理操作失败
   */
  void batch(Statement statement) throws SQLException;

  /**
   * 执行更新操作(INSERT、UPDATE、DELETE)
   * 
   * <p><b>执行流程:</b></p>
   * <ol>
   *   <li>调用 parameterize() 方法设置参数</li>
   *   <li>执行 Statement.executeUpdate()</li>
   *   <li>返回影响的行数</li>
   * </ol>
   *
   * @param statement JDBC Statement 对象
   * @return 影响的行数
   * @throws SQLException 如果数据库操作失败
   */
  int update(Statement statement) throws SQLException;

  /**
   * 执行查询操作,返回结果列表
   * 
   * <p><b>执行流程:</b></p>
   * <ol>
   *   <li>调用 parameterize() 方法设置参数</li>
   *   <li>执行 Statement.executeQuery()</li>
   *   <li>调用 ResultSetHandler 处理结果集</li>
   *   <li>返回映射后的对象列表</li>
   * </ol>
   *
   * @param <E> 返回结果的类型
   * @param statement JDBC Statement 对象
   * @param resultHandler 结果处理器
   * @return 查询结果列表
   * @throws SQLException 如果数据库操作失败

   */
  <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;

  /**
   * 执行查询操作,返回游标(用于流式处理大数据集)
   * 
   * <p>与 {@link #query(Statement, ResultHandler)} 不同,此方法返回 Cursor 对象,
   * 允许逐条读取结果,避免一次性加载所有数据到内存。</p>
   *
   * @param <E> 返回结果的类型
   * @param statement JDBC Statement 对象
   * @return 查询结果游标
   * @throws SQLException 如果数据库操作失败
   */
  <E> Cursor<E> queryCursor(Statement statement) throws SQLException;

  /**
   * 获取绑定的 SQL 信息
   * <p>BoundSql 包含最终要执行的 SQL 语句、参数映射信息和参数对象。</p>
   * @return BoundSql 对象,包含 SQL 语句和参数信息
   */
  BoundSql getBoundSql();

  /**
   * 获取参数处理器
   * <p>ParameterHandler 负责将 Java 参数转换为 JDBC 参数。</p>
   * @return ParameterHandler 对象
   */
  ParameterHandler getParameterHandler();
}

Mybatis拦截器Interceptor接口

1. 了解插件接口类

public interface Interceptor {
	// 核心拦截方法
  Object intercept(Invocation invocation) throws Throwable;
	// 生成代理对象,将该插件加入到过滤器链中。底层是代理技术。
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
	// 设置属性值,接收xml传递来的参数信息
  default void setProperties(Properties properties) {
    // NOP
  }
}

2. 实现Mybatis拦截器接口

public class TestMybatisInterceptor implements Interceptor {
    /**
     * 核心方法
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 核心方法,确保mybatis方法可以正常运行
        Object proceed = invocation.proceed();
        
        return proceed;
    }
}

3. 了解@Intercepts()注解

3.1 基本结构

@Intercepts({
    @Signature(type = 要拦截的类.class, method = "要拦截的方法名", args = {参数类型列表})
})
public class MyInterceptor implements Interceptor {
    // 实现Interceptor接口的方法
}

3.2 可拦截的类-也就是四大组件

MyBatis允许拦截以下四个接口的方法:

3.3 @Signature注解的详细说明

确定要拦截的组件类型。如果拦截对象为Executor执行器,则全局搜索Executor.class接口。method=该接口中的方法args=该方法的参数类型。

/**
 * 拦截mybatis执行器的方法query()【执行查询操作(常用版本)】
 */
@Intercepts(
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
)
public class TestMybatisInterceptor implements Interceptor {

    /**
     * 核心拦截方法
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 核心方法,确保mybatis方法可以正常运行
        Object proceed = invocation.proceed();
        return proceed;
    }
}

3.4 拦截器方法的参数说明

在intercept方法中,可以通过Invocation对象获取被拦截的方法的参数、目标对象和方法。

invocation.getTarget():获取被拦截的对象(例如Executor实例)。
invocation.getMethod():获取被拦截的方法(Method对象)。
invocation.getArgs():获取被拦截方法的参数数组。
invocation.proceed():执行原方法。

3.5 拦截Executor组件query方法

package com.maven.mybatis.plugins;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Method;

/**
 * 拦截mybatis执行器的方法query()【执行查询操作(常用版本)】
 */
@Intercepts(
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
)
public class TestMybatisInterceptor implements Interceptor {

    /**
     * 核心拦截方法,拦截所有的查询方法,并输出sql语句
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // CachingExecutor
        Object target = invocation.getTarget();
        // 拦截的那个方法:被@Signature注解修饰的methdo="query"方法。
        // org.apache.ibatis.executor.Executor.query(MappedStatement,Object,RowBounds,ResultHandler)
        Method method = invocation.getMethod();
        // 方法的参数信息
        Object[] args = invocation.getArgs();
        // 获取SQL语句处理器
        MappedStatement ms = (MappedStatement) args[0];
        // args[1]:paramHandler,获取参数处理器
        BoundSql boundSql = ms.getBoundSql(args[1]);
        // 获取sql语句,xml中定义的sql语句,未进行参数替换
        String sql = boundSql.getSql();
        System.out.println("sql = " + sql);
        // 核心方法,确保mybatis方法可以正常运行
        Object proceed = invocation.proceed();
        return proceed;
    }
}

3.6 在mybatis-config.xml配置文件引入插件

    <plugins>
        <plugin interceptor="com.maven.mybatis.plugins.TestMybatisInterceptor">
        </plugin>
    </plugins>

3.7 执行测试

    @Test
    public void user() throws Exception {
        // 将配置文件转为输入流
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 创建SqlSessionFactoryBuilder,作用是根据配置文件,创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 根据配置文件,创建工程SqlSessionFactory,全局唯一
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        // 工厂创建SqlSession,建立一次会话
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 使用和指定语句的参数和返回值相匹配的接口,映射器实例
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.selectUserById(1);
        System.out.println("user = " + user);
        sqlSession.close();
    }
sql = select
            id ,
            user_name userName,
            password
        from user where id = ?

Mybatis 缓存机制

缓存机-官网简介:https://mybatis.org/mybatis-3/zh_CN/sqlmap-xml.html#cache

本地缓存-官网简介:https://mybatis.org/mybatis-3/zh_CN/java-api.html

缓存机制

Mybatis 使用到了两种缓存:本地缓存(一级缓存、local cache)二级缓存(second level cache)

本地缓存(一级缓存、事务缓存)

默认事务期间,会开启事务级别缓存。

  • 同一个事务期间,前面查询的数据,后面如果再执行相同查询,会从一级缓存中获取数据,不会给数据库发送SQL。

概念:一级缓存(SqlSession级别)

每当一个新 sqlSession被创建,MyBatis 就会创建一个与之相关联的本地缓存。任何在 sqlSession执行过的查询结果都会被保存在本地缓存中,所以,当再次执行参数相同的相同查询时,就不需要实际查询数据库了。本地缓存将会在做出修改、事务提交或回滚,以及关闭 sqlSession时清空。

默认情况下,本地缓存数据的生命周期等同于整个 sqlSession的周期。由于缓存会被用来解决循环引用问题和加快重复嵌套查询的速度,所以无法将其完全禁用。但是你可以通过设置 localCacheScope=STATEMENT 来只在语句执行时使用缓存。

注意,如果 localCacheScope 被设置为 SESSION,对于某个对象,MyBatis 将返回在本地缓存中唯一对象的引用。对返回的对象(例如 list)做出的任何修改将会影响本地缓存的内容,进而将会影响到在本次 sqlSession中从缓存返回的值。因此,不要对 MyBatis 所返回的对象作出更改,以防后患。

二级缓存(Mapper级别)

二级缓存是基于 Mapper 的,它需要手动开启。二级缓存的作用范围更广,可以跨 SqlSession 使用。开启二级缓存后,相同的查询可以在不同的 SqlSession 中共享缓存数据。但是,二级缓存需要查询结果对象实现 Serializable (序列化)接口,因为缓存数据可能会被序列化到磁盘

三级缓存(自定义缓存)

三级缓存是 MyBatis 提供的一个扩展点,允许开发者自定义缓存实现。在分布式系统中,一级和二级缓存可能会导致数据不一致的问题,因此可以通过集成第三方缓存,如 Redis 或 Ehcache,来实现分布式缓存。这种方式可以跨应用共享缓存数据,更适合现代的微服务架构。

一级缓存与二级缓存的区别?

MyBatis 的一级缓存和二级缓存是MyBatis中提供的两种缓存机制,它们的主要区别在于作用域生命周期。下面我将从多个维度对它们进行详细的对比和解释。

一级缓存(Local Cache)

作用域

生命周期

配置

工作机制

二级缓存(Second Level Cache)

作用域

生命周期

配置

工作机制

缓存执行流程

执行查询操作
    ↓
检查二级缓存(如果开启)
    ↓ 命中 → 返回结果
    ↓ 未命中
检查一级缓存(当前SqlSession)
    ↓ 命中 → 返回结果(并存入二级缓存)
    ↓ 未命中
查询数据库
    ↓
结果存入一级缓存
    ↓
SqlSession关闭时 → 一级缓存数据刷入二级缓存

举例说明一级和二级缓存

一级缓存(SqlSession级别):

二级缓存(Mapper级别):

什么时候会导致缓存失效?

缓存不命中与缓存失效一个意思。
失效几种情况:

  1. 查询的东西不一样。
  2. 两次查询之间,进行了一次增删改(由于增删改,会引发数据库的变化,mybatis认为,数据可能变化了,他就要再发一次查询)。

总结 

到此这篇关于Mybatis插件机制(拦截器)和缓存机制的文章就介绍到这了,更多相关Mybatis拦截器和缓存机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文