MyBatis详解6.MyBatis技术内幕

字节跳动飞书内推!
北京、杭州、武汉、广州、深圳、上海,六大城市等你来投。
感兴趣的朋友可以私我咨询&内推,也可以通过链接直接投递
海量HC,极速响应,快来和我成为同事吧。
今日头条、抖音、Tik Tok也可以内推~

点击进入我的博客

MyBatis详解1.概述
MyBatis详解2.MyBatis使用入门
MyBatis详解3.MyBatis配置详解
MyBatis详解4.映射器Mapper
MyBatis详解5.动态SQL
MyBatis详解6.MyBatis技术内幕
MyBatis详解7.插件
MyBatis详解8.集成Spring

1 构建SqlSessionFactory的过程

通过SqlSessionFactoryBuilder构建SqlSessionFactory共有两步:

  1. 通过XMLConfigBuilder解析MyBatis的配置文件,并读取到Confinguration对象中。
  2. 使用Confinguration对象去创建SqlSessionFactory,由于SqlSessionFactory是一个接口,最终构建出的其实是DefaultSqlSessionFactory的对象。
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 通过XMLConfigBuilder读入配置
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      // ......
    }
  }
    
  public SqlSessionFactory build(Configuration config) {
    // 返回的是DefaultSqlSessionFactory
    return new DefaultSqlSessionFactory(config);
  }
Configuration的主要作用如下:
  • 读入配置文件,包括基础配置的XML文件和映射器的XML文件。
  • 初始化基础配置,比如MyBatis的别名等,一些重要的类对象如,插件、映射器、 ObjectFactory和typeHandler对象。
  • 提供单例,为后续创建SessionFactory服务并提供配置的参数。
  • 执行一些重要的对象方法,初始化配置信息。

2 映射器的内部构成

一个映射器是由3个部分组成
  1. MappedStatement,它保存映射器的一个节点(select、insert、delete、update)。包括许多我们配置的SQL、SQL的id、缓存信息、resultMap、parameterType、resultType、languageDriver等重要配置内容。
  2. SqlSource,是一个接口,它的主要作用是根据参数和其他的规则组装SQL,它是提供BoundSql对象的地方,它是MappedStatement的一个属性。
  3. BoundSql,它是建立SQL和参数的地方。它有3个常用的属性:SQL、parameterObject、parameterMappings。
映射器的内部构成
BoundSql的主要属性
  • BoundSql会提供3个主要的属性:parameterObject、parameterMappings和sql
  • parameterObject为参数本身,可以传递简单对象、POJO、Map或者@Param注解的参数。
  • parameterMappings,它是一个List,每一个元素都是ParameterMapping的对象。这个对象会描述我们的参数,包括属性、名称、表达式、 javaType、 jdbcType、typeHandler等重要信息。通过它可以实现参数和SQL的结合,以便PreparedStatement能够通过它找到parameterObject对象的属性并设置参数,使得程序准确运行。
  • sql属性就是我们书写在映射器里面的一条SQL,在插件的情况下,我们可以根据需要进行改写。
parameterObject的细节
  • 传递简单对象(如基本数据类型)时,例如当我们传递int类型时,MyBatis会把参数变为Integer对象传递。如果我们传递的是POJO或者Map,那么这个parameterObject就是你传入的POJO或者Map不变。
  • 当然我们也可以传递多个参数,如果没有@Param注解,那么MyBatis就会把parameterObject变为一个Map<String, Object>对象,其键值的关系是按顺序来规划的,类似于这样的形式:{"1":p1; "2":p2, "3":p3,...,"param1":pl, "param2":p2, "param3":p3},可以使用#{param1}或者#{1}去引用第1个参数。
  • 如果我们使用@Param注解,那么MyBatis就会把parameterObject会变为一个Map<String, Object>对象,键为@Param注解的键。

3 SqlSession的运行过程

3.1 Mapper的动态代理

我们自定义的Mapper接口想要发挥功能,必须有具体的实现类,在MyBatis中是通过为Mapper每个接口提供一个动态代理类来实现的。整个过程主要有三个类:MapperProxyFactory、MapperProxy、MapperMethod。

  • MapperProxyFactory就是MapperProxy的工厂类,主要方法就是包装了Java动态代理的Proxy.newProxyInstance()方法。
  • MapperProxy就是一个动态代理类,它实现了InvocationHandler接口。对于代理对象的调用都会被代理到InvocationHandler#invoke方法上。
  • MapperMethod包含了具体增删改查方法的实现逻辑。
public class MapperProxyFactory<T> {
   
  // 这里可以看到是通过Java的动态代理来实现的,具体代理的方法被放到来MapperProxy中
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}
// 实现了InvocationHandler接口
public class MapperProxy<T> implements InvocationHandler, Serializable {
  
  // 对代理类的所有方法的执行,都会进入到invoke方法中
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 此处判断是否是Object类的方法,如toString()、clone(),如果是则直接执行不进行代理
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    // 如果不是Object类的方法,则初始化一个MapperMethod并放入缓存中
    // 或者从缓存中取出之前的MapperMethod
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 调用MapperMethod执行对应
    return mapperMethod.execute(sqlSession, args);
  }
}
public class MapperMethod {
  // MapperMethod采用命令模式运行,根据上下文跳转,它可能跳转到许多方法中
  // 实际上它最后就是通过SqlSession对象去运行对象的SQL。
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: { //...
      }
      case UPDATE: { //...
      }
      case DELETE: { //...
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        //...
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }

    return result;
  }  
}

3.2 SqlSession中的对象

Mapper执行的过程是通过Executor、StatementHandler、ParameterHandler和ResultHandler来完成数据库操作和结果返回的:

  • Executor代表执行器,由它来调度StatementHandler、ParameterHandler、ResultHandler等来执行对应的SQL。
  • StatementHandler的作用是使用数据库的Statement(PreparedStatement)执行操作,起到承上启下的作用。
  • ParameterHandler用于SQL对参数的处理。
  • ResultHandler是进行最后数据集(ResultSet)的封装返回处理的。

3.3 执行器Executor

执行器是一个真正执行Java和数据库交互的类,一共有三种执行器,我们可以在MyBatis的配置文件中设置defaultExecutorType属性进行选择。

  • SIMPLE(org.apache.ibatis.executor.SimpleExecutor),简易执行器,默认执行器。
  • REUSE(org.apache.ibatis.executor.ReuseExecutor),是一种执行器重用预处理语句。
  • BATCH(org.apache.ibatis.executor.BatchExecutor),执行器重用语句和批量更新,它是针对批量专用的执行器。
// Configure类中创建Executor的具体逻辑
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    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);
    }
    // 在executor完成创建之后,会通过interceptorChain来添加插件
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

创建Executor的具体逻辑在Configure类中,可以看到,在Executor创建完成之后,会通过interceptorChain来添加插件,通过代理到方式,在调度真实的Executor方法之前执行插件代码来完成功能。

Executor的具体执行逻辑

我们通过SimpleExecutor来看一下Executor的具体执行逻辑:

  1. 根据Configuration来构建StatementHandler
  2. 然后使用prepareStatement方法,对SQL编译并对参数进行初始化
  3. 在prepareStatement方法中,调用了StatementHandler的prepared进行了预编译和基础设置,然后通过StatementHandler的parameterize来设置参数并执行。
  4. 包装好的Statement通过StatementHandler来执行,并把结果传递给resultHandler。
public class SimpleExecutor extends BaseExecutor {

  @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();
      // (1)根据 Configuration来构建Statementhandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>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;
  }
}

3.4 数据库会话器StatementHandler

StatementHandler就是专门处理数据库会话的,创建StatementHandler的过程在Configuration中。

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

    @Override
    public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
      return delegate.prepare(connection, transactionTimeout);
    }

    @Override
    public void parameterize(Statement statement) throws SQLException {
      delegate.parameterize(statement);
    }
  }

很显然创建的真实对象是一个RoutingStatementHandler对象,它实现了接口StatementHandler。从RoutingStatementHandler的构造方法来看,它其实是使用来委派模式来把具体的StatementHandler类型隐藏起来,通过RoutingStatementHandler来统一管理。一共用三种具体的StatementHandler类型:SimpleHandler、PreparedStatementHandler、CallableStatementHandler。

通过StatementHandler看执行细节

在Executor的具体执行逻辑中,我们主要关注StatementHandler的prepared、parameterize两个方法。

public abstract class BaseStatementHandler implements StatementHandler {
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // instantiateStatement对SQL进行了预编译
      statement = instantiateStatement(connection);
      // 设置超时时间
      setStatementTimeout(statement, transactionTimeout);
      // 设置获取最大的行数
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }
}
public class PreparedStatementHandler extends BaseStatementHandler {
  // 调用parameterize去设置参数,可以发现是通过parameterHandler来具体执行的
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
}
public class PreparedStatementHandler extends BaseStatementHandler {
  // 具体的查询就是通过PreparedStatement#execute来执行的
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }
}

3.5 参数处理器ParameterHandler

MyBatis是通过ParameterHandler对预编译的语句进行参数设置的。

public interface ParameterHandler {
  // 返回参数对象
  Object getParameterObject();
  // 设置预编译的SQL语句的参数
  void setParameters(PreparedStatement ps) throws SQLException;
}
public class DefaultParameterHandler implements ParameterHandler {

  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }
}

MyBatis为ParameterHandler提供了一个实现类DefaultParameterHandler,具体执行过程还是从 parameterObject对象中取参数然后使用typeHandler进行参数处理,而typeHandler也是在My Batis初始化的时候,注册在Configuration里面的,我们需要的时候可以直接拿来用。

3.6 ResultSetHandler

public interface ResultSetHandler {
  // 包装结果集的
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;
  // 处理存储过程输出参数的
  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
  void handleOutputParameters(CallableStatement cs) throws SQLException;
}

MyBatis为我们提供了一个DefaultResultSetHandler类,在默认的情况下都是通过这个类进行处理的。这个类JAVASSIST或者CGLIB作为延迟加载,然后通过typeHandler和ObjectFactory进行组装结果再返回。

3.7 总结

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容