mybatis配置加载阶段源码之XMLConfigBuilder

迷南。 2022-11-07 04:06 224阅读 0赞

文章目录

  • 作用
  • 构造方法
  • 配置解析
    • 标签解析
      • properties
      • setting
      • typeAliases
      • plugins
      • objectFactory
      • objectWrapperFactory
      • reflectorFactory
      • environments
      • databaseIdProvider
      • typeHandlers
      • mappers

作用

XMLConfigBuilder 的作用是解析mybatis-config.xml配置文件,它是在SqlSessionFactoryBuilder被初始化的,然后调用XMLConfigBuilder 对象的parse 方法开始解析配置文件。
在这里插入图片描述

构造方法

XMLConfigBuilder 继承了BaseBuilder,它有七个构造方法,
在这里插入图片描述
但最终调用的是XMLConfigBuilder(XPathParser parser, String environment, Properties props)

  1. private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  2. // 初始化默认配置
  3. super(new Configuration());
  4. ErrorContext.instance().resource("SQL Mapper Configuration");
  5. this.configuration.setVariables(props);
  6. this.parsed = false;
  7. this.environment = environment;
  8. this.parser = parser;
  9. }

配置解析

XMLConfigBuilder 真正开始进行配置解析的 ,parse()方法是解析的开始,在方法parseConfiguration(XNode root) 进行各个节点的解析
parse()

  1. public Configuration parse() {
  2. // 已经解析过不能再次解析
  3. if (parsed) {
  4. throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  5. }
  6. parsed = true;
  7. parseConfiguration(parser.evalNode("/configuration"));
  8. return configuration;
  9. }

parseConfiguration

  1. /** * 解析配置文件 * @param root */
  2. private void parseConfiguration(XNode root) {
  3. try {
  4. // issue #117 read properties first
  5. // 解析properties 节点
  6. propertiesElement(root.evalNode("properties"));
  7. Properties settings = settingsAsProperties(root.evalNode("settings"));
  8. loadCustomVfs(settings);
  9. loadCustomLogImpl(settings);
  10. // 解析 typeAliases 标签
  11. typeAliasesElement(root.evalNode("typeAliases"));
  12. pluginElement(root.evalNode("plugins"));
  13. objectFactoryElement(root.evalNode("objectFactory"));
  14. objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  15. reflectorFactoryElement(root.evalNode("reflectorFactory"));
  16. settingsElement(settings);
  17. // read it after objectFactory and objectWrapperFactory issue #631
  18. // 解析环境environments 标签
  19. environmentsElement(root.evalNode("environments"));
  20. databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  21. typeHandlerElement(root.evalNode("typeHandlers"));
  22. // 解析SQL 文件
  23. mapperElement(root.evalNode("mappers"));
  24. } catch (Exception e) {
  25. throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  26. }
  27. }

标签解析

配置文件的标签如下:
configuration 配置
   properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
以上每个标签都有各自的作用

properties

properties 可以配置一些属性值,然后在配置文件中动态替换。在配置文件中是下面这样的

  1. <properties resource="db.properties">
  2. <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
  3. </properties>

解析步骤:
1、如果有properties标签,先把properties标签下的子标签property进行解析,name作为key,value 作为value,以键值对的形式返回到java.util.Properties 对象中,defaults接收
2、解析properties 标签属性,resource 和 url ,二者只能配置一个,否则会报错,
3、把 配置文件中的属性解析出来,然后放到defaults 中,由于是hashtable存储,所以会覆盖掉property属性中相同key的值
4、把解析到的key-value 设值到XMLConfigBuilder的parser属性的variables和configuration对象的variables属性中以便后续进行属性替换

  1. /** * 解析properties 标签中的变量 * 并最终把解析到的变量放到configuration 中的variables字段中 * @param context * @throws Exception */
  2. private void propertiesElement(XNode context) throws Exception {
  3. if (context != null) {
  4. // 解析默认的property 标签
  5. Properties defaults = context.getChildrenAsProperties();
  6. String resource = context.getStringAttribute("resource");
  7. String url = context.getStringAttribute("url");
  8. // resource 和 url 属性不能同时为空
  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. defaults.putAll(Resources.getResourceAsProperties(resource));
  14. } else if (url != null) {
  15. defaults.putAll(Resources.getUrlAsProperties(url));
  16. }
  17. Properties vars = configuration.getVariables();
  18. if (vars != null) {
  19. defaults.putAll(vars);
  20. }
  21. parser.setVariables(defaults);
  22. configuration.setVariables(defaults);
  23. }
  24. }

setting

setting 是MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为,有关setting的标签可以查看 官网有详细的介绍,这里主要是看下源码
setting的加载有四个步骤:
1、判断setting 子标签中的name属性对应的值是否是Configuration的属性,不是则抛异常

  1. /** * 判断setting 子标签中的name属性对应的值是否是Configuration的属性,不是则抛异常 * @param context * @return */
  2. private Properties settingsAsProperties(XNode context) {
  3. if (context == null) {
  4. return new Properties();
  5. }
  6. Properties props = context.getChildrenAsProperties();
  7. // Check that all settings are known to the configuration class
  8. MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
  9. for (Object key : props.keySet()) {
  10. // 判断是否有set 方法
  11. if (!metaConfig.hasSetter(String.valueOf(key))) {
  12. throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
  13. }
  14. }
  15. return props;
  16. }

2、设值自定义的VFS 实现类并设值到configuration对象中

  1. private void loadCustomVfs(Properties props) throws ClassNotFoundException {
  2. String value = props.getProperty("vfsImpl");
  3. if (value != null) {
  4. String[] clazzes = value.split(",");
  5. for (String clazz : clazzes) {
  6. if (!clazz.isEmpty()) {
  7. @SuppressWarnings("unchecked")
  8. Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
  9. configuration.setVfsImpl(vfsImpl);
  10. }
  11. }
  12. }
  13. }

3、加载自定义的日志实现类并设值到configuration对象中

  1. private void loadCustomLogImpl(Properties props) {
  2. Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
  3. configuration.setLogImpl(logImpl);
  4. }

4、将setting中其他配置加载并设值到configuration对象中

  1. /** * setting 属性值设值到configuration对象中 * @param props */
  2. private void settingsElement(Properties props) {
  3. configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
  4. configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
  5. configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
  6. configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
  7. configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
  8. configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
  9. configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
  10. configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
  11. configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
  12. configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
  13. configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
  14. configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
  15. configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
  16. configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
  17. configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
  18. configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
  19. configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
  20. configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
  21. configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
  22. configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
  23. configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
  24. configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
  25. configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
  26. configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
  27. configuration.setLogPrefix(props.getProperty("logPrefix"));
  28. configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  29. configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
  30. configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
  31. }

typeAliases

使用 typeAliases 标签给解析到的类起一个别名,或者也可以自己给类自定义别名。
使用方法如下:

  1. <typeAliases>
  2. <typeAlias alias="User" type="entity.UserInfo"/>
  3. <!-- <package name="entity.UserInfo"/>-->
  4. </typeAliases>

解析的方法是typeAliasesElement

  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. }

该方法会解析typeAliases 下的所有标签元素如果解析到package标签,它会package标签下指定的包下的所有类都给注册到configuration#typeAliasRegistry别名注册表中,注册时会把类名全部转成小写字母然后作为key,类权限定名作为value,注册到注册表中。
如果解析到typeAlias标签,会获取alias和type 属性,如果alias属性没有会判断type 类上是否有Alias 注解,如果有就用Alias注解中的表名作为key,没有类名转为小写字母注册。
注册源码如下:

  1. public void registerAlias(Class<?> type) {
  2. String alias = type.getSimpleName();
  3. Alias aliasAnnotation = type.getAnnotation(Alias.class);
  4. if (aliasAnnotation != null) {
  5. alias = aliasAnnotation.value();
  6. }
  7. registerAlias(alias, type);
  8. }

两个标签最终都会调用TypeAliasRegistry#registerAlias(String alias, Class<?> value)方法

  1. public void registerAlias(String alias, Class<?> value) {
  2. if (alias == null) {
  3. throw new TypeException("The parameter alias cannot be null");
  4. }
  5. // issue #748
  6. String key = alias.toLowerCase(Locale.ENGLISH);
  7. if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
  8. throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
  9. }
  10. typeAliases.put(key, value);
  11. }

注意:
package 和typeAlias 这两个标签最好不要同时使用,如果同一个类被注册两次是会抛异常的,所以最好不要重复扫描。

plugins

plugins 标签中可以指定自定义实现Interceptor的拦截器。官网 关于plugins插件讲的也比较详细。
解析的源码如下:

  1. private void pluginElement(XNode parent) throws Exception {
  2. if (parent != null) {
  3. for (XNode child : parent.getChildren()) {
  4. String interceptor = child.getStringAttribute("interceptor");
  5. Properties properties = child.getChildrenAsProperties();
  6. Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
  7. interceptorInstance.setProperties(properties);
  8. configuration.addInterceptor(interceptorInstance);
  9. }
  10. }
  11. }

它会把解析到的自定义拦截器注册到configuration#interceptorChain 拦截器链中,InterceptorChain 类中维护中一个Interceptor list数组,使用的时候会循环调用Interceptor 的plugin方法,最终是通过动态代理使用调用的。

  1. public class InterceptorChain {
  2. private final List<Interceptor> interceptors = new ArrayList<>();
  3. public Object pluginAll(Object target) {
  4. for (Interceptor interceptor : interceptors) {
  5. target = interceptor.plugin(target);
  6. }
  7. return target;
  8. }
  9. public void addInterceptor(Interceptor interceptor) {
  10. interceptors.add(interceptor);
  11. }
  12. public List<Interceptor> getInterceptors() {
  13. return Collections.unmodifiableList(interceptors);
  14. }
  15. }

objectFactory

objectWrapperFactory

reflectorFactory

environments

databaseIdProvider

objectFactory、objectWrapperFactory、reflectorFactory、environments、databaseIdProvider
这几个官网介绍也详细,源码感兴趣的可以看一下。

typeHandlers

typeHandlers 类型处理器 是在数据库查到结果进行映射时使用的。
在XMLConfigBuilder父类BaseBuilder中维护着一个typeHandlerRegistry 注册表,解析到的类型处理器都会注册到注册表中,跟typeAliasRegistry类似,只不过作用不一样,它是以Type对象作为key,以Map>对象作为value

  1. /** * 类型映射 * @param parent */
  2. private void typeHandlerElement(XNode parent) {
  3. if (parent != null) {
  4. for (XNode child : parent.getChildren()) {
  5. if ("package".equals(child.getName())) {
  6. String typeHandlerPackage = child.getStringAttribute("name");
  7. typeHandlerRegistry.register(typeHandlerPackage);
  8. } else {
  9. String javaTypeName = child.getStringAttribute("javaType");
  10. String jdbcTypeName = child.getStringAttribute("jdbcType");
  11. String handlerTypeName = child.getStringAttribute("handler");
  12. Class<?> javaTypeClass = resolveClass(javaTypeName);
  13. JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
  14. Class<?> typeHandlerClass = resolveClass(handlerTypeName);
  15. if (javaTypeClass != null) {
  16. if (jdbcType == null) {
  17. typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
  18. } else {
  19. typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
  20. }
  21. } else {
  22. typeHandlerRegistry.register(typeHandlerClass);
  23. }
  24. }
  25. }
  26. }
  27. }

最终调用的方法
TypeHandlerRegistry#register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler)

  1. private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
  2. if (javaType != null) {
  3. Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
  4. if (map == null || map == NULL_TYPE_HANDLER_MAP) {
  5. map = new HashMap<>();
  6. }
  7. map.put(jdbcType, handler);
  8. typeHandlerMap.put(javaType, map);
  9. }
  10. allTypeHandlersMap.put(handler.getClass(), handler);
  11. }

mappers

在以上配置都解析完成以后mappers 映射才开始解析,它是解析*Mpper.xml文件的。
mappers 标签是告诉mybatis去哪里找SQL映射文件, 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 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>

XMLConfigBuilder#mapperElement(XNode parent)源码如下,在mapperElement方法中又引出了mybatis初始化的另一个核心类XMLMapperBuilder。

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

解析步骤:
1、先解析是否有package标签,有的话就把对象包下的Mpper类注册到configuration对象下的mapperRegistry注册表中。最终调用的方法是MapperRegistry#addMapper(Class type)方法,
addMapper 方法中会对
Mapper类进行注解扫描。先看addMapper 源码

  1. /** * 把mapper 接口添加到 knownMappers 中注册中心 * @param type * @param <T> */
  2. public <T> void addMapper(Class<T> type) {
  3. if (type.isInterface()) {
  4. if (hasMapper(type)) {
  5. throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
  6. }
  7. boolean loadCompleted = false;
  8. try {
  9. // 建立mapper 和 MapperProxyFactory 的连接
  10. knownMappers.put(type, new MapperProxyFactory<>(type));
  11. // It's important that the type is added before the parser is run
  12. // otherwise the binding may automatically be attempted by the
  13. // mapper parser. If the type is already known, it won't try.
  14. // 解析接口上的注解信息并添加到configuration对象中
  15. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  16. parser.parse();
  17. loadCompleted = true;
  18. } finally {
  19. if (!loadCompleted) {
  20. knownMappers.remove(type);
  21. }
  22. }
  23. }
  24. }

MapperAnnotationBuilder#parse 进行注解扫描并解析映射器方法的@Selcet的注解,生成MappedStatement对象,MappedStatement是存储Selcet、update、insert、delete节点的信息的,里面包含着这些节点的重要信息,MappedStatement在以后的文章中会介绍。

  1. public void parse() {
  2. String resource = type.toString();
  3. if (!configuration.isResourceLoaded(resource)) {
  4. loadXmlResource();
  5. configuration.addLoadedResource(resource);
  6. assistant.setCurrentNamespace(type.getName());
  7. parseCache();
  8. parseCacheRef();
  9. for (Method method : type.getMethods()) {
  10. if (!canHaveStatement(method)) {
  11. continue;
  12. }
  13. // 如果方法有 Select、 SelectProvider注解 并且有ResultMap 注解,解析 方法
  14. if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
  15. && method.getAnnotation(ResultMap.class) == null) {
  16. parseResultMap(method);
  17. }
  18. try {
  19. //解析节点信息
  20. parseStatement(method);
  21. } catch (IncompleteElementException e) {
  22. configuration.addIncompleteMethod(new MethodResolver(this, method));
  23. }
  24. }
  25. }
  26. parsePendingMethods();
  27. }

2、解析mapper标签,获取resource、url、class属性并解析,解析resource和url时会生成 XMLMapperBuilder 对象,通过XMLMapperBuilder 解析获取*Mapper.xml信息,解析class属性时跟解析pakage标签一样进行映射器注册。

  1. // 使用相对于类路径的资源引用
  2. String resource = child.getStringAttribute("resource");
  3. // 使用完全限定资源定位符(URL)
  4. String url = child.getStringAttribute("url");
  5. //使用映射器接口实现类的完全限定类名
  6. String mapperClass = child.getStringAttribute("class");
  7. if (resource != null && url == null && mapperClass == null) {
  8. ErrorContext.instance().resource(resource);
  9. try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
  10. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  11. mapperParser.parse();
  12. }
  13. } else if (resource == null && url != null && mapperClass == null) {
  14. ErrorContext.instance().resource(url);
  15. try(InputStream inputStream = Resources.getUrlAsStream(url)){
  16. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
  17. mapperParser.parse();
  18. }
  19. } else if (resource == null && url == null && mapperClass != null) {
  20. Class<?> mapperInterface = Resources.classForName(mapperClass);
  21. configuration.addMapper(mapperInterface);
  22. } else {
  23. throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
  24. }

注意:
package标签是 扫描Mapper类的,它会把mapper类注册到mapper注册表中,同时会扫描类中的方法是否有SQL语句的注解,例如:@Selcet、@Insert等,如果使用的是mybatis全注解的话可以使用package标签,当然混用也行,但是切记不要将同一个*Mapper.java 不要注册两次,否则会报错,也就是说package 和 mapper 标签不要进行重复注册。

XMLConfigBuilder 内容大致就这些,如果感兴趣可以自己探索一下源码。
能力有限,水平一般,如有错误,请多指出。

发表评论

表情:
评论列表 (有 0 条评论,224人围观)

还没有评论,来说两句吧...

相关阅读