mybatis源码之创建SqlSessionFactory代码分析

这个mybatis源码分析系列会尽量全面地分析mybatis的核心执行流程、与springboot的集成流程。

系列文档:


待分析的课题

  • xml mapper独立使用源码分析
  • mapper接口独立使用源码分析
  • springboot集成源码分析
  • mybatis-plus核心源码分析
  • mybatis-plus与springboot集成源码分析

本文分析的内容

  • 开发环境
  • 示例代码
  • 创建SqlSessionFactory代码的第1部分:事务管理器、数据源的解析以及插件的解析

环境准备

开发环境

windows 10

java 1.8

mybatis版本

mybatis 3.5.6依赖:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.6</version>
</dependency>

示例代码 - xml配置方式

示例代码

public class XmlBlogMapperTest {

  private SqlSession session;

  @Before
  public void before() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    session = sessionFactory.openSession();
  }

  @After
  public void after() {
    session.close();
  }

  @Test
  public void testSelectBlogById() {
    String statement = "org.net5ijy.mybatis.test.BlogMapper.selectBlogById";
    Blog blog = session.selectOne(statement, 1);
    System.out.println(blog);
  }

  @Test
  public void testSelectBlogByBlogSearchParameter() throws ParseException {

    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    List<Integer> ids = new ArrayList<>();
    ids.add(1);
    ids.add(2);

    Blog blog = new Blog();
    blog.setContent("mybatis源码分析");
    blog.setTitle("mybatis");

    BlogSearchParameter parameter = new BlogSearchParameter();
    parameter.setId(1);
    parameter.setIds(ids);
    parameter.setCreateTime(format.parse("2020-01-01 00:00:00"));

    parameter.setBlog(blog);

    List<Blog> list = session.selectList(
        "org.net5ijy.mybatis.test.BlogMapper.selectBlogByParameter", parameter);

    for (Blog b : list) {
      System.out.println(b);
    }
  }
}

主配置文件mybatis-config.xml

<?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/BlogMapper.xml"/>
  </mappers>
</configuration>

BlogMapper.xml文件

<?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="org.net5ijy.mybatis.test.BlogMapper">
  <resultMap id="BlogResultMap" type="org.net5ijy.mybatis.test.entity.Blog">
    <id column="id" property="id"/>
    <id column="title" property="title"/>
    <id column="content" property="content"/>
    <result column="create_time" property="createTime"/>
    <result column="update_time" property="updateTime"/>
  </resultMap>

  <select id="selectBlogById" resultMap="BlogResultMap" 
          parameterType="java.lang.Integer">
    select * from blog where id = #{id}
  </select>

  <select id="selectBlogByParameter" resultMap="BlogResultMap"
    parameterType="org.net5ijy.mybatis.test.parameter.BlogSearchParameter">
    select *
    from blog
    <where>
      <if test="id != null">
        and id = #{id}
      </if>
      <if test="ids != null and ids.size > 0">
        and id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
          #{id}
        </foreach>
      </if>
      <if test="blog.title != null">
        and title like concat(concat('%',#{blog.title}),'%')
      </if>
      <if test="blog.content != null">
        and content like concat(concat('%',#{blog.content}),'%')
      </if>
      <if test="createTime != null">
        and create_time <![CDATA[ > ]]> #{createTime}
      </if>
    </where>
  </select>

</mapper>

创建SqlSessionFactory代码分析 - 第1部分

入口

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
session = sessionFactory.openSession();

创建SqlSessionFactory

SqlSessionFactory - Creates an SqlSession out of a connection or a DataSource.

在示例代码中,通过这行代码创建SqlSessionFactory:

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

build方法:

public SqlSessionFactory build(
    InputStream inputStream, String environment, Properties properties) {
  try {
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    // parser.parse()会解析配置文件,返回org.apache.ibatis.session.Configuration对象
    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.
    }
  }
}

// 创建了一个DefaultSqlSessionFactory对象
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

所以,重点就是parser.parse()方法。

XMLConfigBuilder.parse()方法

这个方法非常重要,主要做了以下事情:

  • 主配置文件的解析
    • 解析事务管理器和数据源
    • 插件
  • mapper xml文件的解析
    • 解析parameter map
    • 解析result map
    • 解析statement

此处只列出了核心的内容,其余的例如别名、settings之类的就不做重点分析了。

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

private void parseConfiguration(XNode root) {
  try {
    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);
    // 加载事务管理器、数据源
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    // 解析mapper xml文件
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration.");
  }
}

解析事务管理器和数据源

配置

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

源码入口

environmentsElement(root.evalNode("environments"));

environmentsElement方法:

private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      // 解析environments的default属性 = development
      environment = context.getStringAttribute("default");
    }
    // 遍历所有的environment节点
    for (XNode child : context.getChildren()) {
      // 解析environment的id属性
      String id = child.getStringAttribute("id");
      // 判断environment的id属性 = development
      if (isSpecifiedEnvironment(id)) {
        // 解析transactionManager
        TransactionFactory txFactory = 
            transactionManagerElement(child.evalNode("transactionManager"));
        // 解析dataSource
        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());
      }
    }
  }
}

解析transactionManager

事务管理器的类型:

  • JDBC - Transaction that makes use of the JDBC commit and rollback facilities directly. It relies on the connection retrieved from the dataSource to manage the scope of the transaction. Delays connection retrieval until getConnection() is called. Ignores commit or rollback requests when autocommit is on.
  • MANAGED - Transaction that lets the container manage the full lifecycle of the transaction. Delays connection retrieval until getConnection() is called. Ignores all commit or rollback requests. By default, it closes the connection but can be configured not to do it.

是在这里注册的:

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

  // ...
}

这是mybatis内置两种Transaction实现,如果在spring环境中,使用的是SpringManagedTransaction实现。

根据我们的配置,使用的是JdbcTransactionFactory和JdbcTransaction实现。

这行代码:

TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));

transactionManagerElement方法:

private TransactionFactory transactionManagerElement(XNode context) throws Exception {
  if (context != null) {
    // type = jdbc
    String type = context.getStringAttribute("type");
    Properties props = context.getChildrenAsProperties();
    // 使用type到typeAliasRegistry获取到对应的实现
    // 并调用默认无参构造方法实例化
    TransactionFactory factory = (TransactionFactory)
        resolveClass(type).getDeclaredConstructor().newInstance();
    // 设置属性
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a TransactionFactory.");
}

解析dataSource

这行代码:

DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();

DataSourceFactory接口:

public interface DataSourceFactory {

  void setProperties(Properties props);

  DataSource getDataSource();
}

dataSource的type有三种类型:

  • JNDI - JndiDataSourceFactory从jndi环境获取数据源,此处不做详细介绍
  • POOLED - PooledDataSourceFactory使用连接池机制
  • UNPOOLED - UnpooledDataSourceFactory不使用连接池机制

按照我们的配置,使用的应该是PooledDataSourceFactory类型。

dataSourceElement方法:

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
  if (context != null) {
    // 获取到数据源实现
    // JNDI、POOLED、UNPOOLED或者使用DataSourceFactory的实现类全名
    // 所以如果mybatis集成druid就可以从这里入手
    String type = context.getStringAttribute("type");
    Properties props = context.getChildrenAsProperties();
    // 实例化
    DataSourceFactory factory = (DataSourceFactory)
        resolveClass(type).getDeclaredConstructor().newInstance();
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

这个DataSource就是java.sql.DataSource类型,C3P0、Druid等第三方库都有实现,mybatis内置了两个实现:

  • POOLED - PooledDataSource使用连接池机制
  • UNPOOLED - UnpooledDataSource不使用连接池机制
UnpooledDataSource实现

核心代码:

// 每次都获取一个新的Connection对象
private Connection doGetConnection(Properties properties) throws SQLException {
  initializeDriver();
  Connection connection = DriverManager.getConnection(url, properties);
  configureConnection(connection);
  return connection;
}

private synchronized void initializeDriver() throws SQLException {
  if (!registeredDrivers.containsKey(driver)) {
    Class<?> driverType;
    try {
      if (driverClassLoader != null) {
        driverType = Class.forName(driver, true, driverClassLoader);
      } else {
        driverType = Resources.classForName(driver);
      }
      Driver driverInstance = (Driver) driverType.getDeclaredConstructor().newInstance();
      DriverManager.registerDriver(new DriverProxy(driverInstance));
      registeredDrivers.put(driver, driverInstance);
    } catch (Exception e) {
      throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
    }
  }
}

private void configureConnection(Connection conn) throws SQLException {
  if (defaultNetworkTimeout != null) {
    conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
  }
  if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
    conn.setAutoCommit(autoCommit);
  }
  if (defaultTransactionIsolationLevel != null) {
    conn.setTransactionIsolation(defaultTransactionIsolationLevel);
  }
}
PooledDataSource实现

核心代码:

public class PooledDataSource implements DataSource {

  private static final Log log = LogFactory.getLog(PooledDataSource.class);

  private final PoolState state = new PoolState(this);

  private final UnpooledDataSource dataSource;

  // OPTIONAL CONFIGURATION FIELDS
  protected int poolMaximumActiveConnections = 10;
  protected int poolMaximumIdleConnections = 5;
  protected int poolMaximumCheckoutTime = 20000;
  protected int poolTimeToWait = 20000;
  protected int poolMaximumLocalBadConnectionTolerance = 3;
  protected String poolPingQuery = "NO PING QUERY SET";
  protected boolean poolPingEnabled;
  protected int poolPingConnectionsNotUsedFor;

  // ...

  @Override
  public Connection getConnection() throws SQLException {
    // 获取到的是一个代理对象
    return popConnection(dataSource.getUsername(), dataSource.getPassword())
        .getProxyConnection();
  }

  private PooledConnection popConnection(
      String username, String password) throws SQLException {
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;

    while (conn == null) {
      synchronized (state) {
        if (!state.idleConnections.isEmpty()) {
          // Pool has available connection
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection ");
          }
        } else {
          // Pool does not have available connection
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // Can create new connection
            conn = new PooledConnection(dataSource.getConnection(), this);
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }
          } else {
            // Cannot create new connection
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
              // Can claim overdue connection
              state.claimedOverdueConnectionCount++;
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
              state.accumulatedCheckoutTime += longestCheckoutTime;
              state.activeConnections.remove(oldestActiveConnection);
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                try {
                  oldestActiveConnection.getRealConnection().rollback();
                } catch (SQLException e) {
                  log.debug("Bad connection. Could not roll back");
                }
              }
              conn = 
                  new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
              conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } else {
              // Must wait
              try {
                if (!countedWait) {
                  state.hadToWaitCount++;
                  countedWait = true;
                }
                if (log.isDebugEnabled()) {
                  log.debug("Waiting ...");
                }
                long wt = System.currentTimeMillis();
                state.wait(poolTimeToWait);
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
              } catch (InterruptedException e) {
                break;
              }
            }
          }
        }
        if (conn != null) {
          // ping to server and check the connection is valid or not
          if (conn.isValid()) {
            if (!conn.getRealConnection().getAutoCommit()) {
              conn.getRealConnection().rollback();
            }
            conn.setConnectionTypeCode(
                assembleConnectionTypeCode(dataSource.getUrl(), username, password));
            conn.setCheckoutTimestamp(System.currentTimeMillis());
            conn.setLastUsedTimestamp(System.currentTimeMillis());
            state.activeConnections.add(conn);
            state.requestCount++;
            state.accumulatedRequestTime += System.currentTimeMillis() - t;
          } else {
            if (log.isDebugEnabled()) {
              log.debug("A bad connection ...");
            }
            state.badConnectionCount++;
            localBadConnectionCount++;
            conn = null;
            if (localBadConnectionCount > 
                (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
              if (log.isDebugEnabled()) {
                log.debug("PooledDataSource: Could not get good connection.");
              }
              throw new SQLException("...");
            }
          }
        }
      }
    }

    if (conn == null) {
      if (log.isDebugEnabled()) {
        log.debug("PooledDataSource: Unknown severe error condition ...");
      }
      throw new SQLException("...");
    }

    return conn;
  }

  // 归还连接
  protected void pushConnection(PooledConnection conn) throws SQLException {

    synchronized (state) {
      state.activeConnections.remove(conn);
      if (conn.isValid()) {
        if (state.idleConnections.size() < poolMaximumIdleConnections &&
            conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          PooledConnection newConn = 
              new PooledConnection(conn.getRealConnection(), this);
          state.idleConnections.add(newConn);
          newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
          newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
          conn.invalidate();
          if (log.isDebugEnabled()) {
            log.debug("Returned connection to pool.");
          }
          state.notifyAll();
        } else {
          state.accumulatedCheckoutTime += conn.getCheckoutTime();
          if (!conn.getRealConnection().getAutoCommit()) {
            conn.getRealConnection().rollback();
          }
          conn.getRealConnection().close();
          if (log.isDebugEnabled()) {
            log.debug("Closed connection " + conn.getRealHashCode() + ".");
          }
          conn.invalidate();
        }
      } else {
        if (log.isDebugEnabled()) {
          log.debug("A bad connection ...");
        }
        state.badConnectionCount++;
      }
    }
  }
}
PoolState类

封装当前空闲、活跃连接等信息。

public class PoolState {

  protected PooledDataSource dataSource;

  protected final List<PooledConnection> idleConnections = new ArrayList<>();
  protected final List<PooledConnection> activeConnections = new ArrayList<>();
  protected long requestCount = 0;
  protected long accumulatedRequestTime = 0;
  protected long accumulatedCheckoutTime = 0;
  protected long claimedOverdueConnectionCount = 0;
  protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
  protected long accumulatedWaitTime = 0;
  protected long hadToWaitCount = 0;
  protected long badConnectionCount = 0;
}
PooledConnection类

这个类使用代理方式实现,覆盖了默认的close方法实现了关闭连接归还连接池的机制:

// 这个类实现连接池的方式是比较基础而且最容易上手的
class PooledConnection implements InvocationHandler {

  private static final String CLOSE = "close";
  private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };

  private final int hashCode;
  private final PooledDataSource dataSource;
  private final Connection realConnection;
  private final Connection proxyConnection;
  private long checkoutTimestamp;
  private long createdTimestamp;
  private long lastUsedTimestamp;
  private int connectionTypeCode;
  private boolean valid;

  public PooledConnection(Connection connection, PooledDataSource dataSource) {
    this.hashCode = connection.hashCode();
    this.realConnection = connection;
    this.dataSource = dataSource;
    this.createdTimestamp = System.currentTimeMillis();
    this.lastUsedTimestamp = System.currentTimeMillis();
    this.valid = true;
    // 创建代理
    this.proxyConnection = (Connection)
        Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
  }

  // 返回代理对象
  public Connection getProxyConnection() {
    return proxyConnection;
  }

  /**
   * Required for InvocationHandler implementation.
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    // 覆盖掉默认的close方法,把连接归还给连接池
    if (CLOSE.equals(methodName)) {
      dataSource.pushConnection(this);
      return null;
    }
    try {
      if (!Object.class.equals(method.getDeclaringClass())) {
        // issue #579 toString() should never fail
        // throw an SQLException instead of a Runtime
        checkConnection();
      }
      // 调用真实的方法
      return method.invoke(realConnection, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  private void checkConnection() throws SQLException {
    if (!valid) {
      throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
    }
  }
}

构建Environment

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

插件 - 拦截器

插件 - 拦截器概述

mybatis的插件 - 拦截器机制:开发者编写Interceptor接口的实现类,对mybatis中的特定组件的特定方法做拦截实现,在主配置文件通过plugins注册使其生效。

配置方式

<configuration>
  <!-- other code -->
  <plugins>
    <plugin interceptor="org.net5ijy.mybatis.test.interceptor.PrintSqlInterceptor"/>
  </plugins>
  <!-- other code -->
</configuration>

源码分析

pluginElement(root.evalNode("plugins"));

pluginElement方法:

private void pluginElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      String interceptor = child.getStringAttribute("interceptor");
      Properties properties = child.getChildrenAsProperties();
      Interceptor interceptorInstance = (Interceptor)
          resolveClass(interceptor).getDeclaredConstructor().newInstance();
      interceptorInstance.setProperties(properties);
      configuration.addInterceptor(interceptorInstance);
    }
  }
}

实例化和设置属性的方式与之前的事务管理器、数据源一致。

configuration.addInterceptor(interceptorInstance);

在Configuration中使用InterceptorChain保存拦截器,在InterceptorChain中使用一个Interceptor的集合保存所有的拦截器。

以上这些内容就是初始化阶段的插件 - 拦截器加载。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值