1. 程式人生 > >mybatis原始碼學習——Configuration類及其初始化過程、TypeHandler、TypeAlias

mybatis原始碼學習——Configuration類及其初始化過程、TypeHandler、TypeAlias

Configuration類是Mybatis中的特別核心的一個類,主要用來進行Mybatis執行過程中的各項引數的設定。第一次Debug原始碼時,會感覺到什麼配置都需要在Configuration中設定,多次Debug之後,發現確實如此,這就是Mybatis中的核心配置類。。。2333

因為在Mybatis的整個生命週期中,只存在一個Configuration的例項。這裡沒有使用單例模式,所以在Configuration例項化之前,完成了對成員變數的例項化,所以可以訪問其中的方法。並且這些成員變數是final的永遠指向同一個物件例項。

Mybatis的所有配置引數都在Configuration

中。

下面開始Configuration原始碼的分析~~

1.Configuration中的成員變數

可以看到Configuration中的所有成員變數的訪問許可權都是protected ,只允許org.apache.ibatis.session中的類訪問。

  //主要用來配置事務工廠和資料來源,以及environment的id。
  //可以使用多個環境,例如開發環境、測試環境,以id來區分。
  protected Environment environment;

  protected boolean safeRowBoundsEnabled = false;
  protected
boolean safeResultHandlerEnabled = true; protected boolean mapUnderscoreToCamelCase = false; //資料庫表字段駝峰命令 protected boolean aggressiveLazyLoading = true; protected boolean multipleResultSetsEnabled = true; protected boolean useGeneratedKeys = false; //自動生成主鍵 protected boolean useColumnLabel = true
; protected boolean cacheEnabled = true; //預設快取是開啟的 protected boolean callSettersOnNulls = false; protected boolean useActualParamName = true; protected String logPrefix; protected Class <? extends Log> logImpl; protected Class <? extends VFS> vfsImpl; //快取的作用域。 //SESSION表示會快取一個會話中執行的所有查詢。 //STATEMENT表示本地會話僅用在語句執行上,對相同SqlSession的不同調用將不會共享資料。 //具體快取,後面分析 protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION; protected JdbcType jdbcTypeForNull = JdbcType.OTHER; protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" })); protected Integer defaultStatementTimeout; protected Integer defaultFetchSize; /** * 預設ExecutorType為SIMPLE,但實際使用過程中因為cacheEnabled = true而使用了CachingExecutor。 * ExecutorType有[SIMPLE, REUSE, BATCH], * REUSE使用PrepareStatement, * BATCH表示可以批量執行(這種模式下可以使用sqlSession中的flushStatements()) */ protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL; protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE; protected Properties variables = new Properties(); protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory(); protected ObjectFactory objectFactory = new DefaultObjectFactory(); protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory(); protected boolean lazyLoadingEnabled = false; protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL protected String databaseId; protected Class<?> configurationFactory; //建立MapperRegistry並將Configuration傳遞給MapperRegistry //MapperRegistry主要用來管理Mapper介面 protected final MapperRegistry mapperRegistry = new MapperRegistry(this); //攔截器鏈,賦值在Executor建立的時候 //重點:型別轉換的註冊,在這裡註冊了常用的型別轉換器。當不能滿足開發需要時,可以自定義,例如0,1與false,true的型別轉換器。 //也就是Java中的型別與資料庫中支援的型別的相互轉換關係的定義。 //TypeHandlerRegistry主要用來管理TypeHandler protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(); //重點:類別名的註冊。方便我們在使用mapper.xml中的使用。例如在paramterType中我們可以直接寫"int",會被對映為java.lang.Integer //TypeAliasRegistry所有的對應關係被記錄在變數TYPE_ALIASES引用的HashMap中。 //幷包含一個 public <T> Class<T> resolveAlias(String string) 的方法,用來根據String型別的key獲取對應的類。 protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry(); //這幾個都使用了StrictMap是HashMap的一個子類 //先打 TODO 後面再分析吧。。。 protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection"); protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection"); protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection"); protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection"); protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection"); protected final Set<String> loadedResources = new HashSet<String>(); protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers"); protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>(); protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>(); protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>(); protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>(); /* * A map holds cache-ref relationship. The key is the namespace that * references a cache bound to another namespace and the value is the * namespace which the actual cache is bound to. */ protected final Map<String, String> cacheRefMap = new HashMap<String, String>();

2.Configuration的建構函式

  public Configuration(Environment environment) {
    this();
    this.environment = environment;
  }

  //然後是Configuration預設建構函式的
  public Configuration() {
  //為常用配置名註冊別名
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);   //JDBC註冊了JdbcTransactionFactory工廠
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

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

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);   //預設為XML
    languageRegistry.register(RawLanguageDriver.class);
  }

3.Configuration中成員變數的初始化

這裡主要分析MapperRegistryInterceptorChainTypeHandlerRegistryTypeAliasRegistryLanguageDriverRegistry的例項化。

3.1.MapperRegistry的例項化

MapperRegistry例項化:主要是將Configuration傳遞給MapperRegistry,而MapperRegistry中維護了一個HashMap, 提供對Mapper的新增、判斷是否存在、獲取操作。

 protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

這裡寫圖片描述

public class MapperRegistry {

  private final Configuration config;  //這裡的變數還是final修飾的
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }
  ...
}

當使用Configuration中的getMapper方法時,其實呼叫了MapperRegistry中的public <T> T getMapper(Class<T> type, SqlSession sqlSession) 。在knownMappers的HashMap中拿到MapperProxyFactory代理工廠。

Configuration.java

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
MapperRegistry.java

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

3.2.InterceptorChain

攔截器鏈,ArrayList實現。

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

3.3.TypeHandlerRegistry

主要用來註冊型別轉換器。
TypeHandler也就是我們在Mapper.xml中常見的jdbcType與javaType之間的對映關係。
內部使用HashMap來記錄型別對應關係。

這裡寫圖片描述

這裡寫圖片描述

1.TypeHandler介面定義了獲取sql查詢結果的3種方式,以及設定引數的方法。可以看到,傳遞引數使用了JDBC的PreparedStatement,預處理sql。3中過載getResult()提供了根據列名,列索引獲取sql查詢結果的方法。實際上是對ResultSet和CallableStatement的封裝。

2.BaseTypeHandler是個抽象類,並實現了TypeHandler介面。

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  protected Configuration configuration;

  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }

3.在這裡所有的TypeHandler都繼承自BaseTypeHandler。
同時使用了模板方法設計模式,定義了執行過程,具體的獲取結果的方式,交給子類去實現。這樣設計有極大的靈活性,當不能滿足開發需要時,我們可以定義自己的TypeHandler~~

自定義TypeHandler的載入見XMLConfigBuilder#parseConfiguration(XNode root)#typeHandlerElement(root.evalNode("typeHandlers"));

同時可以看到BaseTypeHandler中持有Configuration的引用,所以說Configuration真是貫穿於Mybatis的整個生命週期。。。2333

4.那我們怎知道什麼時候呼叫String型別的TypeHandler什麼時候呼叫Integer型別的TypeHandler呢? 再看BaseTypeHandler的定義 class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T>這裡使用了泛型,T繼承自TypeReference。這裡是關鍵,TypeReference裡記錄了rawType,所以可以準確的呼叫到對應的TypeHandler去getResult。

5.TypeHandler預設構造器中註冊了常用的型別轉換器。如下:

  public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());

    register(Byte.class, new ByteTypeHandler());
    register(byte.class, new ByteTypeHandler());
    register(JdbcType.TINYINT, new ByteTypeHandler());

    register(Short.class, new ShortTypeHandler());
    register(short.class, new ShortTypeHandler());
    register(JdbcType.SMALLINT, new ShortTypeHandler());

    register(Integer.class, new IntegerTypeHandler());
    register(int.class, new IntegerTypeHandler());
    register(JdbcType.INTEGER, new IntegerTypeHandler());

    register(Long.class, new LongTypeHandler());
    register(long.class, new LongTypeHandler());

    register(Float.class, new FloatTypeHandler());
    register(float.class, new FloatTypeHandler());
    register(JdbcType.FLOAT, new FloatTypeHandler());

    register(Double.class, new DoubleTypeHandler());
    register(double.class, new DoubleTypeHandler());
    register(JdbcType.DOUBLE, new DoubleTypeHandler());

    register(Reader.class, new ClobReaderTypeHandler());
    register(String.class, new StringTypeHandler());
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
    register(JdbcType.CHAR, new StringTypeHandler());
    register(JdbcType.VARCHAR, new StringTypeHandler());
    register(JdbcType.CLOB, new ClobTypeHandler());
    register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(JdbcType.NVARCHAR, new NStringTypeHandler());
    register(JdbcType.NCHAR, new NStringTypeHandler());
    register(JdbcType.NCLOB, new NClobTypeHandler());

    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
    register(JdbcType.ARRAY, new ArrayTypeHandler());

    register(BigInteger.class, new BigIntegerTypeHandler());
    register(JdbcType.BIGINT, new LongTypeHandler());

    register(BigDecimal.class, new BigDecimalTypeHandler());
    register(JdbcType.REAL, new BigDecimalTypeHandler());
    register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
    register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

    register(InputStream.class, new BlobInputStreamTypeHandler());
    register(Byte[].class, new ByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
    register(byte[].class, new ByteArrayTypeHandler());
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.BLOB, new BlobTypeHandler());

    register(Object.class, UNKNOWN_TYPE_HANDLER);
    register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);

    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    register(JdbcType.TIMESTAMP, new DateTypeHandler());
    register(JdbcType.DATE, new DateOnlyTypeHandler());
    register(JdbcType.TIME, new TimeOnlyTypeHandler());

    register(java.sql.Date.class, new SqlDateTypeHandler());
    register(java.sql.Time.class, new SqlTimeTypeHandler());
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

    // mybatis-typehandlers-jsr310
    try {
      // since 1.0.0
      register("java.time.Instant", "org.apache.ibatis.type.InstantTypeHandler");
      register("java.time.LocalDateTime", "org.apache.ibatis.type.LocalDateTimeTypeHandler");
      register("java.time.LocalDate", "org.apache.ibatis.type.LocalDateTypeHandler");
      register("java.time.LocalTime", "org.apache.ibatis.type.LocalTimeTypeHandler");
      register("java.time.OffsetDateTime", "org.apache.ibatis.type.OffsetDateTimeTypeHandler");
      register("java.time.OffsetTime", "org.apache.ibatis.type.OffsetTimeTypeHandler");
      register("java.time.ZonedDateTime", "org.apache.ibatis.type.ZonedDateTimeTypeHandler");
      // since 1.0.1
      register("java.time.Month", "org.apache.ibatis.type.MonthTypeHandler");
      register("java.time.Year", "org.apache.ibatis.type.YearTypeHandler");

    } catch (ClassNotFoundException e) {
      // no JSR-310 handlers
    }

    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());
  }

3.4.TypeAliasRegistry的例項化

主要為java類設定別名,我們在xml中配置的TypeAlias也會被註冊進來(詳細執行過程見XMLConfigBuilder中的typeAliasesElement()方法)。

內部還是HashMap實現。Map<String, Class<?>> TYPE_ALIASES

在Configuration的預設構造方法中,註冊了”JDBC”等全域性的別名。

public class TypeAliasRegistry {

  private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

  public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }

4.再說Configuration

Configuration的這種設計雖然違背了單一職責的設計原則,但也帶來了極大的好處,我們可以在sqlsession、sqlsessionFactory中拿到Configuration進而拿到所有設定。

例如:

//獲取所有註冊的別名
Configuration configuration = sqlSessionFactory.getConfiguration();
TypeAliasRegistry typeAliasRegistry = configuration.getTypeAliasRegistry();
Map<String, Class<?>> typeAliases = typeAliasRegistry.getTypeAliases();
for (Map.Entry<String, Class<?>> entry : typeAliases.entrySet()) {
    System.out.println(entry.getKey() + "  " + entry.getValue());
}

//獲取所有已註冊的型別轉換器
TypeHandlerRegistry typeHandlerRegistry = session.getConfiguration().getTypeHandlerRegistry();
Collection<TypeHandler<?>> typeHandlers = typeHandlerRegistry.getTypeHandlers();
for (Map.Entry<String, Class<?>> entry : typeAliases.entrySet()) {
    System.out.println(entry.getKey() + "  " + entry.getValue());
}

5.總結

1.首先Configuration是在XMLConfigBuilder的private XMLConfigBuilder(XPathParser parser, String environment, Properties props)構造器中建立的,這裡涉及到XML配置檔案解析成Configuration參見 mybatis原始碼學習之執行過程分析(2)——Mapper介面和XML檔案對映及解析中的分析。

2.Configuration中的MapperRegistryInterceptorChainTypeHandlerRegistryTypeAliasRegistryLanguageDriverRegistry在Configuration構造方法呼叫之前進行了例項化和基本的設定。最後由Configuration中的final修飾的成員變數引用,所以在Mybatis的生命週期中,可以通過Configuration例項獲取到這些註冊的資訊。