mybatis 原理简单解析


前言

简单复习以下mybatis的原理为学习mybatis-plusPageHelper做准备,sqlSessionFactory的生成之类的没什么作用没写。
主要资料来源于书籍《深入浅出MyBatis技术原理与实战》
顺便吐槽一下,mybatis的源码真的一点注释都没有,如果不是有资料真的不想看

SqlSession中的主要对象

StatementHandler: 使用数据库的Statement执行对应操作

ParameterHandler: 对Sql中的参数进行处理

ResultHandler: 对数据集进行封装返回处理

Executor:用于调度其他组件来执行对应的Sql

Executor

所谓调度其他组件就是通过Configuration对象来生成其他组件然后按照流程调用对应的组件。默认的SimpleExecutor执行简单查询的流程代码如下:

@Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

用文字描述就是先通过configuration对象参数创建一个StatementHandler 对象,然后使用StatementHandler通过方法prepare() 进行预编译和基础设置来创建Statement对象,再通过方法parameterize() 方法来设置参数,最后再将StatementResultHandler 传递给StatementHandler来执行并返回结果

StatementHandler

StatementHandler的创建,创建时会创建一个RoutingStatementHandlerRoutingStatementHandler会根据配置的参数来创建CallableStatementHandler
PreparedStatementHandlerSimpleStatementHandler中的一种,默认为PreparedStatementHandler,创建时的代码如下

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

需要注意的时实际用于处理的StatementHandler是创建的RoutingStatementHandler中的delegate 参数而非RoutingStatementHandler本身。

可以自定义的过程

Interceptor

mybatis提供一个叫做Interceptor的接口。是想该接口并通过注解可以对SqlSession中四个对象的任意一个方法进行拦截,接口的定义如下

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}

intercept() 方法类似于Invoker接口中的invoke() 方法,Invocation 对象本身就包含了调用所需的参数,可以通过MetaObject类来对它进行处理。

plugin() 方法用于生成代理对象,setProperties() 用于读取配置文件来初始化(好像不能直接读取springboot的配置文件),可以看到新版的mybatis中都提供了默认实现。

实现方法:

通过configuration创建Sqlsession中对象时会调用这样一个方法

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

可以看到会使用所有以注册的Interceptor来创建代理对象,这样所有注册了的Interceptor都可以代理具体的对象

简单示例

代码是从书中直接抄来的,为了方便运行小改了一下

@Intercepts({@Signature(type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class})
})
public class LimitInterceptor implements Interceptor {

    //防止重名
    private static final String LMT_TABLE_NAME = "limit_Table_Name_xxx";
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        //分离出原始对象,这步因为没注释我看不懂反正这么做就行了
        while (metaStatementHandler.hasGetter("h")){
            Object o = metaStatementHandler.getValue("h");
            metaStatementHandler = SystemMetaObject.forObject(o);
        }

        String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        String limitSql;
        //没有被重写过
        if(!sql.contains(LMT_TABLE_NAME)){
            sql = sql.trim();
            //重新sql
            limitSql = "SELECT * FROM (" + sql + ")" + LMT_TABLE_NAME + " limit 500";
            metaStatementHandler.setValue("delegate.boundSql.sql", limitSql);
        }

        return invocation.proceed();
    }


}

@Intercepts注解表示该类同于拦截mybatis组件的方法,@Signature注解用参数表明了拦截的方法
在springboot中直接提供bean即可注册

@Bean
    public Interceptor myInterceptor(){
        return new LimitInterceptor();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值