MyBatis启动流程源码分析

先来吐槽下CSDN,我辛辛苦苦写的文章,保存到草稿之后,准备返回,结果提示我没有的登陆,等我登陆之后,发现我写的文章竟然没有了!!!!!而且这个事情已经好几次了,估计是用CSDN的人太多了吧,想逼走一部分用户。

之前写了一篇关于MyBatis整个架构,工作流程的文章,保存之后发现啥都没有,也不想再写了,这篇直接就开始进入整个流程的源码分析阶段。

第一阶段

    获取相关配置,根据SqlSessionFactoryBuilder创建SqlSessionFactory,然后再根据SqlSessionFactory打开一个SqlSession这个阶段

   对于SqlSessionFactoryBuilder创建SqlSessionFactory,一般我们程序当中的代码执行如下

private static SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
private static SqlSessionFactory sqlSessionFactory;

private static void init() throws IOException {
    String resource = "mybatis-config.xml";
    Reader reader = Resources.getResourceAsReader(resource);
    sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
}

从一个指定的文件当中获取到Reader对象,可以理解为文件字节流,SqlSessionFactoryBuilder根据这个文件的内容,创建好一个SqlSessionFactory对象,我们来看builder方法

 public SqlSessionFactory build(Reader reader) {
        return this.build((Reader)reader, (String)null, (Properties)null);
    }


 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                reader.close();
            } catch (IOException var13) {
                ;
            }

        }

        return var5;
    }

builder方法的执行步骤如下

1、通过Reader字节流,将xml文件当中的内容,通过XMLConfigBuilder解析器,将其中的配置文件属性内容对应到具体的java对象字段上

2、在XMLConfigBuilder当中的读取到的配置信息会封装到对象Configuration对象上,并且该对象被XMLConfigBuilder所持有。

3、根据Configuration对象创建SqlSessionFactory。

 public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

这个方法主要是创建一个具体的SqlSessionFactroy的实现类,DefaultSqlSessionFactory,并将Configuration对象传递进去,作为DefaultSqlSessionFactory当中的一个属性。

接下来我们看下在SqlSessionFactory当中通过OpenSession获去SqlSession的代码,如果是无参的调用

 public SqlSession openSession() {
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }

这一步直接调用的就是openSessionFromDataSource方法,其中传递的参数是我们从configuration当中获取到的executorType,这个配置属性,autocommit给出的是false。假如我们的配置文件如下,可以看到并没有ExecutorType,但是我们可以从XMLConfigBuilder当中看到如果xml没有配置defaultExecutorType属性的话,这里会默认设置成SIMPLE.

的话,会设置

接下来我们来看OpenSessionFromDataSource这个方法的实现

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

我们来看下上面代码的具体做了什么

1、获取到配置文件的Enviroment信息,可以看到我们上面贴出来的xml的内容

2、从配置文件当中获取到TransactionFactory,这个TransactionFactory就是我们Environment当中配置的TransactionManager, 我们配置给出了type=‘JDBC’, 如果我们没有配置这个TransactionManager,那么就会使用初始化一个ManagedTransactionFactory对象。

3、根据我们上面获取到的TransactionFactory来创建一个Transation,这里我们上述配置的是JDBC,那么这个地方就是根据JdbcTransactionFactory来创建Transaction对象。(这个地方使用的就是工厂设计模式,不同类型的TransactionManager创建不同的Transaction)

4、根据Transaction和我们传递进来的ExecutorType创建Executor对象(根据我们上面xml配置,executorType是SIMPLE)

5、根据创建好的Configuration, 创建好的Executor,以及是否autocomit来创建session

接下来我们来看下在创建SqlSession之前的操作,创建Transaction,我们配置当中选择的是,JDBC,这里我们去看下JdbcTransactionFactory当中创建的JdbcTransaction对象

public class JdbcTransactionFactory implements TransactionFactory {
    public JdbcTransactionFactory() {
    }

    public void setProperties(Properties props) {
    }

    public Transaction newTransaction(Connection conn) {
        return new JdbcTransaction(conn);
    }

    public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
        return new JdbcTransaction(ds, level, autoCommit);
    }
}

创建jdbcTransaction时,会根据传递进来的数据库,事务级别,是否commit,来创建Transaction,JdbcTransaction的创建

 public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
        this.dataSource = ds;
        this.level = desiredLevel;
        this.autoCommmit = desiredAutoCommit;
    }

可以看到这个构造方法,就是对Transaction当中的对象进行赋值,关于Transaction具体是做什么的我们来看下其接口当中提供的方法。

public interface Transaction {
    Connection getConnection() throws SQLException;

    void commit() throws SQLException;

    void rollback() throws SQLException;

    void close() throws SQLException;
}

可以看到Transaction主要是用来处理数据库事物的,对于获取数据库连接,提交,回滚,关闭连接。

看完Transaction之后,我们来看下ExecutorType是做什么的,创建Executor是在Configuration类当中进行创建的,我们来看下具体实现代码

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null?this.defaultExecutorType:executorType;
        executorType = executorType == null?ExecutorType.SIMPLE:executorType;
        Object 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(this.cacheEnabled) {
            executor = new CachingExecutor((Executor)executor);
        }

        Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
        return executor;
    }

1、获取到当前传递进来ExecutorType,基于前面的分析,我们直到MyBatis给我们设置的是默认的ExecutorType, SIMPLE(枚举类型),

2、根据ExecutorType和事务创建Executor,可以看到我们当前创建的是SimpleExecutor。

3、判断是否支持缓存,如果是的话,我们这里就会采用CachingExecutor, 但是需要注意的是,我们并没有放弃使用之前的SimpleExecutor对象,而是将其作为参数传递到了CachingExecutor当中,后面分析缓存实现原理时,会着重分析这块内容。

4、这一步是将我们生成好的executor放入到InterceptorChain当中。(这个地方采用的就是责任链模式)关于plugin在后面分页插件当中会详细介绍。

以上是整个mybatis从配置文件到openSession的过程,接下来我们需要来看如何通过DefaultSqlSession来获取mapper,然后再进行增删改查操作。

获取mapper对象,并执行sql

我们在使用Mapper时,一般会通过SqlSession的getMapper方法来获取对应的配置文件的当中的mapper,由上面的分析可知,这里创建出来的是,DefaultSqlSession,

 public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

这里获取Mapper方式直接从Configuration当中获取了,我们来看Configuration类当中查看下

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

Configuration类也不想做这个事情,就把这个获取Mapper的方法,扔给了MapperRegistry,我们继续去看,MapperRegistry当中如何获取mapper对象

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null)
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

MapperRegistry会获取到这个Class对应的MapperProxyFactory,通过MapperProxyFactory来动态创建mapper接口的实现类

我们来看下MapperProxyFactory类,通过SqlSession来动态创建mapper的实现类

 public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

先通过sqlSession,接口类,methodCache来创建了一个动态代理类MapperProxy,我们先看下MapperProxy类

到了这里就非常清楚了,实现了InvocationHandler,重写了invoke方法,采用的就是jdk自动的动态代理接口,这个地方不熟悉的可以去看我关于动态代理模式的源码分析。设计模式——代理模式之动态代理源码分析JDK1.8(一)

到这里都已经非常清楚了,MapperProxy是一个动态代理类。我们接着上面分析MapperProxyFactory进行分析,通过newInstance方法调用生成了动态代理类的对象,然后根据接口类的加载器,接口类,以及动态代理对象动态生成指定的mapper接口的实体类。

总结一下关于Mapper对象的创建过程

DefaultSqlSession(getMapper)---->Configuration(getMapper)------>MapperRegistry(getMapper)------>MapperProxyFactory(newInstance)-------->MapperProxy(构造方法)------->MapperProxyFactory(newInstance)

所以Mapper对象的动态生成,其实就是MapperProxyFactory和MapperProxy这两个类来创建的。

接下来我们要分析MapperProxy这个代理类是如何调用具体的方法的

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

1、invoke方法在调用时会先判断,如果是否是Object类当中的方法,

2、如果是的话,直接调用方法即可,

3、如果不是Object的Class对象,会先通过cachedMapperMethod来获取MapperMethod,

4、采用MapperMethod执行具体的方法。

接下来我们来看调用的cachedMapperMethod方法

private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

1、这个methodCache是一个ConcurrentHashMap,key是Method,value为MapperMethod,是从MapperProxyFactory当中传递过来,在初始化MapperProxyFactory时,会创建一个空的ConcurrentHashMap。

2、如果当前Method在methodCache当中查询不到,就创建一个新的MapperMethod,并将其放入在cacheMethod当中。

3、最终返回一个mapperMethod方法

关于这个MapperMethod到底是什么我们从其构造方法入手,再进行分析


  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, method);
  }

1、根据参数创建SqlCommand

2、获取到调用方法的签名

接下来我们看创建SqlCommand的构造方法

 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {
      String statementName = mapperInterface.getName() + "." + method.getName();
      MappedStatement ms = null;
      if (configuration.hasStatement(statementName)) {
        ms = configuration.getMappedStatement(statementName);
      } else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
        String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
        if (configuration.hasStatement(parentStatementName)) {
          ms = configuration.getMappedStatement(parentStatementName);
        }
      }
      if (ms == null) {
        throw new BindingException("Invalid bound statement (not found): " + statementName);
      }
      name = ms.getId();
      type = ms.getSqlCommandType();
      if (type == SqlCommandType.UNKNOWN) {
        throw new BindingException("Unknown execution method for: " + name);
      }
    }

这个构造方法就是从Configuration当中获取到MappedStatement对象,MappedStatement其实就是我们xml当中的配置的需要执行的sql,以及返回类型,参数类型封装起来的一个java对象,关于解析过程这里不做详细分析,可以查看XmlStatementBuilder当中parseStatementNode以及MapperBuilderAssistant类当中的addMappedStatement方法,会将解析好的MappedStatement放入到configuration对象当中。再回到SqlCommand,其中封装的只有name和type属性,也就是通过这个构造方法,获取到我们在xml配置的id和type, 这里的SqlCommandType就是我们的select,update ,delete, insert标签。

分析完SqlCommand,接下来我们来看,构造方法当中另外一个MethodSignature的构造方法

 public MethodSignature(Configuration configuration, Method method) throws BindingException {
      this.returnType = method.getReturnType();
      this.returnsVoid = void.class.equals(this.returnType);
      this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
      this.mapKey = getMapKey(method);
      this.returnsMap = (this.mapKey != null);
      this.hasNamedParameters = hasNamedParams(method);
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
    }

可以看到这一步就是将方法的信息都获取到并绑定在MethodSignature对象上,包括参数,返回类型等。

上面这些构造方法结束之后,我们再回到MapperProxy,因为我们最初就是要分析它的invoke方法,是如何调用

上面的分析,我们已经构造好了一个MapperMethod对象,接下来就是使用MapperMethod来执行execute方法

这里我们以insert为例,进入看下sqlSession的insert方法,根据前面的分析,这里是DefaultSqlSession

 public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }

insert方法调用的是由参数的update方法

 public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

这个方法是根据我们Mapper当中的id,来查询到对应的MappedStatement,然后交给Executor去执行update操作,Executor上面的分析,在默认情况下,MyBatis会给我们使用SimpleExecutor,同时如果缓存开启就将SimpleExecutor作为delegate创建一个CachingExecutor,我们去CachingExecutor当中查看这个update方法。

public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }

1、第一步就是必要时刷新缓存,暂时先不做分析

2、调用delegate执行更新方法,所以最终还是回到我们当时的SimpleExecutor当中执行这个update方法

下面是先从BaseExecutor当中进行一个判断

 public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

然后再调用子类具体的doUpdate方法,这里BaseExecutor和SimpleExecutor之间就使用了模板方法设计模式,接下来进入到SimpleExecutor当中

public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

可以看到SimpleExecutor也并没有执行sql语句,而是将sql的执行交给了StatementHandler来执行。

上面关于MyBatis在处理Mapper时的一个过程进行总结:

通过MapperProxy和MapperProxyFactory动态代理地方式创建好Mapper接口的实现类,然后当我们调用mapper的接口时,会触发MapperProxy当中的invoke方法,invoke方法调用Executor执行sql,但是Executor会把这个sql的执行继续往下传递,最终执行SQL的就是StatementHandle。

猜你喜欢

转载自blog.csdn.net/summerZBH123/article/details/81304439