1. 程式人生 > 其它 >Mybatis原始碼解讀系列(七)-SqlSessionFactory&SqlSession對Mybatis的初始化構架&SqlSession對Mapper介面的執行

Mybatis原始碼解讀系列(七)-SqlSessionFactory&SqlSession對Mybatis的初始化構架&SqlSession對Mapper介面的執行

技術標籤:學習筆記javaMybatis原始碼解析mybatis

一、SqlSessionFactory的建立

1、demo程式碼

Reader reader = Resources
    .getResourceAsReader("org/apache/ibatis/submitted/maptypehandler/mybatis-config.xml")) {
  sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration> <properties resource="org/mybatis/example/config.properties"/> <typeHandlers> <typeHandler handler="org.apache.ibatis.submitted.maptypehandler.LabelsTypeHandler" /> </typeHandlers> <environments default="development"
>
<environment id="development"> <transactionManager type="JDBC"> <property name="" value="" /> </transactionManager> <dataSource type="UNPOOLED"> <
property
name="driver" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:maptypehandler" /> <property name="username" value="sa" /> </dataSource> </environment> </environments> <mappers> <mapper class="org.apache.ibatis.submitted.maptypehandler.Mapper" /> </mappers> </configuration>

2、demo程式碼說明

​ 1)、可以看到這裡是先通過resource目錄地址將對應關於mybatis初始化的xml檔案讀取為一個Reader流物件。

​ 2)、然後再是SqlSessionFactoryBuilder物件的建立在呼叫build方法,入參是配置檔案的讀入流。

​ 3)、build方法

public SqlSessionFactory build(Reader reader) {
  return build(reader, null, null);
}
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 {
    ......
  }
}

二、SqlsessionFactory的構建過程原始碼分析

1、XMLConfigBuilder的建立

public class XMLConfigBuilder extends BaseBuilder {
    ......
  public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}

​ 可以看到這裡首先是通過reader建立了一個XPathParser物件,再將其與入參environment、props呼叫this。

1)、XPathParser的建立

public class XPathParser {

  private final Document document;
  private boolean validation;
  private EntityResolver entityResolver;
  private Properties variables;
  private XPath xpath;

  public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver)   {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(reader));
  }
    private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver)   {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }
}

​ 可以看到這裡這裡會將輸入流的內容讀取建立為一個Document物件。我們再看下entityResolver(XMLMapperEntityResolver)

2)、XMLMapperEntityResolver

/**
 * Offline entity resolver for the MyBatis DTDs.
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class XMLMapperEntityResolver implements EntityResolver {

  private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
  private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
  private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";

  private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
    ......

​ 通過這個類的一些類變數即註釋可以看到,這個類是用來解析DTD檔案的(可以DTD檔案來約束XML檔案的節點的名稱、順序等)。

​ 通過上面1)、2)我們可以初步判斷這個XPathParser類用來處理mybatis的初始配置的XML檔案的內容的。

3)、XMLConfigBuilder建構函式

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物件。然後是將入參賦值。

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

  typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
  typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
  ......
  languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
  languageRegistry.register(RawLanguageDriver.class);
}

​ 這裡是將一些工廠建立類設定對應別名,用來以後根據別名建立對應物件。同時可以看到這裡將將全域性的variables設定到了Configurartion。

2、XMLConfigBuilder.parse()

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

​ 首先通過 [ parsed = true ]標記這個解析流已經處理了,再通過parseConfiguration去解析xml檔案的<configuration>節點。起始就是對整個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);
  }
}

​ 這個方法我們這個系列的第3篇有梳理。

3、SqlSessionFactory的初始化

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}
public DefaultSqlSessionFactory(Configuration configuration) {
  this.configuration = configuration;
}

​ DefaultSqlSessionFactory的建立。

三、DefaultSqlSessionFactory

接下來我們來看下DefaultSqlSessionFactory,不過我們首先來開下DefaultSqlSessionFactory實現的介面SqlessionFactory

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);

  SqlSession openSession(Connection connection);

  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);

  SqlSession openSession(ExecutorType execType, boolean autoCommit);

  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}

​ 這個介面的方法主要是兩個方面,建立獲取SqlSession&獲取Configuration。下面我們來看下沒有引數的openSession方法(這些方法都會調到DefaultSqlSessionFactory一個底層的openSessionFromDataSource方法)

1、openSession()

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

​ 可以看到這裡的入參是通過getDefaultExecutorType來獲取ExecutorType,中間的null是TransactionIsolationLevel是事務管理級別,false是是否自動提交引數。

2、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);
    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();
  }
}
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
  if (environment == null || environment.getTransactionFactory() == null) {
    return new ManagedTransactionFactory();
  }
  return environment.getTransactionFactory();
}

​ 這裡是根據Envirement獲取事務工廠,再通過事務工廠去建立對應的事務,之後再根據執行器類別去建立對應的執行器,有這些再去建立對應的DefaultSqlSession。

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

四、SqlSession介面

image-20201227084423834

​ 這個就是對應的sql會話的增刪改查相關的一些方法。

五、DefaultSqlSession

1、結構&成員變數&構造方法

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;

  private final boolean autoCommit;
  private boolean dirty;
  private List<Cursor<?>> cursorList;

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

​ 這裡根據名稱及全面 的介紹就能明白了,就不再贅敘了。

2、方法

1)、selectList(…)

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    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();
  }
}

​ 可以看到這裡就是從configuration的mappedStatements(Map<String, MappedStatement> mappedStatements)獲取對應介面方法MappedStatement,再通過執行器的查詢方法去處理。

2)、update(String statement, Object parameter)

@Override
public int update(String statement, Object parameter) {
  try {
    dirty = true;
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.update(ms, wrapCollection(parameter));
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

​ 這裡與前面類似。

3、insert()&update()

@Override
public int insert(String statement, Object parameter) {
  return update(statement, parameter);
}

@Override
public int update(String statement) {
  return update(statement, null);
}

六、Mapper介面代理物件的獲取

1、demo

@Test
void testGetNamesAndItemsLinked() {
  try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    SPMapper spMapper = sqlSession.getMapper(SPMapper.class);

    List<Name> names = spMapper.getNamesAndItemsLinked();
    assertEquals(4, names.size());
    ......
  }
}

​ 這裡就是首先通過DefaultSqlSessionFactory獲取DefaultSqlSession,再通過DefaultSqlSession去獲取Mapper介面對應的代理物件

2、getMapper(Class<T> type)

@Override
public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

​ 可以看到這裡就是通過MapperProxyFactory代理工廠去建立對應的代理物件。

3、MapperProxy

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -4724728412955527868L;
  private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
      | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
  private static final Constructor<Lookup> lookupConstructor;
  private static final Method privateLookupInMethod;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;

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

​ 這裡就是對映代理,不過這裡中間有用到一些新特性,以及各種處理,為例不在這一篇增加新內容,我們跳過的一些細節。

1)、PlainMethodInvoker

private static class PlainMethodInvoker implements MapperMethodInvoker {
  private final MapperMethod mapperMethod;

  public PlainMethodInvoker(MapperMethod mapperMethod) {
    super();
    this.mapperMethod = mapperMethod;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return mapperMethod.execute(sqlSession, args);
  }
}

​ 然後是Mapper方法呼叫者,去處理對應的方法。這裡的MapperMethod就是對應待執行方法的內容描敘。再通過invoke去執行對應方法。

2)、MapperMethod

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
public static class MethodSignature {

  private final boolean returnsMany;
  private final boolean returnsMap;
  private final boolean returnsVoid;
  private final boolean returnsCursor;
  private final boolean returnsOptional;
  private final Class<?> returnType;
  private final String mapKey;
  private final Integer resultHandlerIndex;
  private final Integer rowBoundsIndex;
  private final ParamNameResolver paramNameResolver;

​ SqlCommand是用來描敘執行型別的種類的,然後MethodSignature通過成員變數的名稱就能理解。

3)、execute(SqlSession sqlSession, Object[] args)

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
    case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT:
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {
        result = executeForCursor(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
        if (method.returnsOptional()
            && (result == null || !method.getReturnType().equals(result.getClass()))) {
          result = Optional.ofNullable(result);
        }
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements();
      break;
    default:
      throw new BindingException("Unknown execution method for: " + command.getName());
  }
  ......
  }
  return result;
}

​ 這個execute方法就是通過sqlSession去執行具體的方法了。然後(case SELECT)這種型別我們可以其會有各種(execute…)型別,但最後還是會落到SqlSession中,而SqlSession則會再去呼叫Executor。

​ 至此整個Mybatis原始碼解析系列的主要邏輯就結束了,之後可能再寫一些番外。例如之後複習學習設計模式的時候可以看下Spring&Mybatis這些框架對其的應用。