Mybatis 学习笔记

本文探讨了Mybatis相对于传统JDBC的优势,包括使用连接池提高性能,将SQL语句与Java代码分离以增强维护性。还深入解析了Mybatis插件的工作原理,解释了Executor、ParameterHandler、ResultSetHandler和StatementHandler四个可拦截对象,以及如何通过interceptorChain形成动态代理责任链。此外,文章介绍了分页插件的实现方式,即通过拦截SQL添加分页语句。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传统JDBC 的缺点 与 Mybatis 的解决方案
  1. 频繁的数据库连接的创建与销毁,没有使用连接池,浪费系统资源,从而影响系统性能;
    解决: 通过配置文件配置连接池,复用数据库连接。
  2. sql 语句存在硬编码,实际应用会频繁改动 sql 代码,代码难以维护,没有将 sql 代码与 java 代码分来管理;
    解决:将 sql 语句配置在 ***Mapper.xml 中。
  3. preparedStatement 向 sql 使用占位符的形式传递参数,条件难以确定;
    解决:自动将 java 对象映射到 sql 中,通过 parameterType 指定。
  4. 结果集处理中也存在着硬编码的问题,sql 的变化导致解析代码的不确定性,难以维护。
    解决:自动将 sql 执行结果映射到 java 对象中,通过将 resultType 或 resultMap 指定。
Mybatis 的插件原理

Mybatis 允许在已映射的 sql 语句执行过程中的某一点进行拦截调用。默认情况下,允许插件拦截的对象和对应的方法有:

  1. Executor:Mybatis 执行器,负责串联 StatementHandler,ParamterHandler,ResultSetHandler,执行增删查改操作。
    可拦截的方法:update, query, flushStatements, commit, rollback, getTransaction, close, isClosed。
  2. ParameterHandler :负责处理 sql 参数。
    可拦截的方法:getParameterObject, setParameters。
  3. ResultSetHandler:处理 sql 的返回集映射。
    可拦截的方法:handleResultSets, handleOutputParameters。
  4. StatementHandler:使用 Statement 执行 sql 语句。
    可拦截的方法:prepare, parameterize, batch, update, query。

一个插件对象的声明实例:

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

在 Mybatis 的操作过程中,会执行这四个对象的实例方法:

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
}

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
  ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
}

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
}

public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor, autoCommit);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

实例完成后,会调用 interceptorChain.pluginAll,这里的 interceptors 会保存在扫描配置文件时获取到的所有插件对象。

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
}
public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  
  default void setProperties(Properties properties) {
  }
}

实际上就是创建了一层一层的动态代理,形成了责任链。

  public static Object wrap(Object target, Interceptor interceptor) {
    // 获得interceptor配置的@Signature的type
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    // 当前代理类型
    Class<?> type = target.getClass();
    // 根据当前代理类型 和 @signature指定的type进行配对, 配对成功则可以代理
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

最后得到的对象:
在这里插入图片描述
getSignatureMap:根据Intercepts注解的 Signature 信息判定是否插件是否拦截。

分页插件的原理

分页插件就是利用 Mybatis 的分页插件,拦截待执行的 sql ,添加对应的分页语句和参数。
如: select * from user -> select t.* from (select * from user) t limit 0,10

Mybatis 源码分析

请添加图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值