mybatis 源碼分析(一)框架結構概覽

本篇博客將主要對 mybatis 總體介紹,包括 mybatis 的項目結構,執行的主要流程,初始化流程,API 等各模塊進行簡單的串聯,讓你可以對 mybatis 有一個總體的把握。另外在 mybatis 源碼的閱讀過程當中,若是不想寫 demo 能夠直接使用項目中的單元測試;html

1、mybatis 結構介紹

mybatis的主要功能和使用 demo,在網上已經有不少了我就再也不囉嗦了,同時 官方文檔 也很是的詳細;另外 mybatis 中使用了多種設計模式,包括建造者、動態代理、策略、裝飾器模式等,在查看源碼的時候,最好先對這些設計模式有必定的瞭解;java

其中 mybatis 的模塊結構以下:mysql

<img src="https://img2018.cnblogs.com/blog/1119937/201908/1119937-20190815185530368-1985790502.png" width = "800" alt="" align=center />sql

mybatis 的執行流程以下:數據庫

  • 首先經過 Java API 或者 XML 配置完成初始化,最終全部的配置都在 Configuration 類中維護;
  • 而後經過 SqlSessionFactory 獲得 SqlSession,這裏 SqlSession 就是 mybatis 的頂層 API 了,主要經過他完成數據庫的增刪改查等操做;
  • 而後 SqlSession 將具體的操做委託給 Executor 執行,Executor 就是 mybatis 的調度核心了,主要職責有 SQL 語句生成、一二級緩存維護和事務的相關操做;
  • 而後 Executor 將數據庫相關的操做委託給 StatementHandler,StatementHandler 中完成了 mybatis 最核心的工做,包括參數綁定,指定 SQL 語句,結果集映射等;

具體過程如圖所示:apache

<img src="https://img2018.cnblogs.com/blog/1119937/201908/1119937-20190815185556997-769547107.png" width = "650" alt="" align=center />設計模式

2、初始化

mybatis 中包含了不少的配置項,具體每一項的講解 官網 也很詳細,其結構大體以下:(另外正如上面說的 mybatis 的配置項最後都由 Configuration 類維護,這其實就是外觀模式)緩存

configuration(配置)
  properties(屬性)
  settings(設置)
  typeAliases(類型別名)
  typeHandlers(類型處理器)
  objectFactory(對象工廠)
  plugins(插件)
  environments(環境配置)
    environment(環境變量)
      transactionManager(事務管理器)
      dataSource(數據源)
  mappers(映射器)

1. Java API 初始化

Java API 初始化的方式雖然不經常使用,可是相較於 XML 的方式能夠更清楚的看到 Configuration 的構成,其示例以下:安全

PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT");
dataSource.setUsername("root");
dataSource.setPassword("root");
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(UserMapper.class);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

2. XML 配置初始化

相交於 Java API 的方式,XML 配置初始化,必然會多出 XML 的解析部分;代碼以下:session

String resource = "org/apache/ibatis/builder/MapperConfig.xml";
Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();

下面是一個相對完整的配置示例:

<?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="org/apache/ibatis/databases/blog/blog-derby.properties"/>
    
  <settings>
    <setting name="cacheEnabled" value="true"/>
    <setting name="lazyLoadingEnabled" value="false"/>
    ...
  </settings>
    
  <typeAliases>
    <typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author"/>
    <typeAlias alias="Blog" type="org.apache.ibatis.domain.blog.Blog"/>
    ...
  </typeAliases>

  <typeHandlers>
    <typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.CustomStringTypeHandler"/>
  </typeHandlers>

  <objectFactory type="org.apache.ibatis.builder.ExampleObjectFactory">
    <property name="objectFactoryProperty" value="100"/>
  </objectFactory>

  <plugins>
    <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
      <property name="pluginProperty" value="100"/>
    </plugin>
  </plugins>

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC">
        <property name="" value=""/>
      </transactionManager>
      <!--<dataSource type="UNPOOLED">-->
      <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="org/apache/ibatis/builder/AuthorMapper.xml"/>
    <mapper resource="org/apache/ibatis/builder/BlogMapper.xml"/>
    ...
  </mappers>

</configuration>

其解析的流程以下:

<img src="https://img2018.cnblogs.com/blog/1119937/201908/1119937-20190815185630032-229242454.png" width = "800" alt="" align=center />

主要代碼以下:

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

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

從上面的代碼和流程圖中能夠看到,XML 初始化的主要流程被封裝到了 XMLConfigBuilder 當中;主要的代碼邏輯以下:

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 {
    //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、SqlSession 使用方式

1. 直接指定 MappedStatement

try (SqlSession session = sqlMapper.openSession()) {
  Author author = session.selectOne("org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAuthor", new Author(101));
}

這種方式經過 namespace + sqlId 的方式直接指定 MappedStatement;這種方式由於直接編寫字符串和強類型轉換,既不安全也稍顯麻煩,因此如今已經不推薦使用了;

@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
    registerCursor(cursor);
    return cursor;
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

2. 動態代理 Mapper 的方式

try (SqlSession session = sqlMapper.openSession()) {
  AuthorMapper mapper = session.getMapper(AuthorMapper.class);
  Author author = mapper.selectAuthor(500);
}

這種方式不經避免了以上的問題,同時也可以使用註解的方式編寫 sql,並且可使用 IDE 提示;如今通常都推薦使用這種方式;可是其最終也是調用了上面的接口;

首先在初始化的時候經過 bindMapperForNamespace,註冊對應的 Mapper(要求namespace和Mapper的全限定名保持一致);

// XMLMapperBuilder
private void bindMapperForNamespace() {
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) { //ignore, bound type is not required }
    if (boundType != null) {
      if (!configuration.hasMapper(boundType)) {
        configuration.addLoadedResource("namespace:" + namespace);
        configuration.addMapper(boundType);
      }
    }
  }
}

// MapperRegistry
public <T> void addMapper(Class<T> type) {
  if (type.isInterface()) {
    if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); }
    boolean loadCompleted = false;
    try {
      knownMappers.put(type, new MapperProxyFactory<>(type));  // 添加代理工廠
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

使用的時候,經過 class 類名獲取 MapperProxyFactory 代理工廠,製造一個新的 Mapper 代理(注意這裏時每次都要生成一個代理類,由於其中包含了 SqlSession,而 SqlSession 是線程不安全的因此不能緩存,可是我以爲這裏任然是能夠優化的,有興趣你能夠本身嘗試一下);

try (SqlSession session = sqlMapper.openSession()) {
  AuthorMapper mapper = session.getMapper(AuthorMapper.class);  // 代理類
}

// MapperRegistry
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
public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}

// MapperProxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    if (Object.class.equals(method.getDeclaringClass())) {  // 從Object中繼承的方法
      return method.invoke(this, args);
    } else if (method.isDefault()) {  // 有默認實現的接口方法
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);  // 而後由 MapperMethod 執行,這裏使用策略模式,後面還會詳細講解
}

總結

  • SqlSession 是線程不安全的,因此在示例代碼中每次使用都會將其關閉?

    在 mybatis 中還有一個類 SqlSessionManager 裏面有一個 ThreadLocal 用來管理 SqlSession,在 Spring 中也一樣是用 SqlSessionHolder 來管理的,因此並不會每次都建立一個新的 SqlSession;

  • 以上內容只是大體將了 mybatis 的主要結構,後面的章節還會分模塊進行講解;

另外本文主要參考了《MyBatis技術內幕》,有興趣的能夠自行查看;

原文出處:https://www.cnblogs.com/sanzao/p/11359871.html

相關文章
相關標籤/搜索