1. 程式人生 > 其它 >mybatis原始碼解讀:session包

mybatis原始碼解讀:session包

技術標籤:mybaits原始碼mybatis

​session包是整個mybatis應用的對外介面包。

1.SqlSession及其相關類

1.1 SqlSession的生成鏈

在進行查詢操作時,只需要和SqlSession物件打交道,而SqlSession物件是由SqlSessionFactory生產出來的,而SqlSessionFactory又是由SqlSessionFactoryBuilder建立的。

public class SqlSessionFactoryBuilder {

  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }

  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }

  /**
   * 建造一個SqlSessionFactory物件
   * @param reader 讀取字元流的抽象類
   * @param environment 環境資訊
   * @param properties 配置資訊
   * @return SqlSessionFactory物件
   */
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      // 傳入配置檔案,建立一個XMLConfigBuilder類
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      // 分兩步:
      // 1、解析配置檔案,得到配置檔案對應的Configuration物件
      // 2、根據Configuration物件,獲得一個DefaultSqlSessionFactory
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
      }
    }
  }

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

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

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

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

  /**
   * 根據配置資訊建造一個SqlSessionFactory物件
   * @param config 配置資訊
   * @return SqlSessionFactory物件
   */
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}

DefaultSqlSessionFactory物件可以創建出SqlSession的子類DefualtSqlSession類的物件,該過程由openSessionFromDataSourcce方法完成。

 /**
   * 從資料來源中獲取SqlSession物件
   * @param execType 執行器型別
   * @param level 事務隔離級別
   * @param autoCommit 是否自動提交事務
   * @return SqlSession物件
   */
  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);
      // 建立DefaultSqlSession物件
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx);
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

至此,整個SqlSession生成鏈的相關操作,經過逐級生成後終於得到DefaultSqlSession物件。

1.2 DefaultSqlSessio類

DefaultSqlSessio類的主要工作是把介面包的工作交給執行器包處理。

public class DefaultSqlSession implements SqlSession {
  // 配置資訊
  private final Configuration configuration;
  // 執行器
  private final Executor executor;
  // 是否自動提交
  private final boolean autoCommit;
  // 快取是否已經被汙染
  private boolean dirty;
  // 遊標列表
  private List<Cursor<?>> cursorList;

  /**
   * 查詢結果列表
   * @param <E> 返回的列表元素的型別
   * @param statement SQL語句
   * @param parameter 引數物件
   * @param rowBounds  翻頁限制條件
   * @return 結果物件列表
   */
  @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();
    }
  }
}

DefaultSqlSession類將主要操作都交給屬性中的Executor物件處理,相關資料庫查詢操作都有Executor物件的query方法來完成。

1.3SqlSessionManager類

public class SqlSessionManager implements SqlSessionFactory, SqlSession {

  // 構造方法中傳入的SqlSessionFactory物件
  private final SqlSessionFactory sqlSessionFactory;
  // 在構造方法中建立的SqlSession代理物件
  private final SqlSession sqlSessionProxy;
  // 該變數用來儲存被代理的SqlSession物件
  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();

  /**
   * SqlSessionManager構造方法
   * @param sqlSessionFactory SqlSession工廠
   */
  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 嘗試從當前執行緒中取出SqlSession物件
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      if (sqlSession != null) { // 當前執行緒中確實取出了SqlSession物件
        try {
          // 使用取出的SqlSession物件進行操作
          return method.invoke(sqlSession, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } else { // 當前執行緒中還沒有SqlSession物件
        // 使用屬性中的SqlSessionFactory物件建立一個SqlSession物件
        try (SqlSession autoSqlSession = openSession()) {
          try {
            // 使用新建立的SqlSession物件進行操作
            final Object result = method.invoke(autoSqlSession, args);
            autoSqlSession.commit();
            return result;
          } catch (Throwable t) {
            autoSqlSession.rollback();
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
      }
    }
  }
}

SqlSessionManager類主要提供產品複用功能。工廠生成的產品可以放入執行緒ThreadLocal儲存,從而實現產品的複用,這樣即保證了執行緒安全又提升了效率。

1.4Configuration類

配置檔案mybatis-config.xml是mybatis配置主入口,該配置檔案的資訊經過解析後都存入了Configuration物件中,因此Configuration類包含了mybatis執行的所有配置資訊。而且Configuration類還對配置資訊進行了進一步加工,為許多配置項設定了預設值,為許多實體類定義了別名等。


public class Configuration {

  // <environment>節點的資訊
  protected Environment environment;

  // 以下為<settings>節點中的配置資訊
  protected boolean safeRowBoundsEnabled;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase;
  protected boolean aggressiveLazyLoading;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls;
  protected boolean useActualParamName = true;
  protected boolean returnInstanceForEmptyRow;

  protected String logPrefix;
  protected Class<? extends Log> logImpl;
  protected Class<? extends VFS> vfsImpl;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  protected ResultSetType defaultResultSetType;
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
  // 以上為<settings>節點中的配置資訊

  // <properties>節點資訊
  protected Properties variables = new Properties();
  // 反射工廠
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  // 物件工廠
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  // 物件包裝工廠
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
  // 是否啟用懶載入,該配置來自<settings>節點
  protected boolean lazyLoadingEnabled = false;
  // 代理工廠
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
  // 資料庫編號
  protected String databaseId;
  // 配置工廠,用來建立用於載入反序列化的未讀屬性的配置。
  protected Class<?> configurationFactory;
  // 對映登錄檔
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  // 攔截器鏈(用來支援外掛的插入)
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  // 型別處理器登錄檔,內建許多,可以通過<typeHandlers>節點補充
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  // 類型別名登錄檔,內建許多,可以通過<typeAliases>節點補充
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  // 語言驅動登錄檔
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  // 對映的資料庫操作語句
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  // 快取
  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
  // 結果對映,即所有的<resultMap>節點
  protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
  // 引數對映,即所有的<parameterMap>節點
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
  // 主鍵生成器對映
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
  // 載入的資源,例如對映檔案資源
  protected final Set<String> loadedResources = new HashSet<>();
  // SQL語句片段,即所有的<sql>節點
  protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

  // 暫存未處理完成的一些節點
  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

  // 用來儲存跨namespace的快取共享設定
  protected final Map<String, String> cacheRefMap = new HashMap<>();
}

為了便於配置資訊的快速查詢,Configuration類中還設定了一個內部類StrictMap,是HashMap的子類。


  protected static class StrictMap<V> extends HashMap<String, V> {

    private static final long serialVersionUID = -4950446264854982944L;
    private final String name;
    private BiFunction<V, V, String> conflictMessageProducer;


    /**
     * 向Map中寫入鍵值對
     * @param key 鍵
     * @param value 值
     * @return 舊值,如果不存在舊值則為null。因為StrictMap不允許覆蓋,則只能返回null
     */
    @Override
    @SuppressWarnings("unchecked")
    public V put(String key, V value) {
      if (containsKey(key)) {
        //如果已經存在此key了,直接報錯
        throw new IllegalArgumentException(name + " already contains value for " + key
            + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
      }
      if (key.contains(".")) {
        // 例如key=“com.github.yeecode.clazzName”,則shortName = “clazzName”,即獲取一個短名稱
        final String shortKey = getShortName(key);
        if (super.get(shortKey) == null) {
          // 以短名稱為鍵,放置一次
          super.put(shortKey, value);
        } else {
          // 放入該物件,表示短名稱會引發歧義
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      // 以長名稱為鍵,放置一次
      return super.put(key, value);
    }

    @Override
    public V get(Object key) {
      V value = super.get(key);
      if (value == null) {
        throw new IllegalArgumentException(name + " does not contain value for " + key);
      }
      if (value instanceof Ambiguity) {
        throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
            + " (try using the full name including the namespace, or rename one of the entries)");
      }
      return value;
    }

    // 含糊,是說短名稱指代不明,引發歧義
    // 因此,只要拿到該型別的value,說明:
    // 1,使用者一定使用了shortName進行了查詢
    // 2, 這個shortName存在重名
    protected static class Ambiguity {
      final private String subject;

      public Ambiguity(String subject) {
        this.subject = subject;
      }

      public String getSubject() {
        return subject;
      }
    }

    private String getShortName(String key) {
      final String[] keyParts = key.split("\\.");
      return keyParts[keyParts.length - 1];
    }
  }

歡迎關注本人公眾號: