Mybatis source code analysis one

More exciting content can visit my independent blog

We start with the simplest piece of code and analyze the general workflow of mybatis. Then analyze some characteristics of mybatis from the code details.

Basic code examples

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

The User class is actually a pojo with only id, username, and password. I will not paste the code.
Of course, mybatis must have the corresponding sql code.

<?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>

There are also configurations that are indispensable to any framework.

<?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>

The code has been pasted out. Let's take this code as an example to analyze the execution process of mybatis.

Perform process analysis

The creation process of SqlSessionFactory

The first line of valid code is this InputStream inputStream = Resources.getResourceAsStream(resource);place, Resourcesa tool class, and its role is to facilitate us to load various resource files. It does not matter how it is implemented internally. In short, what this line of code does is simply load our configuration file.

Our highlight is actually this line of code. SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
We divided this line of code into two parts. The first part is new SqlSessionFactoryBuilder()that this does not need to be explained. The role of this part is to create an SqlSessionFactoryBuilderobject. The second part is to call the SqlSessionFactory build(Reader reader)method of the object . This method Very important, this method actually calls another overloaded method, and its specific implementation is as follows:

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

This code, if we don't consider other things, this code actually does three things, using the configuration file we passed in reader(the latter two parameters are passed when calling null), create an XMLConfigBuilderobject, Then call the parse()method of the object , get an Configurationobject, use the Configurationobject, use the build(Configuration cofig)method, create the SqlSessionFactoryobject, and then return the SqlSessionFactoryobject.

Below we explain what these three steps did.

  • Step 1: Create XMLConfigBuilderobjects.
    Although it looks simple and new, the object is created. In fact, it is still relatively complicated. Various overloaded constructors are called layer by layer, and finally came to this 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;
  }

Since basically all the constructors do is assign values ​​to attributes, then we have to analyze a wave XMLConfigBuilderof what attributes are there and what their role is.

//下面三个是继承自父类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();
  • The second step: use the XMLConfigBuilderobject's Configuration parse()method to get the Configurationobject.
    The implementation of this method is as follows:
  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;
  }

After reading this code, we can know that the second part is actually to parse configurationthe information under the nodes in the configuration file , to initialize XMLConfigBuilderthe configurationattributes in the object, and then return the initialization configuration.

  • The third step: use the full configuration information obtained configurationas a parameter to call the SqlSessionFactory build(Configuration config)method to build the SqlSessionFactoryobject

It first calls this method:

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

This method instantiates an SqlSessionFactoryinterface implementation class DefaultSqlSessionFactory. The
specific instantiation process is as follows:

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

It's very simple, but just configurationpassed to the DefaultSqlSessionFactoryobject.
At this point we have successfully created the SqlSessionFactoryobject.
But here, we seem to have overlooked a very important link. How does our UserMapperx.xml file configurationhandle the nodes of the configuration file ?

How to handle Mapper.xml when creating SqlSessionFactory?

To know how our Mapper.xml file is processed, we must first find out where it is processed.
In our analysis before SqlSessionFactorythe time of the creation process, we have to analyze XMLConfigBuilderthe Configuration parse()methods, this method would have been parsing the configuration file. Then processing Mapper.xml file should also start from this place.
In Configuration parse()the call to an important method void parseConfiguration(XNode root), its realization is as follows;

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

We look mappersat the specific place of processing : its implementation is as follows:

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.");
          }
        }
      }
    }
  }

This method looks very complicated, but we can get at least a little information:
There are three ways to write mapper configuration:

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

This method looks very complicated, but it is just three things, get the mapper file, create an XMLMapperBuilderobject, and parse the mapper file.

  • Step 1: Obtain the mapper file specified by each node based on the configuration information. Take a mapper
    configuration as an example:
    InputStream inputStream = Resources.getResourceAsStream(resource);
    it still uses a powerful Resourcestool class for loading various resource files .

  • Step 2: Create XMLMapperBuilderobjects. The process of
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    creation XMLMapperBuilderis basically to instantiate and then inject various values ​​in the parameters.

  • Step 3: Analyze the corresponding mapper node
    mapperParser.parse();

The specific implementation of this method is as follows;

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

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

There are many details involved in this part. If you are familiar with the writing method of mapper in mybatis, you will know that there are many tags in mapper and the writing method is more complicated. All the source code of this part is actually very complicated, so this part After preparing, I will analyze a wave.

SqlSession creation process

After the previous efforts, we have obtained the SqlSessionFactoryobject. Now you need to create the SqlSessionobject. This process is completed by this line of code.
SqlSession sqlSession = factory.openSession();
Its specific implementation is as follows:

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

This method involves a very important method:

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

The key point of this method is to get the transaction object and create an executor.
The specific implementation of creating an actuator is as follows:

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

Observing this method, the first information we can get is that there are three types of actuators. They are:
ExecutorType.SIMPLE: This type does not do anything else, it creates a PreparedStatement for each statement
ExecutorType.REUSE: This type reuses PreparedStatements.
ExecutorType.BATCH: This type is updated in batches of this type, and it is necessary to distinguish the select statement in it to ensure that the action is easy to understand.

Let's take the ExecutorType.SIMPLE type as an example to look at some of the things that are done when creating an actuator.
executor = new SimpleExecutor(this, transaction);
This line of code is essentially the constructor of SimpleExecutorthe parent class BaseExecutorthat was called .
The specific implementation is as follows:

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

Some important properties have been injected into the constructor. Among them, the key is the transactionattribute. This object includes a series of methods for directly operating the database, such as obtaining a database connection, submitting, and rolling back.
The interface is as follows:

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;

}

Let's go back to the analysis just now SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit). You
can create it when you get the constructor SqlSession. The
specific implementation is this line of code:
return new DefaultSqlSession(configuration, executor, autoCommit);
The specific implementation of the called constructor is as follows:

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

We can see that by encapsulating the configuration information with the actuator, we getSqlSession

How does SqlSession operate the database

Let's take the query as an example:

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

The specific implementation is as follows:

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

From this code, we can see that this code is the main role of List<T> list = this.selectList(statement, parameter);this code, the rest of the code is used to process the return value.

Then we analyze selectList(statement, parameter)the specific realization of a wave :

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

This code, first get the MappedStatementobject that encapsulates all the information to execute the sql , and then call the executor to execute the sql.
However, before calling the executor, it also processes our incoming parameters. The code for wrapCollection(parameter)
processing parameters is very simple. The logic for processing parameters is basically a kind of mark for complex parameter types. Normal objects are not processed.

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

After all the preparation work is ready, it is the executor to execute the query.

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

This part is also very complicated. In the future, I will study a wave and write a blog.

Guess you like

Origin www.cnblogs.com/zofun/p/12728006.html