Análisis del código fuente de Mybatis uno

Más contenido emocionante puede visitar mi blog independiente

Comenzamos con el código más simple y analizamos el flujo de trabajo general de mybatis. Luego analice algunas características de mybatis a partir de los detalles del código.

Ejemplos de código básico

public class test {
  public static void main(String[] args) throws IOException{
    String resource = "example/mybatis-config.xml";
    // 加载配置文件 并构建SqlSessionFactory对象
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    // 从SqlSessionFactory对象中获取 SqlSession对象
    SqlSession sqlSession = factory.openSession();
    // 执行操作
    User user=new User();
    user.setId(1);
    Object u= (User)sqlSession.selectOne("getUser", user);
    System.out.println(u.toString());
    // 关闭SqlSession
    sqlSession.close();
  }
}

La clase de usuario es en realidad un pojo con solo ID, nombre de usuario y contraseña. No pegaré el código.
Por supuesto, mybatis debe tener el código sql correspondiente.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="example.Dao.UserMapper">
  <select id="getUser" parameterType="int" resultType="example.Pojo.User">
        select * from user where id= #{id}
    </select>
</mapper>

También hay configuraciones que son indispensables para cualquier marco.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis_study?useUnicode=true"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="example/UserMapper.xml"/>
  </mappers>
</configuration>

El código se ha pegado. Tomemos este código como ejemplo para analizar el proceso de ejecución de mybatis.

Realizar análisis de procesos

El proceso de creación de SqlSessionFactory

La primera línea de código válido es este InputStream inputStream = Resources.getResourceAsStream(resource);lugar, Resourcesuna clase de herramienta, y su función es facilitarnos la carga de varios archivos de recursos. No importa cómo se implemente internamente. En resumen, lo que hace esta línea de código es simplemente cargar nuestro archivo de configuración.

Lo más destacado es en realidad esta línea de código. SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
Dividimos esta línea de código en dos partes. La primera parte es new SqlSessionFactoryBuilder()que esto no necesita ser explicado. El papel de esta parte es crear un SqlSessionFactoryBuilderobjeto. La segunda parte es llamar al SqlSessionFactory build(Reader reader)método del objeto . Este método Muy importante, este método en realidad llama a otro método sobrecargado, y su implementación específica es la siguiente:

 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

Este código, si no consideramos otras cosas, este código en realidad hace tres cosas, usando el archivo de configuración que pasamos reader(los dos últimos parámetros se pasan cuando se llama nulo), crea un XMLConfigBuilderobjeto, Luego llame al parse()método del objeto , obtenga un Configurationobjeto, use el Configurationobjeto, use el build(Configuration cofig)método, cree el SqlSessionFactoryobjeto y luego devuelva el SqlSessionFactoryobjeto.

A continuación explicamos lo que hicieron estos tres pasos.

  • Paso 1: crear XMLConfigBuilderobjetos.
    Aunque parece simple y nuevo, el objeto es creado. De hecho, todavía es relativamente complicado. Varios constructores sobrecargados se llaman capa por capa, y finalmente llegaron a este constructor.
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  //创建一个空的Configuration对象,实例化了XMLConfigBuilder对象
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    //对属性各种赋值
    //我们配置时分离出的Properties文件中的信息,就是在这里进入到mybatis的
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    //parser就是用来解析XML文件的,在之前的构造器中,已经将配置文件的inputStream设置到该对象中去了
    this.parser = parser;
  }

Como básicamente todo lo que hacen los constructores es asignar valores a los atributos, entonces tenemos que analizar una ola XMLConfigBuilderde qué atributos están allí y qué hacen.

//下面三个是继承自父类BaseBuilder中的属性

    /**
    * 存储基础配置信息的一个对象
    */
    protected final Configuration configuration; 
    /**
    * 故名思意,就是存储各种类型的别名的一个对象
    */
    protected final TypeAliasRegistry typeAliasRegistry;
    
    /**
    * 存储各种类型的类型处理器的对象
    */
    protected final TypeHandlerRegistry typeHandlerRegistry;


    /**
    * 标记该配置文件是否已经解析过
    */
    private boolean parsed;
    
    /**
    * 解析器模块,配置文件就由它进行解析
    */
    private final XPathParser parser;
    private String environment;
    
    /**
    * 默认反射工厂实现
    */
    private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
  • El segundo paso: use el método XMLConfigBuilderdel objeto Configuration parse()para obtener el Configurationobjeto.
    La implementación de este método es la siguiente:
  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;//将该配置文件标记为已解析
    
    //对配置文件中的configuration节点下的所有属性进行解析,并将解析到的信息封装到
    //XMLConfigBuilder对象的configuration属性中。
    parseConfiguration(parser.evalNode("/configuration"));
    //将填充好各种值的configuration返回
    return configuration;
  }

Después de leer este código, podemos saber que la segunda parte es analizar configurationla información debajo de los nodos en el archivo de configuración , inicializar XMLConfigBuilderlos configurationatributos en el objeto y luego devolver la inicialización configuration.

  • El tercer paso: use la información de configuración completa obtenida configurationcomo parámetro para llamar al SqlSessionFactory build(Configuration config)método para construir el SqlSessionFactoryobjeto

Primero llama a este método:

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

Este método crea una instancia de una SqlSessionFactoryclase de implementación de interfaz DefaultSqlSessionFactory. El
proceso de creación de instancias específico es el siguiente:

  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }

Es muy simple, pero acaba de configurationpasar al DefaultSqlSessionFactoryobjeto.
En este punto, hemos creado con éxito el SqlSessionFactoryobjeto.
Pero aquí, parece que hemos pasado por alto un enlace muy importante: ¿cómo maneja nuestro archivo UserMapperx.xml configurationlos nodos del archivo de configuración ?

¿Cómo manejar Mapper.xml al crear SqlSessionFactory?

Para saber cómo se procesa nuestro archivo Mapper.xml, primero debemos averiguar dónde se procesa.
En nuestro análisis antes de SqlSessionFactoryla hora del proceso de creación, tenemos que analizar XMLConfigBuilderlos Configuration parse()métodos, este método habría sido analizar el archivo de configuración. Luego, el procesamiento del archivo Mapper.xml también debe comenzar desde este lugar.
En Configuration parse()la llamada a un método importante void parseConfiguration(XNode root), su realización es como sigue;

 private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      
      //在这个地方处理的mappers
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

Analizamos mappersel lugar específico del procesamiento : su implementación es la siguiente:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

Este método parece muy complicado, pero podemos obtener al menos un poco de información:
hay tres formas de escribir la configuración del mapeador:

  <mappers>
    <mapper url=""/>
    <mapper class=""/>
    <mapper resource=""/>
  </mappers>

Este método parece muy complicado, pero son solo tres cosas: obtener el archivo del mapeador, crear un XMLMapperBuilderobjeto y analizar el archivo del mapeador.

  • Paso 1: Obtenga el archivo de mapeador especificado por cada nodo en función de la información de configuración. Tome una
    configuración de mapeador como ejemplo:
    InputStream inputStream = Resources.getResourceAsStream(resource);
    todavía utiliza una poderosa Resourcesclase de herramienta para cargar varios archivos de recursos .

  • Paso 2: crear XMLMapperBuilderobjetos. El proceso de
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    creación XMLMapperBuilderes básicamente instanciar y luego inyectar varios valores en los parámetros.

  • Paso 3: analizar el nodo del mapeador correspondiente
    mapperParser.parse();

La implementación específica de este método es la siguiente;

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) { //如果没有解析过
        //解析每个mapper节点中的信息
      configurationElement(parser.evalNode("/mapper"));
      //将当前文件加入已解析文件集合
      configuration.addLoadedResource(resource);
      //将mapper和命名空间进行绑定
      //本质就是将命名空间所对应的类和mapper文件都加入configuration中
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

Hay muchos detalles involucrados en esta parte. Si está familiarizado con el método de escritura de mapper en mybatis, sabrá que hay muchas etiquetas en mapper y el método de escritura es relativamente complicado. Después de prepararme, analizaré una ola.

Proceso de creación de SQLSession

Después de los esfuerzos anteriores, hemos obtenido el SqlSessionFactoryobjeto. Ahora necesitas crear el SqlSessionobjeto. Este proceso se completa con esta línea de código.
SqlSession sqlSession = factory.openSession();
Su implementación específica es la siguiente:

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

Este método implica un método muy importante:

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //创建执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      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();
    }
  }

El punto clave de este método es obtener el objeto de transacción y crear un ejecutor.
La implementación específica de la creación de un actuador es la siguiente:

  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 = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

Observando este método, la primera información que podemos obtener es que hay tres tipos de actuadores. Ellos son:
ExecutorType.SIMPLE: Este tipo no hace nada más, crea un PreparedStatement para cada instrucción
ExecutorType.REUSE: Este tipo reutiliza PreparedStatements.
ExecutorType.BATCH: este tipo se actualiza en lotes de este tipo, y es necesario distinguir la instrucción select en él para garantizar que la acción sea fácil de entender.

Tomemos el tipo ExecutorType.SIMPLE como ejemplo para ver algunas de las cosas que se hacen al crear un actuador.
executor = new SimpleExecutor(this, transaction);
Esta línea de código es esencialmente el constructor de SimpleExecutorla clase padre BaseExecutorque se llamó .
La implementación específica es la siguiente:

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<>();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

Se han inyectado algunas propiedades importantes en el constructor. Entre ellos, la clave es el transactionatributo. Este objeto incluye una serie de métodos para operar directamente la base de datos, como obtener la conexión de la base de datos, enviar y retroceder.
La interfaz es la siguiente:

public interface Transaction {

  /**
   * Retrieve inner database connection.
   * @return DataBase connection
   * @throws SQLException
   */
  Connection getConnection() throws SQLException;

  /**
   * Commit inner database connection.
   * @throws SQLException
   */
  void commit() throws SQLException;

  /**
   * Rollback inner database connection.
   * @throws SQLException
   */
  void rollback() throws SQLException;

  /**
   * Close inner database connection.
   * @throws SQLException
   */
  void close() throws SQLException;

  /**
   * Get transaction timeout if set.
   * @throws SQLException
   */
  Integer getTimeout() throws SQLException;

}

Volvamos al análisis ahora mismo SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit). Puede
crearlo cuando obtenga el constructor SqlSession. La
implementación específica es esta línea de código:
return new DefaultSqlSession(configuration, executor, autoCommit);
La implementación específica del constructor llamado es la siguiente:

  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

Podemos ver que al encapsular la información de configuración con el actuador, obtenemosSqlSession

¿Cómo opera SqlSession la base de datos?

Tomemos la consulta como ejemplo:

Object u= (User)sqlSession.selectOne("getUser", user);

La implementación específica es la siguiente:

  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

A partir de este código, podemos ver que este código es la función principal de List<T> list = this.selectList(statement, parameter);este código, el resto del código se usa para procesar el valor de retorno.

Luego analizamos selectList(statement, parameter)la realización específica de una onda :

  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }
  
  //最终调用了这个重载实现
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        /*MappedStatement就是对sql语句和相关配置信息的封装,
        基本上执行一个sql所需的信息,MappedStatement中都有*/
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

Este código, primero obtiene el MappedStatementobjeto que encapsula toda la información para ejecutar el sql , y luego llama al ejecutor para ejecutar el sql.
Sin embargo, antes de llamar al ejecutor, también procesa nuestros parámetros entrantes. El código para wrapCollection(parameter)
procesar los parámetros es muy simple. La lógica para procesar los parámetros es básicamente una especie de marca para los tipos de parámetros complejos. Los objetos normales no se procesan.

  private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
      StrictMap<Object> map = new StrictMap<>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap<Object> map = new StrictMap<>();
      map.put("array", object);
      return map;
    }
    return object;
  }

Después de que todo el trabajo de preparación esté listo, es el ejecutor el que ejecuta la consulta.

  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  //可以理解为对sql信息的进一步处理,更加接近jdbc
    BoundSql boundSql = ms.getBoundSql(parameter);
    //计算缓存的key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }
  
  //接下来调用了这个方法
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
      //尝试从缓存中读取
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
        //缓存中,查询结果为空,就继续查询
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  
  //如果没有缓存,或缓存无效的话,会调用这个方法,从数据库中查询
  
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
      //去数据库中查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
  
  //从数据库中查询的实现
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
    //进行查询
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
  
  
  
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
    //获取configuration对象
      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);
    }
  }
  
  
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }
  
  
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    //这里的sql就是可以执行的sql了
    String sql = boundSql.getSql();
    
    statement.execute(sql);
    //对查询的结果集进行处理
    return resultSetHandler.handleResultSets(statement);
  }

Esta parte también es muy complicada. En el futuro, estudiaré una ola y escribiré un blog.

Supongo que te gusta

Origin www.cnblogs.com/zofun/p/12728006.html
Recomendado
Clasificación