如何理解mybatis运行原理?试试理解他为咱们到底作了啥---前人种树后人乘凉

这是我参与更文挑战的第21天,活动详情查看: 更文挑战java

[TOC]程序员

mybatis运行分为两部分,第一部分读取配置文件缓存到Configuration对象中。用以建立SqlSessionFactory,第二部分是SqlSession的执行过程。spring

Mybatis基本认识

动态代理

  • 以前咱们知道Mapper仅仅是一个接口,而不是一个逻辑实现类。可是在Java中接口是没法执行逻辑的。这里Mybatis就是经过动态代理实现的。关于动态代理咱们经常使用的有Jdk动态代理和cglib动态代理。两种却别这里不作赘述。关于CGLIB代理在框架中使用的比较多。sql

  • 关于动态代理就是全部的请求有一个入口,由这个入口进行分发。在开发领域的一个用途就是【负载均衡】数据库

  • 关于Mybatis的动态代理是使用了两种的结合。缓存

  • 下面看看JDK和cglib两种实现markdown

JDK实现

  • 首先咱们须要提供一个接口 , 这个接口是对咱们程序员的一个抽象。 拥有编码和改BUG的本领
public interface Developer {

    /** * 编码 */
    void code();

    /** * 解决问题 */
    void debug();
}

复制代码
  • 关于这两种本领每一个人处理方式不一样。这里咱们须要一个具体的实例对象
public class JavaDeveloper implements Developer {
    @Override
    public void code() {
        System.out.println("java code");
    }

    @Override
    public void debug() {
        System.out.println("java debug");
    }
}

复制代码
  • 咱们传统的调用方式是经过java提供的new 机制创造一个JavaDeveloper对象出来。而经过动态代理是经过java.lang.reflect.Proxy对象建立对象调用实际方法的。session

  • 经过newProxyInstance方法获取接口对象的。而这个方法须要三个参数mybatis

ClassLoader loader : 经过实际接口实例对象获取ClassLoader Class<?>[] interfaces : 咱们抽象的接口 InvocationHandler h : 对咱们接口对象方法的调用。在调用节点咱们能够进行咱们的业务拦截app

JavaDeveloper jDeveloper = new JavaDeveloper();
Developer developer = (Developer) Proxy.newProxyInstance(jDeveloper.getClass().getClassLoader(), jDeveloper.getClass().getInterfaces(), (proxy, method, params) -> {
    if (method.getName().equals("code")) {
        System.out.println("我是一个特殊的人,code以前先分析问题");
        return method.invoke(jDeveloper, params);
    }
    if (method.getName().equals("debug")) {
        System.out.println("我没有bug");

    }
    return null;
});
developer.code();
developer.debug();

复制代码

CGLIB动态代理

  • cglib动态代理优势在于他不须要咱们提早准备接口。他代理的实际的对象。这对于咱们开发来讲就很方便了。
public class HelloService {
    public HelloService() {
        System.out.println("HelloService构造");
    }

    final public String sayHello(String name) {
        System.out.println("HelloService:sayOthers>>"+name);
        return null;
    }

    public void sayHello() {
        System.out.println("HelloService:sayHello");
    }
}

复制代码
  • 下面咱们只须要实现cglib提供的MethodInterceptor接口,在初始化设置cglib的时候加载这个实例化对象就能够了
public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======插入前置通知======");
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("======插入后者通知======");
        return object;
    }
}

复制代码
  • 下面咱们就来初始化设置cglib
public static void main(String[] args) {
    //代理类class文件存入本地磁盘方便咱们反编译查看源代码
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/root/code");
    //经过CGLIB动态代理获取代理对象过程
    Enhancer enhancer = new Enhancer();
    //设置enhancer对象的父类
    enhancer.setSuperclass(HelloService.class);
    // 设置enhancer的回调对象
    enhancer.setCallback(new MyMethodInterceptor());
    //建立代理对象
    HelloService helloService = (HelloService) enhancer.create();
    //经过代理对象调用目标方法
    helloService.sayHello();
}

复制代码
  • 仔细看看cglib和spring的aop特别像。针对切点进行切面拦截控制。

总结

  • 经过对比两种动态代理咱们很容易发现,mybatis就是经过JDK代理实现Mapper调用的。咱们Mapper接口实现经过代理到xml中对应的sql执行逻辑

反射

  • 相信有必定经验的Java工程师都对反射或多或少有必定了解。其实从思想上看不惯哪一种语言都是有反射的机制的。
  • 经过反射咱们就摆脱了对象的限制咱们调用方法再也不须要经过对象调用了。能够经过Class对象获取方法对象。从而经过invoke方法进行方法的调用了。

Configuration对象做用

  • Configuration对象存储了全部Mybatis的配置。主要初始化一下参数
    • properties
    • settings
    • typeAliases
    • typeHandler
    • ObjectFactory
    • plugins
    • environment
    • DatabaseIdProvider
    • Mapper映射器

映射器结构

  • BoundSql提供三个主要的属性 parameterMappings 、parameterObject、sql

  • parameterObject参数自己。咱们能够传递java基本类型、POJO、Map或者@Param标注的参数。

  • 当咱们传递的是java基本类型mybatis会转换成对应的包装对象 int -> Integer

  • 若是咱们传递POJO、Map。就是对象自己

  • 咱们传递多个参数且没有@Param指定变量名则parameterObject 相似

{"1":p1,"2":p2,"param1":p1,"param2":p2}

  • 咱们传递多个参数且@Param指定变量名 则parameterObject相似

{"key1":p1,"key2":p2,"param1":p1,"param2":p2}

  • parameterMapping 是记录属性、名称、表达式、javaType,jdbcType、typeHandler这些信息
  • sql 属性就是咱们映射器中的一条sql. 正常咱们在常见中对sql进行校验。正常不须要修改sql。

sqlsession执行流程(源码跟踪)

  • 首先咱们看看咱们平时开发的Mapper接口是如何动态代理的。这就须要提到MapperProxyFactory这个类了。该类中的newInstance方法
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

复制代码
  • 经过上满代码及上述对jdk动态代理的表述。咱们能够知道mapperProxy是咱们代理的重点。
  • MapperProxy是InvocationHandler的实现类。他重写的invoke方法就是代理对象执行的方法入口。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
    if (Object.class.equals(method.getDeclaringClass())) {
    return method.invoke(this, args);
    } else if (isDefaultMethod(method)) {
    return invokeDefaultMethod(proxy, method, args);
    }
} catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

复制代码
private boolean isDefaultMethod(Method method) {
return (method.getModifiers()
    & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
    && method.getDeclaringClass().isInterface();
}

复制代码
  • 经过源码发现。invoke内部首先判断对象是不是类 。 经过打断点发现最终会走到cacheMapperMethod这个方法去建立MapperMethod对象。
  • 继续查看MapperMethod中execute方法咱们能够了解到内部实现实际上是一个命令行模式开发。经过判断命令从而执行不一样的语句。判断到具体执行语句而后将参数传递给sqlsession进行sql调用并获取结果。到了sqlsession就和正常jdbc开发sql进行关联了。sqlsession中ExecutorStatementHandlerParameterHandlerResulthandler四大天王

Executor

  • 顾名思义他就是一个执行器。将java提供的sql提交到数据库。Mybatis提供了三种执行器。

  • Configuration.classnewExecutor源码

  • 根据uml咱们不难看出mybatis中提供了三类执行器分别SimpleExecutor、ReuseExecutor、BatchExecutor
public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 获得configuration 中的environment
      final Environment environment = configuration.getEnvironment();
      // 获得configuration 中的事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 获取执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 返回默认的SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

复制代码
  • 经过上述源码咱们知道在sqlsession获取一个数据库session对象时咱们或根据咱们的settings配置加载一个Executor对象。在settings中配置也很简单
<settings>
<!--取值范围 SIMPLE, REUSE, BATCH -->
	<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>

复制代码
  • 咱们也能够经过java代码设置
factory.openSession(ExecutorType.BATCH);

复制代码

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

复制代码
  • 很明显Mybatis中StatementHandler使用的是RoutingStatementHandler这个class

  • 关于StatementHandler和RoutingStatementHandler之间的关系咱们经过源码能够看出这里和Executor同样都是适配器模式。采用这种模式的好处是方便咱们对这些对象进行代理。这里读者能够猜想一下是使用了哪一种动态代理。给点提示 这里使用了接口哦

  • 在查看BaseStatementHandler结构咱们会发现和Executor如出一辙。一样的Mybatis在构造RoutingStatementHandler的时候会根据setting中配置来加载不一样的具体子类。这些子类都是继承了BaseStatementHandler.

  • 前一节咱们跟踪了Executor。 咱们知道Mybatis默认的是SimpleExecutor。 StatementHandler咱们跟踪了Mybaits默认的是PrePareStatementHandler。在SimpleExecutor执行查询的源码以下

  • 咱们发如今executor查询钱会先让statementHandler构建一个Statement对象。最终就是StatementHandler中prepare方法。这个方法在抽象类BaseStatmentHandler中已经封装好了。

  • 这个方法的逻辑是初始化statement和设置链接超时等一些辅助做用
  • 而后就是设置一些参数等设置。最后就走到了执行器executor的doquery

  • PrepareStatement在咱们jdbc开发时是常见的一个类 。 这个方法执行execute前咱们须要设置sql语句,设置参数进行编译。这一系列步骤就是刚才咱们说的流程也是PrepareStatementHandler.prepareStatement帮咱们作的事情。那么剩下的咱们也很容易想到就是咱们对数据结果的封装。正如代码所示下马就是resultSetHandler帮咱们作事情了。

结果处理器(ResultSetHandler)

@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

复制代码
  • 这个方法咱们能够导出来是结果xml中标签配置对结果的一个封装。

总结

  • SqlSession在一个查询开启的时候会先经过CacheExecutor查询缓存。击穿缓存后会经过BaseExector子类的SimpleExecutor建立StatementHandler。PrepareStatementHandler会基于PrepareStament执行数据库操做。并针对返回结果经过ResultSetHandler返回结果数据

主题

相关文章
相关标签/搜索