mybatis源码解读(二)——构建Configuration对象

时间:2022-12-03 08:16:29

  Configuration 对象保存了所有mybatis的配置信息,主要包括:

  ①、 mybatis-configuration.xml 基础配置文件

  ②、 mapper.xml 映射器配置文件

1、读取配置文件

  前面例子有这么一段代码:

     private static SqlSessionFactory sqlSessionFactory;

     static{
InputStream inputStream = MybatisTest.class.getClassLoader().getResourceAsStream("mybatis-configuration.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}

  第 4 行代码是获取基础配置文件mybatis-configuration.xml 的字节流。接着我们将该字节流对象作为 bulid() 方法的参数传入进去。bulid 方法源码如下:这是一个多态方法

     public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
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) {
// Intentionally ignore. Prefer previous error.
}
}
}
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
} private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation); factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true); DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
} @Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
} @Override
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}

  这段代码我们不用深究,只需要知道这是将mybatis-configuration.xml文件解析成org.w3c.dom.Document对象,并将 Document 对象存储在 XPathParser 对象中便于后面解析。(XPath 语法解析xml具有很大的优势

  下一步就是将 Document 对象转换成 Configuration 对象:

  首先回到  SqlSessionFactory build(InputStream inputStream, String environment, Properties 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) {
// Intentionally ignore. Prefer previous error.
}
}
}

  第3行代码完成了xml文件到 Document 对象的转换,接下来我们看 build(parser.parse()) 方法:

     public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//从根节点 <configuration></configuration>处开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
} private void parseConfiguration(XNode root) {
try {
//分别解析相应的节点标签
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);
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//解析引入的mapper.xml文件
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

  我们可以看看前一篇初始化环境博客中对于 mybatis-configuration.xml 文件的配置信息:

 <?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="jdbc.properties">
</properties>
<!-- 可以配置多个运行环境,但是每个 SqlSessionFactory 实例只能选择一个运行环境 一、development:开发模式 二、work:工作模式 -->
<environments default="development">
<!--id属性必须和上面的default一样 -->
<environment id="development">
<transactionManager type="JDBC" />
<!--dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象源 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments> <mappers>
<mapper resource="com/ys/mapper/userMapper.xml"/>
</mappers>
</configuration>

2、初始化基础配置

  上面一步我们已经读取了xml文件的所有配置,接下来初始化配置文件中的信息,也就是读取xml文件每个节点的配置信息:

①、properties 全局参数

  配置举例:

     <!-- 加载数据库属性文件 -->
<properties resource="jdbc.properties">
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>

  具体读取详情:

     private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//先加载property子节点下的属性
Properties defaults = context.getChildrenAsProperties();
//读取properties 节点中的属性resource和url
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
//url和resource不能同时存在,否则抛出异常
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
//读取引入文件的信息,resource引入的文件属性会覆盖子节点的配置
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
//url引入的文件信息也会覆盖子节点的信息
defaults.putAll(Resources.getUrlAsProperties(url));
}
//读取Configuration对象中variables属性信息,如果有,则将其添加到properties对象中
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
//将Properties类设置到XPathParser和Configuration的variables属性中
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
public synchronized void putAll(Map<? extends K, ? extends V> t) {
for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
put(e.getKey(), e.getValue());
}

  具体解释在代码中都已经给出注释了。需要注意如下三点:

  一、可以设置url或resource属性从外部文件中加载一个properties文件

  二、可以通过property子节点进行配置,如果子节点属性的key与外部文件的key重复的话,子节点的将被覆

  三、通过编程方式定义的属性最后加载,优先级最高(上面代码第20行到23行):

  比如:

     <!-- 加载数据库属性文件 -->
<properties resource="jdbc.properties">
<property name="username" value="rootfasfsdf"/>
<property name="password" value="root"/>
</properties>

  只要jdbc.properties 文件中的username是正确的,<property name="username" value="rootfasfsdf"/>标签中value无论是什么,都不影响。

②、setting 设置

  配置举例:

     <settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true" />
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true" />
</settings>

  详细的配置项信息可以参考官网

  接着我们追溯源码:

   private void settingsElement(XNode context) throws Exception {
if (context != null) {
//读取所有子节点信息
Properties props = context.getChildrenAsProperties();
//检查所有setting配置文件的属性是否在 Configuration.class中存在set方法
//如果不存在,则抛出异常
MetaClass metaConfig = MetaClass.forClass(Configuration.class);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
//给configuration类中的属性初始化
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
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"), true));
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.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")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
}

  总结:在<settings>标签中配置的节点信息必须在 Configuration 类中存在相应的属性,否则会抛出异常。然后根据标签中配置的值初始化 Configuration 类中的属性值。

③、typeAliases 别名

  配置举例:

     <typeAliases>
<typeAlias type="com.ys.po.User" alias="user"/>
</typeAliases>

  类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。它还有一个 <package name="com.ys.po" /> 标签,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。

  具体用法可以参考官网

  接下来我们查看源码:

   private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}

  从第 4 行和第 7 行的 if...else... 语句可以看到,<typeAliases> 标签可以有 一个标签<package>或者含有 type属性和 alias 属性的标签(其实就是<typeAlias type="" alias=""/>,在解析文档时做了约束)。

  注意:这两个标签可以共存。但是<typeAliases />标签一定要在 <package />标签的前面。因为一个类可以有多个别名,所以这时候两个标签设置的名称都有效。

  首先看第 6 行代码,解析 package 标签:

     public void registerAliases(String packageName) {
registerAliases(packageName, Object.class);
} public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
//根据包名 packageName 获取包下所有的 .class 文件(反射)
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
//遍历所有的class,不能是匿名类、接口以及成员类
for (Class<?> type : typeSet) {
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
} public void registerAlias(Class<?> type) {
//去掉包名,得到类名
String alias = type.getSimpleName();
//如果配置了注解,以注解上面的名称为准
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
registerAlias(alias, type);
} private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
//将别名作为key,别名代表的类作为value存入HashMap 中
public void registerAlias(String alias, Class<?> value) {
if (alias == null)
throw new TypeException("The parameter alias cannot be null");
//别名转成小写
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.put(key, value);
}

  这段代码其实作用就是将配置的别名作为key(全部转成小写,如果有配置注解,以注解为准),别名代表的类作为 value 存入 HashMap 中。

  接下来看 <typeAlias type="" alias=""/> 标签,它有两个属性,type和alias。其中如果 alias 为空,那么和解析 package 标签一样,缺省采用类名的小写。

  总结:

  ①、不管是通过 package 标签配置,还是通过 typeAlias 标签配置的别名,在mapper.xml文件中使用的时候,转换成小写是相等的,那么就可以使用。

  ②、如果不手动设置别名,默认是类名的小写。

  ③、如果配置了注解别名,注解别名会覆盖上面的所有配置。

  默认别名

  除了上面手动配置的别名以外,mybatis 还为我们默认配置了一系列的别名。

  1、在 TypeAliasRegistry.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);
}

  2、在 Configuration.class 类中

   public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
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);
languageRegistry.register(RawLanguageDriver.class);
}

  对于这些别名,我们可以在配置文件中直接使用,而不用额外配置了。

④、typeHandlers 类型处理器

  我们知道想Java数据类型和数据库数据类型是有区别的,而我们想通过Java代码来操作数据库或从数据库中取值的时候,必须要进行类型的转换。而  typeHandlers 便是来完成这一工作的。

  想要自定义一个类型处理器,必须要实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个类 org.apache.ibatis.type.BaseTypeHandler。

  配置举例:

 <typeHandlers>
<package name="org.mybatis.example"/>
</typeHandlers>

  或者:

 <typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>

  mybatis也为我们提供了许多内置的类型处理器,具体可以参考官网

  <environment>标签中的还有诸如 ObjectFactory 对象、plugin 插件、environment 环境、DatabaserIdProvider 数据库标识等配置,其中 plugin 插件特别重要,这属于 mybatis进阶内容,后面我们会详细讲解。

⑤、Mapper 映射器

  在 mybatis-configuration.xml 配置文件中有两个标签,一个是 <environments/> 用来配置数据源等信息。另一个就是 <mappers />标签了,用来进行 sql 文件映射。也就是说我们需要告诉 MyBatis 到哪里去找到这些语句。 Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。可以使用相对于类路径的资源引用, 或完全限定资源定位符(包括 file:/// 的 URL),或类名和包名等。例如:

 <!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>

  接下来我们追溯源码:

   private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}

  从上面的 if ("package".equals(child.getName())) {} else{} 可以看到在<mappers>标签中是可以同时存在子标签<package>和子标签<mapper>的,但是根据 dtd 约束:

Element : mappers
Content Model : (mapper*, package*)

  mapper子标签必须在package标签前面。实际应用中,package标签使用的比较少,这里就不贴源码对package进行分析了(需要注意的是,如果两个子标签同时存在,前面解析完mapper标签后,存在相同的接口名,会抛出异常)

 if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}

  下面我们来重点分析<mapper /> 子标签:也就是上面代码的第 8 行到第 26 行。

  首先第 8 行到第 10 行,读取子标签属性分别为 resource、url、class的值。然后看下面的if-else语句:

     if(resource !=null&&url ==null&&mapperClass ==null){

     }else if(resource ==null&&url !=null&&mapperClass ==null){

     }else if(resource==null&&url==null&&mapperClass!=null){

     }else{
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}

  第一个 if 表示 resource 值不为 null,且url 值和 mapperClass 值都为null。

  第二个else if 表示 url 值不为 null,且 resource 值和 mapperClass 值都为null。

  第三个 else if 表示 mapperClass 值不为 null,且 resource 值和 url 值都为null。

  第四个 else 表示如果三个都为null或者都不为null,或者有两个不为null,都会抛出异常。

  也就是说这三个标签有且仅有一个有值,其余两个都为null,才能正常执行。

  1、首先看第一个 if 语句:

 if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}

  第3行是获取 resource只能目录的字节流。

  第4行和前面讲解将 xml 文档解析为 Document 对象。

  第5行追溯源码:

   public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
} parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}

  第2行的代码判断了当前资源是否被加载过,如果没有被加载过则会执行第3行到第5行的代码。

  第3行代码从节点 mapper 出开始解析:

     private void configurationElement(XNode context) {
try {
//读取namespace属性值,如果为null或者为空,则抛出异常
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//解析cache-ref标签
cacheRefElement(context.evalNode("cache-ref"));
//解析cache标签
cacheElement(context.evalNode("cache"));
//解析/mapper/parameterMap标签
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析/mapper/resultMap标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析/mapper/sql标签
sqlElement(context.evalNodes("/mapper/sql"));
//解析select|insert|update|delete标签
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}

  配置的属性作用如下:

 cache – 给定命名空间的缓存配置。
cache-ref – 其他命名空间缓存配置的引用。
resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
sql – 可被其他语句引用的可重用语句块。
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语句

  对于第一个解析 cache-ref 标签:

     private void cacheRefElement(XNode context) {
if (context != null) {
//将mapper标签的的namespace作为key,<cache-ref>的namespace作为value存放Configuration对象的cacheRefMap中
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}

  其余的几个标签,其中对于 resultMap 标签的解析,以及对于 select|insert|update|delete 标签的解析是最重要也是最复杂的,后面会详细讲解。

  还有比较重要的对于如下标签的解析:

    <!-- 可以配置多个运行环境,但是每个 SqlSessionFactory 实例只能选择一个运行环境 一、development:开发模式 二、work:工作模式 -->
<environments default="development">
<!--id属性必须和上面的default一样 -->
<environment id="development">
<transactionManager type="JDBC" />
<!--dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象源 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>

  这是对于数据源以及事务的配置,这也是一个 ORM 框架最重要的一部分,后面也会详细讲解。

mybatis源码解读(二)——构建Configuration对象的更多相关文章

  1. Mybatis源码解析&lpar;二&rpar; —— 加载 Configuration

    Mybatis源码解析(二) -- 加载 Configuration    正如上文所看到的 Configuration 对象保存了所有Mybatis的配置信息,也就是说mybatis-config. ...

  2. MyBatis源码解读之延迟加载

    1. 目的 本文主要解读MyBatis 延迟加载实现原理 2. 延迟加载如何使用 Setting 参数配置 设置参数 描述 有效值 默认值 lazyLoadingEnabled 延迟加载的全局开关.当 ...

  3. spring IOC DI AOP MVC 事务&comma; mybatis 源码解读

    demo https://gitee.com/easybao/aop.git spring DI运行时序 AbstractApplicationContext类的 refresh()方法 1: pre ...

  4. MyBatis源码解读(3)——MapperMethod

    在前面两篇的MyBatis源码解读中,我们一路跟踪到了MapperProxy,知道了尽管是使用了动态代理技术使得我们能直接使用接口方法.为巩固加深动态代理,我们不妨再来回忆一遍何为动态代理. 我相信在 ...

  5. 【转】Mybatis源码解读-设计模式总结

    原文:http://www.crazyant.net/2022.html?jqbmtw=b90da1&gsjulo=kpzaa1 虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开 ...

  6. Mybatis源码解读-设计模式总结

    虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式. Mybatis至少 ...

  7. MyBatis源码解读(1)——SqlSessionFactory

    在前面对MyBatis稍微有点了解过后,现在来对MyBatis的源码试着解读一下,并不是解析,暂时定为解读.所有对MyBatis解读均是基于MyBatis-3.4.1,官网中文文档:http://ww ...

  8. jQuery&period;Callbacks 源码解读二

    一.参数标记 /* * once: 确保回调列表仅只fire一次 * unique: 在执行add操作中,确保回调列表中不存在重复的回调 * stopOnFalse: 当执行回调返回值为false,则 ...

  9. &lpar;转&rpar;go语言nsq源码解读二 nsqlookupd、nsqd与nsqadmin

    转自:http://www.baiyuxiong.com/?p=886 ---------------------------------------------------------------- ...

随机推荐

  1. c 函数及指针学习 9

    指针的高级应用 处理命令行参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h>   int main(int ar ...

  2. yii中的若干问题

    一直觉得”程序猿“是个很细致的工作,就像绣花一样,一不小心缝错一针,就可能是个很大的bug,但是为什么平时看起来大而化之的男同胞们确能在这方面如此care呢?? 以下进入正文,省去华丽丽的词语,这里仅 ...

  3. GO语言函数与类型

    package main import () import ( "fmt" "reflect" "errors" ) type age in ...

  4. Asp&period;Net Identity自定义user类的运用&comma;ClaimsIdentity

    mvc5自动生成的用户验证是比较好用的,还可以扩展,可是要求code first,目前使用sqlite,支持entity framework,但不支持code first. 只有自已简单模仿一下了.经 ...

  5. Web 应用配置Log4Net

    1.第一步:在web.config文件添加如下代码: [sourcecode language="csharp"] <configSections> <secti ...

  6. ThinkPHP 3&period;1&period;2 控制器的模块和操作

    本节课大纲: 一.空模块和空操作 1.空操作 function _empty($name){ $this->show("$name 不存在 <a href='__APP__/In ...

  7. ThinkPHP - 关联模型 - 多对多

    表结构: 映射关系: UserRelationModel会取UserRelation为表名称.所以要自定义表名称: //定义主表名称protected $tableName = 'User'; &lt ...

  8. JavaScript的类、对象、原型、继承、引用

    以CSS为例,有一种为所有class为"xxxx"的元素添加样式(外联样式),那么所有class为xxx的元素样式就会改变,在css中像下面这么写: <html> &l ...

  9. ditto复制增强

    1.下载 http://ditto-cp.sourceforge.net/ 2.用法   ctrl+` ctrl+数字 或者  ctrl +`  然后用鼠标选择 soeasy

  10. Dubbo 分布式服务框架

    要想了解Dubbo是什么,我们不防先了解它有什么用. 使用场景:比如我想开发一个网上商城项目,这个网上商城呢,比较复杂,分为pc端web管理后台,微信端销售公众号,那么我们分成四个项目,pc端网站,微 ...