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

本文详细解析了MyBatis配置文件的各项配置,包括基础配置、映射器配置、环境配置等,介绍了各配置项的作用及配置方法。

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

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

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

1、读取配置文件

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

1     private static SqlSessionFactory sqlSessionFactory;
2 
3     static{
4         InputStream inputStream = MybatisTest.class.getClassLoader().getResourceAsStream("mybatis-configuration.xml");
5         sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
6     }

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

 1     public SqlSessionFactory build(InputStream inputStream) {
 2         return build(inputStream, null, null);
 3     }
 4     public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
 5         try {
 6             XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
 7             return build(parser.parse());
 8         } catch (Exception e) {
 9             throw ExceptionFactory.wrapException("Error building SqlSession.", e);
10         } finally {
11             ErrorContext.instance().reset();
12             try {
13                 inputStream.close();
14             } catch (IOException e) {
15                 // Intentionally ignore. Prefer previous error.
16             }
17         }
18     }
19     public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
20         this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
21     }
22     public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
23         commonConstructor(validation, variables, entityResolver);
24         this.document = createDocument(new InputSource(inputStream));
25     }
26 
27     private Document createDocument(InputSource inputSource) {
28         // important: this must only be called AFTER common constructor
29         try {
30             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
31             factory.setValidating(validation);
32 
33             factory.setNamespaceAware(false);
34             factory.setIgnoringComments(true);
35             factory.setIgnoringElementContentWhitespace(false);
36             factory.setCoalescing(false);
37             factory.setExpandEntityReferences(true);
38 
39             DocumentBuilder builder = factory.newDocumentBuilder();
40             builder.setEntityResolver(entityResolver);
41             builder.setErrorHandler(new ErrorHandler() {
42                 @Override
43                 public void error(SAXParseException exception) throws SAXException {
44                     throw exception;
45                 }
46 
47                 @Override
48                 public void fatalError(SAXParseException exception) throws SAXException {
49                     throw exception;
50                 }
51 
52                 @Override
53                 public void warning(SAXParseException exception) throws SAXException {
54                 }
55             });
56             return builder.parse(inputSource);
57         } catch (Exception e) {
58             throw new BuilderException("Error creating document instance.  Cause: " + e, e);
59         }
60     }
View Code

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

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

  首先回到  SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) 方法:

 1   public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
 2     try {
 3       XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
 4       return build(parser.parse());
 5     } catch (Exception e) {
 6       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
 7     } finally {
 8       ErrorContext.instance().reset();
 9       try {
10         inputStream.close();
11       } catch (IOException e) {
12         // Intentionally ignore. Prefer previous error.
13       }
14     }
15   }

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

 1     public Configuration parse() {
 2         if (parsed) {
 3             throw new BuilderException("Each XMLConfigBuilder can only be used once.");
 4         }
 5         parsed = true;
 6         //从根节点 <configuration></configuration>处开始解析
 7         parseConfiguration(parser.evalNode("/configuration"));
 8         return configuration;
 9     }
10 
11     private void parseConfiguration(XNode root) {
12         try {
13             //分别解析相应的节点标签
14             propertiesElement(root.evalNode("properties"));
15             Properties settings = settingsAsProperties(root.evalNode("settings"));
16             loadCustomVfs(settings);
17             typeAliasesElement(root.evalNode("typeAliases"));
18             pluginElement(root.evalNode("plugins"));
19             objectFactoryElement(root.evalNode("objectFactory"));
20             objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
21             reflectorFactoryElement(root.evalNode("reflectorFactory"));
22             settingsElement(settings);
23             environmentsElement(root.evalNode("environments"));
24             databaseIdProviderElement(root.evalNode("databaseIdProvider"));
25             typeHandlerElement(root.evalNode("typeHandlers"));
26             //解析引入的mapper.xml文件
27             mapperElement(root.evalNode("mappers"));
28         } catch (Exception e) {
29             throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
30         }
31     }

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

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
 3 <configuration>
 4 
 5     <!-- 加载数据库属性文件 -->
 6     <properties resource="jdbc.properties">
 7     </properties>
 8     <!-- 可以配置多个运行环境,但是每个 SqlSessionFactory 实例只能选择一个运行环境 一、development:开发模式 二、work:工作模式 -->
 9     <environments default="development">
10         <!--id属性必须和上面的default一样 -->
11         <environment id="development">
12             <transactionManager type="JDBC" />
13             <!--dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象源 -->
14             <dataSource type="POOLED">
15                 <property name="driver" value="${jdbc.driver}" />
16                 <property name="url" value="${jdbc.url}" />
17                 <property name="username" value="${jdbc.username}" />
18                 <property name="password" value="${jdbc.password}" />
19             </dataSource>
20         </environment>
21     </environments>
22 
23     <mappers>
24         <mapper resource="com/ys/mapper/userMapper.xml"/>
25     </mappers>
26 </configuration>

2、初始化基础配置

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

①、properties 全局参数

  配置举例:

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

  具体读取详情:

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

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

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

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

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

  比如:

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

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

②、setting 设置

  配置举例:

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

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

  接着我们追溯源码:

 1   private void settingsElement(XNode context) throws Exception {
 2         if (context != null) {
 3           //读取所有子节点信息
 4           Properties props = context.getChildrenAsProperties();
 5           //检查所有setting配置文件的属性是否在 Configuration.class中存在set方法
 6           //如果不存在,则抛出异常
 7           MetaClass metaConfig = MetaClass.forClass(Configuration.class);
 8           for (Object key : props.keySet()) {
 9             if (!metaConfig.hasSetter(String.valueOf(key))) {
10               throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
11             }
12           }
13           //给configuration类中的属性初始化
14           configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
15           configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
16           configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
17           configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
18           configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
19           configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
20           configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
21           configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
22           configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
23           configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
24           configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
25           configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
26           configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
27           configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
28           configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
29           configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
30           configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
31           configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
32           configuration.setLogPrefix(props.getProperty("logPrefix"));
33           configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
34           configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
35         }
36 }

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

③、typeAliases 别名

  配置举例:

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

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

  具体用法可以参考官网

  接下来我们查看源码:

 1   private void typeAliasesElement(XNode parent) {
 2     if (parent != null) {
 3       for (XNode child : parent.getChildren()) {
 4         if ("package".equals(child.getName())) {
 5           String typeAliasPackage = child.getStringAttribute("name");
 6           configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
 7         } else {
 8           String alias = child.getStringAttribute("alias");
 9           String type = child.getStringAttribute("type");
10           try {
11             Class<?> clazz = Resources.classForName(type);
12             if (alias == null) {
13               typeAliasRegistry.registerAlias(clazz);
14             } else {
15               typeAliasRegistry.registerAlias(alias, clazz);
16             }
17           } catch (ClassNotFoundException e) {
18             throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
19           }
20         }
21       }
22     }
23   }

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

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

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

 1     public void registerAliases(String packageName) {
 2         registerAliases(packageName, Object.class);
 3     }
 4 
 5     public void registerAliases(String packageName, Class<?> superType) {
 6         ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
 7         //根据包名 packageName 获取包下所有的 .class 文件(反射)
 8         resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
 9         Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
10         //遍历所有的class,不能是匿名类、接口以及成员类
11         for (Class<?> type : typeSet) {
12             if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
13                 registerAlias(type);
14             }
15         }
16     }
17 
18     public void registerAlias(Class<?> type) {
19         //去掉包名,得到类名
20         String alias = type.getSimpleName();
21         //如果配置了注解,以注解上面的名称为准
22         Alias aliasAnnotation = type.getAnnotation(Alias.class);
23         if (aliasAnnotation != null) {
24             alias = aliasAnnotation.value();
25         }
26         registerAlias(alias, type);
27     }
28 
29     private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
30     //将别名作为key,别名代表的类作为value存入HashMap 中
31     public void registerAlias(String alias, Class<?> value) {
32         if (alias == null)
33             throw new TypeException("The parameter alias cannot be null");
34         //别名转成小写
35         String key = alias.toLowerCase(Locale.ENGLISH); 
36         if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
37             throw new TypeException("The alias '" + alias + "' is already mapped to the value '"
38                     + TYPE_ALIASES.get(key).getName() + "'.");
39         }
40         TYPE_ALIASES.put(key, value);
41     }

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

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

  总结:

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

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

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

  默认别名

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

  1、在 TypeAliasRegistry.class 类中

 1   public TypeAliasRegistry() {
 2     registerAlias("string", String.class);
 3 
 4     registerAlias("byte", Byte.class);
 5     registerAlias("long", Long.class);
 6     registerAlias("short", Short.class);
 7     registerAlias("int", Integer.class);
 8     registerAlias("integer", Integer.class);
 9     registerAlias("double", Double.class);
10     registerAlias("float", Float.class);
11     registerAlias("boolean", Boolean.class);
12 
13     registerAlias("byte[]", Byte[].class);
14     registerAlias("long[]", Long[].class);
15     registerAlias("short[]", Short[].class);
16     registerAlias("int[]", Integer[].class);
17     registerAlias("integer[]", Integer[].class);
18     registerAlias("double[]", Double[].class);
19     registerAlias("float[]", Float[].class);
20     registerAlias("boolean[]", Boolean[].class);
21 
22     registerAlias("_byte", byte.class);
23     registerAlias("_long", long.class);
24     registerAlias("_short", short.class);
25     registerAlias("_int", int.class);
26     registerAlias("_integer", int.class);
27     registerAlias("_double", double.class);
28     registerAlias("_float", float.class);
29     registerAlias("_boolean", boolean.class);
30 
31     registerAlias("_byte[]", byte[].class);
32     registerAlias("_long[]", long[].class);
33     registerAlias("_short[]", short[].class);
34     registerAlias("_int[]", int[].class);
35     registerAlias("_integer[]", int[].class);
36     registerAlias("_double[]", double[].class);
37     registerAlias("_float[]", float[].class);
38     registerAlias("_boolean[]", boolean[].class);
39 
40     registerAlias("date", Date.class);
41     registerAlias("decimal", BigDecimal.class);
42     registerAlias("bigdecimal", BigDecimal.class);
43     registerAlias("biginteger", BigInteger.class);
44     registerAlias("object", Object.class);
45 
46     registerAlias("date[]", Date[].class);
47     registerAlias("decimal[]", BigDecimal[].class);
48     registerAlias("bigdecimal[]", BigDecimal[].class);
49     registerAlias("biginteger[]", BigInteger[].class);
50     registerAlias("object[]", Object[].class);
51 
52     registerAlias("map", Map.class);
53     registerAlias("hashmap", HashMap.class);
54     registerAlias("list", List.class);
55     registerAlias("arraylist", ArrayList.class);
56     registerAlias("collection", Collection.class);
57     registerAlias("iterator", Iterator.class);
58 
59     registerAlias("ResultSet", ResultSet.class);
60   }
View Code

  2、在 Configuration.class 类中

 1   public Configuration() {
 2     typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
 3     typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
 4 
 5     typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
 6     typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
 7     typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
 8 
 9     typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
10     typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
11     typeAliasRegistry.registerAlias("LRU", LruCache.class);
12     typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
13     typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
14 
15     typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
16 
17     typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
18     typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
19 
20     typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
21     typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
22     typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
23     typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
24     typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
25     typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
26     typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
27 
28     typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
29     typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
30 
31     languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
32     languageRegistry.register(RawLanguageDriver.class);
33   }
View Code

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

④、typeHandlers 类型处理器

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

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

  配置举例:

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

  或者:

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

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

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

⑤、Mapper 映射器

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

 1 <!-- 使用相对于类路径的资源引用 -->
 2 <mappers>
 3   <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
 4   <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
 5   <mapper resource="org/mybatis/builder/PostMapper.xml"/>
 6 </mappers>
 7 <!-- 使用完全限定资源定位符(URL) -->
 8 <mappers>
 9   <mapper url="file:///var/mappers/AuthorMapper.xml"/>
10   <mapper url="file:///var/mappers/BlogMapper.xml"/>
11   <mapper url="file:///var/mappers/PostMapper.xml"/>
12 </mappers>
13 <!-- 使用映射器接口实现类的完全限定类名 -->
14 <mappers>
15   <mapper class="org.mybatis.builder.AuthorMapper"/>
16   <mapper class="org.mybatis.builder.BlogMapper"/>
17   <mapper class="org.mybatis.builder.PostMapper"/>
18 </mappers>
19 <!-- 将包内的映射器接口实现全部注册为映射器 -->
20 <mappers>
21   <package name="org.mybatis.builder"/>
22 </mappers>
View Code

  接下来我们追溯源码:

 1   private void mapperElement(XNode parent) throws Exception {
 2     if (parent != null) {
 3       for (XNode child : parent.getChildren()) {
 4         if ("package".equals(child.getName())) {
 5           String mapperPackage = child.getStringAttribute("name");
 6           configuration.addMappers(mapperPackage);
 7         } else {
 8           String resource = child.getStringAttribute("resource");
 9           String url = child.getStringAttribute("url");
10           String mapperClass = child.getStringAttribute("class");
11           if (resource != null && url == null && mapperClass == null) {
12             ErrorContext.instance().resource(resource);
13             InputStream inputStream = Resources.getResourceAsStream(resource);
14             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
15             mapperParser.parse();
16           } else if (resource == null && url != null && mapperClass == null) {
17             ErrorContext.instance().resource(url);
18             InputStream inputStream = Resources.getUrlAsStream(url);
19             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
20             mapperParser.parse();
21           } else if (resource == null && url == null && mapperClass != null) {
22             Class<?> mapperInterface = Resources.classForName(mapperClass);
23             configuration.addMapper(mapperInterface);
24           } else {
25             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
26           }
27         }
28       }
29     }
30   }

  从上面的 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语句:

1     if(resource !=null&&url ==null&&mapperClass ==null){
2     
3     }else if(resource ==null&&url !=null&&mapperClass ==null){
4 
5     }else if(resource==null&&url==null&&mapperClass!=null){
6         
7     }else{
8         throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
9     }

  第一个 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 语句:

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

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

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

  第5行追溯源码:

 1   public void parse() {
 2     if (!configuration.isResourceLoaded(resource)) {
 3       configurationElement(parser.evalNode("/mapper"));
 4       configuration.addLoadedResource(resource);
 5       bindMapperForNamespace();
 6     }
 7 
 8     parsePendingResultMaps();
 9     parsePendingCacheRefs();
10     parsePendingStatements();
11   }

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

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

 1     private void configurationElement(XNode context) {
 2         try {
 3             //读取namespace属性值,如果为null或者为空,则抛出异常
 4             String namespace = context.getStringAttribute("namespace");
 5             if (namespace == null || namespace.equals("")) {
 6                 throw new BuilderException("Mapper's namespace cannot be empty");
 7             }
 8             builderAssistant.setCurrentNamespace(namespace);
 9             //解析cache-ref标签
10             cacheRefElement(context.evalNode("cache-ref"));
11             //解析cache标签
12             cacheElement(context.evalNode("cache"));
13             //解析/mapper/parameterMap标签
14             parameterMapElement(context.evalNodes("/mapper/parameterMap"));
15             //解析/mapper/resultMap标签
16             resultMapElements(context.evalNodes("/mapper/resultMap"));
17             //解析/mapper/sql标签
18             sqlElement(context.evalNodes("/mapper/sql"));
19             //解析select|insert|update|delete标签
20             buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
21         } catch (Exception e) {
22             throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
23         }
24     }

  配置的属性作用如下:

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

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

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

  其余的几个标签,其中对于 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 框架最重要的一部分,后面也会详细讲解。

先看效果: https://renmaiwang.cn/s/jkhfz Hue系列产品将具备高度的个性化定制能力,并且借助内置红、蓝、绿三原色LED的灯泡,能够混合生成1600种不同色彩的灯光。 整个操作流程完全由安装于iPhone上的应用程序进行管理。 这一创新举措为智能照明控制领域带来了新的启示,国内相关领域的从业者也积极投身于相关研究。 鉴于Hue产品采用WiFi无线连接方式,而国内WiFi网络尚未全面覆盖,本研究选择应用为普及的蓝牙技术,通过手机蓝牙与单片机进行数据交互,进而产生可调节占空比的PWM信号,以此来控制LED驱动电路,实现LED的调光功能以及DIY调色方案。 本文重点阐述了一种于手机蓝牙通信的LED灯设计方案,该方案受到飞利浦Hue智能灯泡的启发,但考虑到国内WiFi网络的覆盖限制,故而选用为通用的蓝牙技术。 以下为相关技术细节的详尽介绍:1. **智能照明控制系统**:智能照明控制系统允许用户借助手机应用程序实现远程控制照明设备,提供个性化的调光及色彩调整功能。 飞利浦Hue作为行业领先者,通过红、蓝、绿三原色LED的混合,能够呈现1600种颜色,实现了全面的定制化体验。 2. **蓝牙通信技术**:蓝牙技术是一种低成本、短距离的无线传输方案,工作于2.4GHz ISM频段,具备即插即用和强抗干扰能力。 蓝牙协议栈由硬件层和软件层构成,提供通用访问Profile、服务发现应用Profile以及串口Profiles等丰富功能,确保不同设备间的良好互操作性。 3. **脉冲宽度调制调光**:脉冲宽度调制(PWM)是一种高效能的调光方式,通过调节脉冲宽度来控制LED的亮度。 当PWM频率超过200Hz时,人眼无法察觉明显的闪烁现象。 占空比指的...
### 使用DeepSeek API流动技术 在信息技术领域,结合DeepSeek API流动(Silicon-Based Flow)的技术主要体现在优化硬件资源利用和提升软件性能方面。对于网络连接的嵌入式设备环境而言,API的设计和发展正逐渐向适应此类新兴硬件趋势转变[^1]。 #### 集成DeepSeek API 为了有效集成DeepSeek API,在开发过程中需遵循特定指导原则: - **初始化配置**:确保所有必要的依赖项已安装并正确配置。这通常涉及设置访问密钥和其他认证凭证。 - **请求构建**:通过指定URL端点来创建HTTP请求对象,并附带适当参数以调用所需功能。例如,获取数据可能需要发送GET请求;而提交新记录则可能是POST操作。 ```python import requests url = "https://api.deepseek.example/v1/data" headers = {"Authorization": "Bearer YOUR_ACCESS_TOKEN"} response = requests.get(url, headers=headers) if response.status_code == 200: data = response.json() else: print(f"Error: {response.status_code}") ``` #### 应用于流技术 当涉及到具体应用时,可以考虑以下几个方面: - **实时监控与分析**:借助于强大的处理能力和高效的算法实现对大量传感器输入信息快速解析,从而支持精准决策制定过程中的即时反馈机制。 - **自动化部署流程**:利用容器化和服务网格架构简化应用程序生命周期管理任务的同时提高灵活性及可扩展性特性。 结构体`security_hook_list`展示了Linux安全模块钩子列表结构,虽然这不是直接关联的例子,但它体现了底层操作系统层面如何通过精心设计的数据结构促进不同组件间的协作效率[^3]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员可乐、

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值