XMLConfigBuilder 是BaseBuilder(解析中会涉及到讲解)的其中一个子类,它的作用是把MyBatis的XML及相关配置解析出来,然后保存到Configuration中。本文就解析过程按照执行顺序进行分析,掌握常用配置的解析原理。
调用XMLConfigBuilder进行解析,要进行两步操作,上篇文章中【MyBatis之启动分析(一)】有提到。
通过new Configuration()的方式实例化: typeAliasRegistry是一个类型别名注册器,实现原理就是维护一份HashMap,别名作为key,类的全限定名作为value。这里将框架中使用的类注册到类型别名注册器中。 TypeAliasRegistry.registerAlias代码如下:
public void registerAlias(String alias, Class<?> value) { if (alias == null) { throw new TypeException("The parameter alias cannot be null"); } // issue #748 // 在验证是否存在key和保存kv前,统一将key转换成小写 String key = alias.toLowerCase(Locale.ENGLISH); if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) { // 当注册的类型已存在时,抛出异常 throw new TypeException("The alias '" alias "' is already mapped to the value '" TYPE_ALIASES.get(key).getName() "'."); } // TYPE_ALIASES 为定义的一个HashMap TYPE_ALIASES.put(key, value); }在实例化Configuration类过程中,在该类里除了实例化了TypeAliasRegistry还实例化了另外一个下面用到的的类:
// 类型处理器注册器 protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();TypeHandlerRegistry和TypeAliasRegistry实例化逻辑相似,里面注册了一些常用类型和处理器,代码易懂。 TypeHandlerRegistry的属性
// jdbc类型和TypeHandler的映射关系,key必须是JdbcType的枚举类型,读取结果集数据时,将jdbc类型转换成java类型 private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class); // Java类型与JdbcType类型的键值对,存在一对多的映射关系 private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>(); // 没有相应的类型处理器时,使用的处理器 private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this); // 类型处理器类类型和类型处理器的映射关系 private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>(); // 空处理器的值,用来做校验 private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap(); // 默认枚举类型处理器 private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;TypeHandlerRegistry构造函数:
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 // 是否包含日期,时间相关的Api,通过判断是否加载java.time.Clock作为依据 if (Jdk.dateAndTimeApiExists) { this.register(Instant.class, InstantTypeHandler.class); this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class); this.register(LocalDate.class, LocalDateTypeHandler.class); this.register(LocalTime.class, LocalTimeTypeHandler.class); this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class); this.register(OffsetTime.class, OffsetTimeTypeHandler.class); this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class); this.register(Month.class, MonthTypeHandler.class); this.register(Year.class, YearTypeHandler.class); this.register(YearMonth.class, YearMonthTypeHandler.class); this.register(JapaneseDate.class, JapaneseDateTypeHandler.class); } // issue #273 register(Character.class, new CharacterTypeHandler()); register(char.class, new CharacterTypeHandler()); }里面调用了两个register()重载方法, type handler 参的TypeHandlerRegistry.register(Class<T> javaType, TypeHandler<? extends T> typeHandler)和 type jdbc type handler 参的TypeHandlerRegistry.register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler)
// java type handler public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) { register((Type) javaType, typeHandler); } private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) { MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class); if (mappedJdbcTypes != null) { for (JdbcType handledJdbcType : mappedJdbcTypes.value()) { register(javaType, handledJdbcType, typeHandler); } if (mappedJdbcTypes.includeNullJdbcType()) { register(javaType, null, typeHandler); } } else { register(javaType, null, typeHandler); } } // java type jdbc type handler public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) { register((Type) type, jdbcType, handler); } // type handler 和 type jdbc type handler 最终都调用此方法 private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) { if (javaType != null) { // 当 javaType 不为空时, 获取 java 类型的的映射 Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType); if (map == null || map == NULL_TYPE_HANDLER_MAP) { // 若映射为空,新建一个映射关系 map = new HashMap<JdbcType, TypeHandler<?>>(); // 保存至类型处理器映射关系中 TYPE_HANDLER_MAP.put(javaType, map); } // 保存jdbcType和处理器关系,完成 java类型,jdbc类型,处理器三者之间的注册 map.put(jdbcType, handler); } // 保存处理器信息中 ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler); } // MappedJdbcTypes 注解 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MappedJdbcTypes { JdbcType[] value(); boolean includeNullJdbcType() default false; } type handler方法:先获取处理器的MappedJdbcTypes注解(自定义处理器注解),若注解的value值不为空时,由于该值为JdbcType[]类型,所以for循环 javaType jdbcType TypeHandler注册,若includeNullJdbcType(jdbcType是否包含null)为true,默认值为false,注册到相应映射中。若注解的value为null,直接调用注册操作,里面不会注册type jdbc type handler关系。type jdbc type handler方法:该方法将java类强制转换为java.lang.reflect.Type类型,然后调用最终注册的方法。BaseBuilder定义有三个属性
protected final Configuration configuration; // 类型别名注册器 protected final TypeAliasRegistry typeAliasRegistry; // 类型处理器注册器 protected final TypeHandlerRegistry typeHandlerRegistry;BaseBuilder构造方法
public BaseBuilder(Configuration configuration) { this.configuration = configuration; this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry(); }这里属性,就是上面讲解到的。
parse()实现配置文件是否解析过
public Configuration parse() { // 若parsed为true,配置文件解析过 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } // 标志已解析过 parsed = true; // 从根节点 configuration 开始解析 parseConfiguration(parser.evalNode("/configuration")); return 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); 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); } }从上面源码中,不难看出这里是解析/configuration中的各个子节点。
从上面源码中,resource和url的配置形式不允许同时存在,否则抛出BuilderException异常。先解析propertie的配置值,再解析resource或url的值。 当propertie存在与resource或url相同的key时,propertie的配置会被覆盖,应为Properties实现的原理就是继承的Hashtable类来实现的。
设置中各项的意图、默认值 图(引用来源:w3cschool)
设置参数描述有效值默认值cacheEnabled该配置影响的所有映射器中配置的缓存的全局开关。true,falsetruelazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。true,falsefalseaggressiveLazyLoading当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。true,false,truemultipleResultSetsEnabled是否允许单一语句返回多结果集(需要兼容驱动)。true,falsetrueuseColumnLabel使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。true,falsetrueuseGeneratedKeys允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。true,falseFalseautoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIALdefaultExecutorType配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。SIMPLE REUSE BATCHSIMPLEdefaultStatementTimeout设置超时时间,它决定驱动等待数据库响应的秒数。Any positive integerNot Set (null)safeRowBoundsEnabled允许在嵌套语句中使用分页(RowBounds)。true,falseFalsemapUnderscoreToCamelCase是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。true, falseFalselocalCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。SESSION,STATEMENTSESSIONjdbcTypeForNull当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。JdbcType enumeration. Most common are: NULL, VARCHAR and OTHEROTHERlazyLoadTriggerMethods指定哪个对象的方法触发一次延迟加载。A method name list separated by commasequals,clone,hashCode,toStringdefaultScriptingLanguage指定动态 SQL 生成的默认语言。A type alias or fully qualified class name.org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDrivercallSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。true,falsefalselogPrefix指定 MyBatis 增加到日志名称的前缀。 Any StringNot setlogImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J, LOG4J, LOG4J2, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGINGNot setproxyFactory指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。CGLIB JAVASSISTCGLIB这里获取到setting的值,并返回Properties对象。然后做配置的name是否合法。 org.apache.ibatis.reflection.MetaClass类是保存着一个利用反射获取到的类信息,metaConfig.hasSetter(String.valueOf(key))是判断metaConfig对象中是否包含key属性。
该方法是解析虚拟文件系统配置,用来加载自定义虚拟文件系统的资源。类保存在Configuration.vfsImpl中。
这个方法的作用就是将解析的settings设置到 configuration中
private void settingsElement(Properties props) throws Exception { configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE"))); configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory"))); configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false)); configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false)); configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true)); configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true)); configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false)); configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE"))); configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null)); configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null)); configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false)); configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION"))); configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER"))); configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString")); configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true)); configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage"))); @SuppressWarnings("unchecked") Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler")); configuration.setDefaultEnumTypeHandler(typeHandler); configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true)); configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false)); configuration.setLogPrefix(props.getProperty("logPrefix")); @SuppressWarnings("unchecked") Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl")); configuration.setLogImpl(logImpl); configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); }该节点是配置类和别名的关系
package节点是配置整个包下的类typeAlias节点是指定配置单个类,type为必填值且为类全限定名,alias为选填。 配置后,是该类时,可直接使用别名。当扫描package时,获取到包名后TypeAliasRegistry.registerAliases(typeAliasPackage)
public void registerAliases(String packageName){ registerAliases(packageName, Object.class); } public void registerAliases(String packageName, Class<?> superType){ ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); // 获取 package 下所有已 .class 结尾的文件 resolverUtil.find(new ResolverUtil.IsA(superType), packageName); // 获取扫描出来的类 Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); for(Class<?> type : typeSet){ // Ignore inner classes and interfaces (including package-info.java) // Skip also inner classes. See issue #6 // 过滤类 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { registerAlias(type); } } }扫描到指定package下所有以.class结尾文件的类,并保存至Set集合中,然后遍历集合,过滤掉没有名称,接口,和底层特定类。 最后TypeAliasRegistry.registerAlias(Class<?> type)注册到别名注册器中。
public void registerAlias(Class<?> type) { // 使用类的 simpleName 作为别名,也就是默认的别名命名规则 String alias = type.getSimpleName(); Alias aliasAnnotation = type.getAnnotation(Alias.class); if (aliasAnnotation != null) { alias = aliasAnnotation.value(); } // 上面分析的最终注册的方法 registerAlias(alias, type); }通过类注册到注册器中时,如果该注册类有使用@Alias(org.apache.ibatis.type.Alias)注解,那么XML配置中配置的别名会被注解配置覆盖。
如果typeAlias的alias有设置值,使用自定名称方式注册,否则使用默认方式注册,即类的simpleName作为别名。
自定义插件需要实现org.apache.ibatis.plugin.Interceptor接口,同时在注解上指定拦截的方法。
这里取<plugin>节点的interceptor可以使用别名设置。从源码中resolveClass方法
// protected Class<?> resolveClass(String alias) { if (alias == null) { return null; } try { return resolveAlias(alias); } catch (Exception e) { throw new BuilderException("Error resolving class. Cause: " e, e); } } // protected Class<?> resolveAlias(String alias) { return typeAliasRegistry.resolveAlias(alias); } // public <T> Class<T> resolveAlias(String string) { try { if (string == null) { return null; } // issue #748 // 将传入的 类 名称统一转换 String key = string.toLowerCase(Locale.ENGLISH); Class<T> value; // 验证别名中是否有当前传入的key if (TYPE_ALIASES.containsKey(key)) { value = (Class<T>) TYPE_ALIASES.get(key); } else { value = (Class<T>) Resources.classForName(string); } return value; } catch (ClassNotFoundException e) { throw new TypeException("Could not resolve type alias '" string "'. Cause: " e, e); } }以上源码为别名解析过程,其他别名的解析也是调用此方法,先去保存的别名中去找,是否有别名,如果没有就通过Resources.classForName生成实例。
以上都是对实现类都是对MyBatis进行扩展。解析方法也类似,最后都是保存在configuration。
// objectFactory 解析 private void objectFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties properties = context.getChildrenAsProperties(); ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance(); factory.setProperties(properties); configuration.setObjectFactory(factory); } } // objectWrapperFactory 解析 private void objectWrapperFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance(); configuration.setObjectWrapperFactory(factory); } } // reflectorFactory 解析 private void reflectorFactoryElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance(); configuration.setReflectorFactory(factory); } }以上为解析objectFactory,objectWrapperFactory,reflectorFactory源码,经过前面的分析后,这里比较容易看懂。
该节点可设置多个环境,针对不同的环境单独配置。environments的属性default是默认环境,该值对应一个environment的属性id的值。
transactionManager为事务管理,属性type为事务管理类型,上面的介绍的new Configuration()有定义类型有:JDBC 和 MANAGED事务管理类型。dataSource是数据源,type为数据源类型,与transactionManager同理,可知内建的数据源类型有:JNDI,POOLED,UNPOOLED数据源类型。若没有配置environment环境或环境没有给id属性,则会抛出异常,若当前id是要使用的就返回true,否则返回false。 TransactionFactory实例化过程比较简单,与创建DataSourceFactory类似。
获取数据源,首先得创建DataSourceFactory,上面使用DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"))创建
private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); }这里就是获取到数据源得type后,利用上面所讲到得resolveClass()方法获取到DataSourceFactory。 以UNPOOLED为例,对应的DataSourceFactory实现类为UnpooledDataSourceFactory。实例化过程中就给该类的属性dataSource数据源赋值了
/** * UnpooledDataSourceFactory 类 */ protected DataSource dataSource; public UnpooledDataSourceFactory() { this.dataSource = new UnpooledDataSource(); } @Override public DataSource getDataSource() { return dataSource; }UnpooledDataSource类里面有静态代码块所以数据源被加载
/** * UnpooledDataSource 类 */ static { Enumeration<Driver> drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); registeredDrivers.put(driver.getClass().getName(), driver); } }基于映射语句中的databaseId属性,可以根据不同数据库厂商执行不同的sql。
根据匹配的数据库厂商类型匹配数据源databaseIdProvider.getDatabaseId(environment.getDataSource())
@Override public String getDatabaseId(DataSource dataSource) { if (dataSource == null) { throw new NullPointerException("dataSource cannot be null"); } try { return getDatabaseName(dataSource); } catch (Exception e) { log.error("Could not get a databaseId from dataSource", e); } return null; } private String getDatabaseName(DataSource dataSource) throws SQLException { // 根据数据源获取数据库产品名称 String productName = getDatabaseProductName(dataSource); if (this.properties != null) { for (Map.Entry<Object, Object> property : properties.entrySet()) { // 判断是否包含,选择使用的数据库产品 if (productName.contains((String) property.getKey())) { return (String) property.getValue(); } } // no match, return null return null; } return productName; } private String getDatabaseProductName(DataSource dataSource) throws SQLException { Connection con = null; try { // 数据库连接 con = dataSource.getConnection(); // 获取连接元数据 DatabaseMetaData metaData = con.getMetaData(); // 获取数据库产品名称 return metaData.getDatabaseProductName(); } finally { if (con != null) { try { con.close(); } catch (SQLException e) { // ignored } } } }这里需要注意的是配置:比如使用mysql,我踩过这里的坑,这里Name为MySQL,我把y写成大写,结果匹配不上。 另外这里写个My也能匹配上,应为是使用的String.contains方法,只要包含就会符合,这里代码应该不够严谨。
扫描整个包或者指定类型之间的映射,javaType, jdbcType非必需,handler必填项
源码分析会根据包下所有处理器或者指定处理器进行解析,最后会根据上面分析到的type handler和type jdbc type handler不同情况注册。 另外这里还有个TypeHandlerRegistry.register(Class<?> typeHandlerClass)注册类
public void register(Class<?> typeHandlerClass) { // 标志是否从 MappedTypes 注解中获取 javaType 注册 boolean mappedTypeFound = false; // 获取 MappedTypes 的值 MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> javaTypeClass : mappedTypes.value()) { // 已 type handler 的方式注册 register(javaTypeClass, typeHandlerClass); // 标志已通过注解注册类型 mappedTypeFound = true; } } if (!mappedTypeFound) { // 通过 TypeHandler 注册 register(getInstance(null, typeHandlerClass)); } } // 实例化 public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) { if (javaTypeClass != null) { try { // 获取有参构造函数 Constructor<?> c = typeHandlerClass.getConstructor(Class.class); // 实例化对象 return (TypeHandler<T>) c.newInstance(javaTypeClass); } catch (NoSuchMethodException ignored) { // ignored } catch (Exception e) { throw new TypeException("Failed invoking constructor for handler " typeHandlerClass, e); } } try { // 获取无参构造函数 Constructor<?> c = typeHandlerClass.getConstructor(); return (TypeHandler<T>) c.newInstance(); } catch (Exception e) { throw new TypeException("Unable to find a usable constructor for " typeHandlerClass, e); } } // 注册实例 public <T> void register(TypeHandler<T> typeHandler) { boolean mappedTypeFound = false; MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class<?> handledType : mappedTypes.value()) { register(handledType, typeHandler); mappedTypeFound = true; } } // @since 3.1.0 - try to auto-discover the mapped type if (!mappedTypeFound && typeHandler instanceof TypeReference) { try { TypeReference<T> typeReference = (TypeReference<T>) typeHandler; register(typeReference.getRawType(), typeHandler); mappedTypeFound = true; } catch (Throwable t) { // maybe users define the TypeReference with a different type and are not assignable, so just ignore it } } if (!mappedTypeFound) { register((Class<T>) null, typeHandler); } }以上的register方法中,了解type jdbc type handler后,其他的register重载方法比较容易理解,其他的都是基于它上面的封装。
可通过以上四种形式配置mappers节点,<package>和<mapper>为互斥节点。
该方法是负责解析<mappers>节点
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { // 如果配置 package 节点,则扫描 if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); // 解析包下类Mapper接口,并注册到configuration的mapperRegistry中 configuration.addMappers(mapperPackage); } else { // 获取mapper节点的resource,url,class属性 String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // 根据resource解析,并且url,class值必须为空,也就不能配置值。url,class同理,其它两个属性也不能配置值 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); // 通过resource获取流 InputStream inputStream = Resources.getResourceAsStream(resource); // 创建XMLMapperBuilder对象 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // 解析映射配置文件 mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); // 通过url获取流 InputStream inputStream = Resources.getUrlAsStream(url); // 和resource解析方式一样,创建XMLMapperBuilder对象,然后解析映射配置文件 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { // 加载class属性的接口 Class<?> mapperInterface = Resources.classForName(mapperClass); // 将接口注册到configuration的mapperRegistry中 configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }<package>的包扫描到的类,然后单个单个注册到configuration的mapperRegistry中,这里和<mapper>使用class属性是一样逻辑。 解析package方式
// Configuration 中定义了 protected final MapperRegistry mapperRegistry = new MapperRegistry(this); /** * 步骤一 * 该函数于 Configuration 中 */ public void addMappers(String packageName) { // mapperRegistry定义在Configuration中的一个属性 mapperRegistry.addMappers(packageName); } /** * 步骤二 * 该函数于 MapperRegistry 中 */ public void addMappers(String packageName) { addMappers(packageName, Object.class); } /** * 步骤三 * 该函数于 MapperRegistry 中 */ public void addMappers(String packageName, Class<?> superType) { // 通过 ResolverUtil 获取包下的类 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); for (Class<?> mapperClass : mapperSet) { // 遍历获取到的类,注册到 MapperRegistry addMapper(mapperClass); } } /** * 步骤四 * 该函数于 MapperRegistry 中 */ public <T> void addMapper(Class<T> type) { // mapper 类为 interface 接口 if (type.isInterface()) { // 判断当前class是否已经注册过 if (hasMapper(type)) { throw new BindingException("Type " type " is already known to the MapperRegistry."); } // 校验是否加载完成 boolean loadCompleted = false; try { // 保存 mapper 接口和 MapperProxyFactory 之间的映射 knownMappers.put(type, new MapperProxyFactory<T>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. // 解析xml和注解 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); // 标志加载完成 loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }解析mapper的class属性
// 该函数于 Configuration 中 public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); } // ... 这里调用上面的【步骤四】这两中方式是直接注册接口到mapperRegistry,另外两种是解析xml的方式就是获取映射文件的namespace,再注册进来,XMLMapperBuilder是负责解析映射配置文件的类,今后会单独详细分析这个类,这里不展开讲。
这里对XMLConfigBuilder解析配置文件到此分析完,本文对配置文件解析的流程大致了解流程和原理。相信遇到配置问题异常,大致能排查到根本原因。
个人博客: https://ytao.top
我的公众号 ytao