Mybatis源码学习(二)简单Demo的运行原理

0 回顾

上一节,我们写了一个简单Demo,并看到了它的运行结果,这一节,我们分析一下Mybatis执行sql的原理。

 

public class MybatisMain {
  public static void main(String[] args) throws IOException
  {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    Blog blog = sqlSession.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
    System.out.println(blog.getContext());
  }
}

1 解析配置文件

InputStream inputStream = Resources.getResourceAsStream(resource);

这一行代码,解析mybatis-config.xml文件,得到一个输入流。我们把xml文件也贴出来

<configuration>
    <properties resource="jdbc.properties"/>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/BlogMapper.xml"/>
    </mappers>
</configuration>

这个xml中有3个配置:jdbc.properties,environments,mappers

2 SqlSessionFactory

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

这里,得到一个SqlSessionFactory对象,执行完这行代码,实现了对上述xml文件的解析,把配置都保存在该对象的成员变量configuration中。

 

 

这些配置信息包括:数据库连接信息、所有Mapper

3 SqlSession

SqlSession sqlSession = sqlSessionFactory.openSession();

该行代码返回SqlSession对象,该对象中包含一个executor变量,默认为CachingExecutor对象(一级缓存)。这个executor对象是实际查询时的执行器。

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

这里可以看出来,mybatis默认是手动提交事务的(详情见附录2)。JDBC默认是自动提交,spring整合mybatis后默认是自动提交。

这里调用了openSessionFromDataSource方法

  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);//public enum ExecutorType {SIMPLE, REUSE, BATCH},默认为SIMPLE.为什么默认为SIMPLE?Configuration对象的defaultExecutorType属性默认值就是SIMPLE,除非对其进行了显式设置
      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();
    }
  }

 上面代码创建了使用了很多对象的引用,比如Environment,TransactionFactory,DataSourceFactory等,他们是如何实例化的,以及他们的具体类型为什么是JdbcTransactionFactory,PooledDataSourceFactory?为什么默认开启一级缓存?感兴趣的话请看最下面的附录。

创建Executor,默认创建的是SimpleExecutor,如果开启了一级缓存,则使用CachingExecutor进行了封装。

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

最终返回一个包含CachingExecutor对象的SqlSession。

 

 

4 执行查询

Blog blog = sqlSession.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);

 执行selectOne方法

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

执行selectList方法

  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

调用CachingExecutor的query方法,判断缓存中是否有记录,如果有,取缓存中的记录,如果没有,则调用SimpleExecutor中的query查询数据库

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

然后,执行SimpleExecutor的doQuery方法

  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      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);
    }
  }

这个方法就是我们使用JDBC查询数据库的代码。

我们再回到刚开始的这行代码

Blog blog = sqlSession.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);

从上面分析来看,我们执行这行代码之后,最终返回给我们的是一个Blog对象,包含了数据库记录。

这就是我们执行mybatis的过程。

 

附录1 mybatis-config.xml的解析过程

mybatis-config.xml文件是mybatis的主要配置文件,里面配置了Enviroment列表,每个Environment中transactionManager的类型、dataSource的类型等重要配置。以及所有的mappers

<?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>
  <properties resource="jdbc.properties"/>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="mapper/UserMapper.xml"/>
  </mappers>
</configuration>

在上面main函数中,

 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 

这行代码用于实例化SqlSessionFactory,

然后调用了

public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

然后

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

上面代码首先创建一个XMLConfigBuilder实例,在其构造方法中创建一个Configuration实例。

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

其中Configuration实例中有一些比较重要的东西,比如typeAliasRegistry对象,其中包含了72种类型对应的默认具体类。比如:

    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

这些配置对后续的对象实例化非常有用,决定了这些对象的具体实现类是什么(比如TransactionFactory的默认实现类是JdbcTransactionFactory)

 

然后,我们回到build方法,我们看一下其中调用的parse方法

  public Configuration parse() {
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

然后

  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"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

我们主要看一下标红的这一行

下面代码进行了删减,只看重要部分

  private void environmentsElement(XNode context) throws Exception {
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
  }

我们以实例化TransactionFactory为例,在下面代码中获取xml文件中的type: <transactionManager type="JDBC"/> ,然后

  private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a TransactionFactory.");
  }

这里会根据上面提到的typeAliasRegistry对象中保存的72种对应关系决定TransactionFactory的具体实现类。

也就是解决了这个疑问,为什么创建的TransactionFactory是JdbcTransactionFactory?( <transactionManager type="JDBC"/> )

同样的道理,DataSourceFactory对应的具体实现类是PooledDataSourceFactory   ( <dataSource type="POOLED"> )

然后,我们返回再看一下environment方法

  private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
          break;
        }
      }
    }
  }

如下代码把txFactory对象和dataSource对象放到了Environment的实例中,然后把这个Environment实例放到configuration对象中。

          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());

至此,解析xml文件中的<environments>标签完毕( environmentsElement(root.evalNode("environments")); )

其他标签的解析后面有时间再添加。

附录2 有关自动提交

 

附录3 纯JDBC

相关介绍参考:https://www.liaoxuefeng.com/wiki/1252599548343744/1321748435828770

public class PureJDBC {
  static final String DB_URL = "jdbc:mysql://xxx:3306/xxx?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8&zeroDateTimeBehavior=CONVERT_TO_NULL";
  static final String USER = "xxx";
  static final String PASS = "xxx";
  public static void main(String[] args) {
    Connection conn = null;
    Statement stmt = null;
    try{
      Class.forName("com.mysql.jdbc.Driver");
      //开启一个连接
      conn = DriverManager.getConnection(DB_URL, USER, PASS);
      System.out.println("是否自动提交:"+conn.getAutoCommit());
      //执行查询
      stmt = conn.createStatement();
      String sql = "select * from t_user where uid='xxx'";
      ResultSet rs = stmt.executeQuery(sql);
      while(rs.next()){
        String uname = rs.getString("uname");
        System.out.print("uname: " + uname);
      }
      rs.close();
    }catch(SQLException se){
      se.printStackTrace();
    }catch(Exception e){
      e.printStackTrace();
    }finally{
      try{
        if(stmt!=null)
          conn.close();
      }catch(SQLException se){}
      try{
        if(conn!=null)
          conn.close();
      }catch(SQLException se){
        se.printStackTrace();
      }
    }
  }
}

执行结果

是否自动提交:true
uname: xxx

从执行结果看,JDBC方式,事务是自动提交的

 

posted @ 2022-01-09 19:42  zhenjingcool  阅读(110)  评论(0编辑  收藏  举报