MyBatis学习-6

Spring运行原理

Posted by lvyonghao on April 20, 2019

MyBatis学习-6

本节课来介绍一下MyBatis的工作原理 我们清楚运行原理,我们在以后的开发,还是插件都是很有必要的。

1.根据配置文件创建一个SqlSessionFactory

研究框架的源码(我也有点虚,一点一点来吧)首先我们回到当初的第一个测试中去看看:

@Test
    public void test1() throws IOException{
        //1.获取SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

        //2.获取SqlSession对象,
        SqlSession openSession = sqlSessionFactory.openSession();

        //3.获取接口的实现类对象
        //会为接口创建UI个代理对象,代理对象去执行增删改查
        try {
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);

            Employee employee = mapper.getEmpById(1);

            System.out.println(mapper);

            System.out.println(employee);
        } finally {
            openSession.close();
        }

    }

这次我们看看mapper到底是个什么东西,泡一下看一眼控制台得到了这个信息org.apache.ibatis.binding.MapperProxy@9bd0fa6它是一个MapperProxy代理对象

  • MyBaits在四大对象的创建过程中,都会有插件进行介入,插件可以利用动态带路机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。
  • MyBatis允许在以映射语句之行过程中的某一点进行拦截调用。
  • 默认情况,MyBatis允许使用插件来拦截的方法调用包括:

Excutor ParameterHandler ResultSetHandler StatementHandler

这四个方法想必在JDBC中我们是见过的,我们在看源码的时候一定要留心这些方法


我们再看看在运行前都做了什么工作

  • 首先通过自己写的getSqlSessionFactory我们创建了一个SqlSessionFactory
  • 之后我们再去看getSqlSessionFactory,他返回了一个new SqlSessionFactoryBuilder().build(inputStream)我们去看Build的源码
      public SqlSessionFactory build(InputStream inputStream) {
          return this.build((InputStream)inputStream, (String)null, (Properties)null);
      }
    

    他返回的就是他自己,ok我们再去看看SqlSessionFactoryBuilder的源码:

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

    他先创建了一个XML的解析器XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);接下来继续看,var5 = this.build(parser.parse());那么这个parse()是个什么方法呢我们继续来看他的源码

      public Configuration parse() {
          if (this.parsed) {
              throw new BuilderException("Each XMLConfigBuilder can only be used once.");
          } else {
              this.parsed = true;
              this.parseConfiguration(this.parser.evalNode("/configuration"));
              return this.configuration;
          }
      }
    

      private void parseConfiguration(XNode root) {
          try {
              this.propertiesElement(root.evalNode("properties"));    
              Properties settings = this.settingsAsProperties(root.evalNode("settings"));
              this.loadCustomVfs(settings);
              this.loadCustomLogImpl(settings);
              this.typeAliasesElement(root.evalNode("typeAliases"));
              this.pluginElement(root.evalNode("plugins"));
              this.objectFactoryElement(root.evalNode("objectFactory"));
              this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
              this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
              this.settingsElement(settings);
              this.environmentsElement(root.evalNode("environments"));
              this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
              this.typeHandlerElement(root.evalNode("typeHandlers"));
              this.mapperElement(root.evalNode("mappers"));
          } catch (Exception var3) {
              throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
          }
      }
    

    parseConfiguration会解析每一个节点你可以在源码中看到settings,typeAliases,reflectorFactory…这些,然后保存在Configuration当中

在其中我们看到他调用了parseConfiguration这个方法,我们看看他是干嘛的,他就在这个类的下面 首先他要拿到我们的空节点

    public Configuration parse() {
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true;
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

这个Configuration获取了当前语句的所有信息包括全局配置封装的Mapperstatment,我们的映射文件信息,这里不需要了解很多,知道这个流程就可以了。 最后他返回了这个configuration,这时候我们还是回到了var5 = this.build(parser.parse());当中,然后调用build方法我们再来看这个方法 public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }他的返回是根据传入的configuration,new了一个新的DefaultSqlSessionFactory,最后再把这个返回的传给创建sqlSessionfatory这个方法。

  • 总结:配置文件解析保存在configuration当中,再把他封装到DefaultSqlSession当中。一个MapStatement代表了一个增删改查的全部信息

2.openSession

接下来就是我们使用sqlSessionFactory创建一个SqlSession对象,让我们一起来看看这个流程吧

  • 首先我们调用了sqlSessionFactory.openSession()方法也就是调用了DefaultSqlSession的openSession(),让我们去DefaultSqlSession源码里去看看
      public SqlSession openSession(boolean autoCommit) {
          return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
      }
    

    我们看到它实际上返回的是一个openSessionFromDataSource()方法的返回值

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

    能看到在这里他获取了一些信息,和事务,比如Environment,TransactionFactoryFromEnvironment,把这些信息放在tx当中。重点是他创建了一个Executor,我们来看看怎么创建的

  • Executor,我们看一下这个newExecutor方法
      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;
      }
    

    如果这个executor如果是BATCH(批量的)的就new一个BatchExecutor对象,如果是REUSE就new一个ReuseExecutor对象,否则就new一个SimpleExecutor,根据executor在全局配置中的类型创建相应的executor,executor是一个接口,里面包含了我们sql的各种方法(增删改查…),我们可以看一下这个接口中的方法

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.executor;

import java.sql.SQLException;
import java.util.List;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

public interface Executor {
    ResultHandler NO_RESULT_HANDLER = null;

    int update(MappedStatement var1, Object var2) throws SQLException;

    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;

    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;

    <E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;

    List<BatchResult> flushStatements() throws SQLException;

    void commit(boolean var1) throws SQLException;

    void rollback(boolean var1) throws SQLException;

    CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);

    boolean isCached(MappedStatement var1, CacheKey var2);

    void clearLocalCache();

    void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);

    Transaction getTransaction();

    void close(boolean var1);

    boolean isClosed();

    void setExecutorWrapper(Executor var1);
}

回到我们的Executor当中接下来他写了这么一个判断 if (this.cacheEnabled) { executor = new CachingExecutor((Executor)executor); }就是在判断我们是否开启了二级缓存,如果是我们就用CachingExecutor对executor进行包装。 这还没有完,他还调用了interceptorChain.pluginAll()方法后才return了executor。这一部非常重要,我们用每一个interceptorChain(插件)的pluginAll()方法对executor进行包装。

  • 好绕了一大圈我们回到了创建好Executor后我们new了一个DefaultSqlSession(this.configuration, executor, autoCommit),他包含了configuration和Executor信息,最终返回给openSession
  • 总结:返回SqlSession实现类DefaultSqlSession对象,他包含了configuration和Executor信息,Executor会在这一步被我们创建。

3.getMapper

  • 我们先使用DefaultSqlSession对象的getMapper方法,那我们就看看这个getMapper是怎么写的 ` public T getMapper(Class type, SqlSession sqlSession) { return this.mapperRegistry.getMapper(type, sqlSession); }` 他返回了一个mapperRegistry的getMapper(type, sqlSession)方法的返回值
  • 来让我们看看这个mapperRegistry的getMapper方法,他就在这个文件当中
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
          MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
          if (mapperProxyFactory == null) {
              throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
          } else {
              try {
                  return mapperProxyFactory.newInstance(sqlSession);
              } catch (Exception var5) {
                  throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
              }
          }
      }
    

    我们可以看到它是通过接口创建一个MapperProxyFactory代理对象,然后返回了mapperProxyFactory.newInstance(sqlSession);,也就是说调用了这个代理对象的newInstance方法,参数是我们的sqlSession。

  • 我们来看看这个newInstance是怎么工作的
      public T newInstance(SqlSession sqlSession) {
          MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
          return this.newInstance(mapperProxy);
      }
    

    他先创建了一个MapperProxy对象,那这个MapperProxy又是什么呢我们再来看看他的源码

  • MapperProxy ``` // // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) //

package org.apache.ibatis.binding;

import java.io.Serializable; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; import org.apache.ibatis.reflection.ExceptionUtil; import org.apache.ibatis.session.SqlSession;

public class MapperProxy implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class mapperInterface; private final Map<Method, MapperMethod> methodCache;

public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }

        if (this.isDefaultMethod(method)) {
            return this.invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable var5) {
        throw ExceptionUtil.unwrapThrowable(var5);
    }

    MapperMethod mapperMethod = this.cachedMapperMethod(method);
    return mapperMethod.execute(this.sqlSession, args);
}

private MapperMethod cachedMapperMethod(Method method) {
    return (MapperMethod)this.methodCache.computeIfAbsent(method, (k) -> {
        return new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
    });
}

private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
    Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class, Integer.TYPE);
    if (!constructor.isAccessible()) {
        constructor.setAccessible(true);
    }

    Class<?> declaringClass = method.getDeclaringClass();
    return ((Lookup)constructor.newInstance(declaringClass, 15)).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}

private boolean isDefaultMethod(Method method) {
    return (method.getModifiers() & 1033) == 1 && method.getDeclaringClass().isInterface();
} } ``` 里面东西有点多哈,没事,我看先看他的开头它继承了一个接口InvocationHandler,这个接口不就是我们JDBC在做动态代理的时候用到的嘛,所我我们现在知道它是一个弄来创建动态代理的

  • 然后返回到我们的newInstance(MapperProxy)再去看这个newInstance方法
      protected T newInstance(MapperProxy<T> mapperProxy) {
          return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
      }
    

    发现它调用返回了我们Java反射中的动态代理对象。

  • 总结一步步下来其实就是getMapper返回了接口的代理对象,包含了sqlSession。

4.getEmpById(1)

下面我们来看如何实现增删改查让我们setp into

  • 首先mapperProxy调用invoke()这个方法,这是怎么回事呢,这就是代理方法在执行前会执行invoke方法,判断当前要执行的方法,
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          try {
              if (Object.class.equals(method.getDeclaringClass())) {
                  return method.invoke(this, args);
              }
    
              if (this.isDefaultMethod(method)) {
                  return this.invokeDefaultMethod(proxy, method, args);
              }
          } catch (Throwable var5) {
              throw ExceptionUtil.unwrapThrowable(var5);
          }
    
          MapperMethod mapperMethod = this.cachedMapperMethod(method);
          return mapperMethod.execute(this.sqlSession, args);
      }
    

就是if (this.isDefaultMethod(method),然后他会把mapperMethod包装成一个cachedMapperMethod,就是MyBatis能认识的方法,然后调用execute方法

  • execute方法,来让我们看一下源码 ``` public Object execute(SqlSession sqlSession, Object[] args) { Object result; Object param; switch(this.command.getType()) { case INSERT: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); break; case UPDATE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); break; case DELETE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); break; case SELECT: if (this.method.returnsVoid() && this.method.hasResultHandler()) { this.executeWithResultHandler(sqlSession, args); result = null; } else if (this.method.returnsMany()) { result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) { result = this.executeForMap(sqlSession, args); } else if (this.method.returnsCursor()) { result = this.executeForCursor(sqlSession, args); } else { param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException(“Unknown execution method for: “ + this.command.getName()); }

      if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
          throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
      } else {
          return result;
      }   }
    

``` 先来判断command方法,根据不同的方法执行相应的方法包装参数为Map或者直接返回,接下来,调用SqlSession的selectone,返回正确的信息就完成了。

  • 总结: 查询流程是这样的,首先创建代理对象,通过DefaultSqlSession,使用Executor方法,再到statementHandler,处理sql语句预编译,设置参数等相关操作,设置参数交给ParameterHandler,结果处理集交给ResultHandler。在整个过程中,进行数据库类型和JavaBean类型映射的是TypeHandler。这些操作的一切底层操作的都是�由JDBC:Statement:PrepareStatement来实现的。