乐于分享
好东西不私藏

《别只会写XML了!从源码看懂MyBatis配置系统如何驱动整个框架》

《别只会写XML了!从源码看懂MyBatis配置系统如何驱动整个框架》

第2篇:配置系统深度解析

1. 配置系统概述

1.0 第1篇思考题解答

在深入学习配置系统之前,让我们先回顾并解答第1篇中提出的思考题,这将帮助我们更好地理解配置系统在整个架构中的作用。

思考题1:为什么 MyBatis 要采用三层架构设计?

答案要点

  • • 职责分离:接口层提供API,核心处理层处理业务逻辑,基础支持层提供基础服务
  • • 降低耦合:每层只依赖下一层,通过接口通信
  • • 提高可扩展性:每层可独立扩展,支持不同实现策略
  • • 便于维护:架构清晰,问题定位容易

配置系统的作用:Configuration 作为基础支持层的核心,为上层提供统一的配置管理服务。

思考题2:各个核心组件的职责分工有什么优势?

答案要点

  • • 单一职责:每个组件专注特定功能,降低复杂度
  • • 高内聚低耦合:组件内部高度相关,组件间依赖最小化
  • • 协作机制:通过接口抽象、依赖注入、配置驱动实现协作

配置系统的协作:Configuration 通过依赖注入为其他组件提供配置信息,实现松耦合的协作。

思考题3:如何理解 MyBatis 的”半自动化”特性?

答案要点

  • • 自动化部分:JDBC连接管理、参数绑定、结果映射、事务管理、缓存管理
  • • 手动控制部分:SQL编写、映射配置、事务边界、性能优化
  • • 优势:性能控制精确、灵活性高、学习成本适中

配置系统的作用:通过配置实现自动化和手动控制的平衡,提供灵活的配置机制。

思考题4:应该从哪个组件开始深入源码分析?

推荐顺序:Configuration → SqlSession → Executor → StatementHandler

从 Configuration 开始的原因

  • • Configuration 是配置系统的核心,其他组件都依赖它
  • • 理解配置系统有助于理解整个系统的构建过程
  • • 为后续学习其他组件奠定基础

1.1 配置系统的作用和重要性

MyBatis 的配置系统是整个框架的核心基础,它负责:

  1. 1. 统一配置管理:集中管理所有 MyBatis 相关的配置项
  2. 2. 配置解析:解析 XML 和注解配置,构建内部数据结构
  3. 3. 配置验证:验证配置的正确性和完整性
  4. 4. 配置扩展:支持自定义配置项和扩展功能
  5. 5. 性能优化:提供配置缓存和懒加载机制

重要提示:理解配置系统是深入 MyBatis 源码的关键,后续的会话管理、执行器、缓存等模块都依赖于配置系统。

1.2 配置文件的层次结构

MyBatis 的配置系统采用分层设计:

配置系统├── 主配置文件 (mybatis-config.xml)│   ├── 环境配置 (environments)│   ├── 数据源配置 (dataSource)│   ├── 事务管理配置 (transactionManager)│   ├── 类型别名配置 (typeAliases)│   ├── 类型处理器配置 (typeHandlers)│   ├── 插件配置 (plugins)│   ├── 缓存配置 (cache)│   └── Mapper 配置 (mappers)├── Mapper XML 配置文件│   ├── SQL 语句定义│   ├── 结果映射定义│   ├── 参数映射定义│   └── 缓存配置└── Mapper 接口注解配置    ├── @Select、@Insert、@Update、@Delete    ├── @Results、@Result    └── @Param、@Options

1.3 配置系统的核心组件

组件
职责
关键类
配置中心
统一管理所有配置项
Configuration
XML 解析器
解析主配置文件
XMLConfigBuilder
Mapper 解析器
解析 Mapper XML
XMLMapperBuilder
注解解析器
解析 Mapper 注解
MapperAnnotationBuilder
配置验证器
验证配置正确性
内置验证逻辑

2. Configuration 类深度解析

2.1 Configuration 类的结构和职责

Configuration 类是 MyBatis 配置系统的核心,它承担着以下职责:

  1. 1. 配置存储:存储所有 MyBatis 配置项
  2. 2. 配置管理:提供配置项的增删改查功能
  3. 3. 配置验证:验证配置的正确性和完整性
  4. 4. 配置扩展:支持插件和自定义配置
  5. 5. 性能优化:提供配置缓存和懒加载

2.2 核心属性分析

让我们深入分析 Configuration 类的核心属性:

publicclassConfiguration {// 环境配置protected Environment environment;// 数据库相关配置protectedboolean safeRowBoundsEnabled;protectedboolean safeResultHandlerEnabled;protectedboolean mapUnderscoreToCamelCase;protectedboolean aggressiveLazyLoading;protectedboolean multipleResultSetsEnabled;protectedboolean useGeneratedKeys;protectedboolean useColumnLabel;protectedboolean callSettersOnNulls;protectedboolean useActualParamName;protectedboolean returnInstanceForEmptyRow;// 日志配置protected String logPrefix;protected Class<? extendsLog> logImpl;// 缓存配置protectedboolean cacheEnabled;protected LocalCacheScope localCacheScope;// 类型处理配置protected JdbcType jdbcTypeForNull;protected Set<String> lazyLoadTriggerMethods;// 超时配置protected Integer defaultStatementTimeout;protected Integer defaultFetchSize;protected ResultSetType defaultResultSetType;// 执行器配置protected ExecutorType defaultExecutorType;// 映射配置protected AutoMappingBehavior autoMappingBehavior;protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior;// 核心注册表protected ReflectorFactory reflectorFactory;protected ObjectFactory objectFactory;protected ObjectWrapperFactory objectWrapperFactory;protected MapperRegistry mapperRegistry;protected InterceptorChain interceptorChain;protected TypeHandlerRegistry typeHandlerRegistry;protected TypeAliasRegistry typeAliasRegistry;protected LanguageDriverRegistry languageRegistry;// 映射存储protected Map<String, MappedStatement> mappedStatements;protected Map<String, Cache> caches;protected Map<String, ResultMap> resultMaps;protected Map<String, ParameterMap> parameterMaps;protected Map<String, KeyGenerator> keyGenerators;// 其他配置protected Properties variables;protected Set<String> loadedResources;protected String databaseId;protected Class<?> configurationFactory;protected Map<String, String> cacheRefMap;}

2.3 核心方法分析

2.3.1 配置项管理方法

// 添加 MappedStatementpublicvoidaddMappedStatement(MappedStatement ms) {    mappedStatements.put(ms.getId(), ms);}// 获取 MappedStatementpublic MappedStatement getMappedStatement(String id) {return mappedStatements.get(id);}// 添加 Mapperpublic <T> voidaddMapper(Class<T> type) {    mapperRegistry.addMapper(type);}// 获取 Mapperpublic <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);}

2.3.2 配置验证方法

// 验证配置完整性publicvoidvalidate() {// 验证必要的配置项if (environment == null) {thrownewIllegalStateException("Environment was not set");    }// 验证 Mapper 配置for (MappedStatement ms : mappedStatements.values()) {if (ms.getCache() != null && ms.getCache().getClass().equals(PerpetualCache.class)) {// 验证缓存配置        }    }}

3. XML 配置解析流程

3.1 XMLConfigBuilder 源码分析

XMLConfigBuilder 是 MyBatis 主配置文件的解析器,它继承自 BaseBuilder:

publicclassXMLConfigBuilderextendsBaseBuilder {privateboolean parsed;privatefinal XPathParser parser;private String environment;privatefinalReflectorFactorylocalReflectorFactory=newDefaultReflectorFactory();publicXMLConfigBuilder(Reader reader) {this(reader, nullnull);    }publicXMLConfigBuilder(Reader reader, String environment) {this(reader, environment, null);    }publicXMLConfigBuilder(Reader reader, String environment, Properties props) {super(newConfiguration());this.environment = environment;this.parser = newXPathParser(reader, true, props, newXMLMapperEntityResolver());    }public Configuration parse() {if (parsed) {thrownewBuilderException("Each XMLConfigBuilder can only be used once.");        }        parsed = true;        parseConfiguration(parser.evalNode("/configuration"));return configuration;    }privatevoidparseConfiguration(XNode root) {try {// 解析 properties 配置            propertiesElement(root.evalNode("properties"));// 解析 settings 配置Propertiessettings= settingsAsProperties(root.evalNode("settings"));            loadCustomVfs(settings);            loadCustomLogImpl(settings);            loadCustomInterceptors(settings);            loadCustomTypeHandlers(settings);            loadCustomObjectFactory(settings);            loadCustomObjectWrapperFactory(settings);            loadCustomReflectorFactory(settings);            settingsElement(settings);// 解析 typeAliases 配置            typeAliasesElement(root.evalNode("typeAliases"));// 解析 plugins 配置            pluginElement(root.evalNode("plugins"));// 解析 objectFactory 配置            objectFactoryElement(root.evalNode("objectFactory"));// 解析 objectWrapperFactory 配置            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));// 解析 reflectorFactory 配置            reflectorFactoryElement(root.evalNode("reflectorFactory"));// 解析 settings 配置            settingsElement(settings);// 解析 environments 配置            environmentsElement(root.evalNode("environments"));// 解析 databaseIdProvider 配置            databaseIdProviderElement(root.evalNode("databaseIdProvider"));// 解析 typeHandlers 配置            typeHandlerElement(root.evalNode("typeHandlers"));// 解析 mappers 配置            mapperElement(root.evalNode("mappers"));        } catch (Exception e) {thrownewBuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);        }    }}

3.2 主配置文件解析过程

3.2.1 Properties 配置解析

privatevoidpropertiesElement(XNode context)throws Exception {if (context != null) {Propertiesdefaults= context.getChildrenAsProperties();Stringresource= context.getStringAttribute("resource");Stringurl= context.getStringAttribute("url");if (resource != null && url != null) {thrownewBuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");        }if (resource != null) {            defaults.putAll(Resources.getResourceAsProperties(resource));        } elseif (url != null) {            defaults.putAll(Resources.getUrlAsProperties(url));        }Propertiesvars= configuration.getVariables();if (vars != null) {            defaults.putAll(vars);        }        parser.setVariables(defaults);        configuration.setVariables(defaults);    }}

3.2.2 Settings 配置解析

privatevoidsettingsElement(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.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));    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.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));    configuration.setLogPrefix(props.getProperty("logPrefix"));    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));}

3.3 配置项验证和默认值处理

MyBatis 在解析配置时会进行以下验证:

  1. 1. 必需配置验证:检查必需的配置项是否存在
  2. 2. 配置值验证:验证配置值的有效性和范围
  3. 3. 依赖关系验证:检查配置项之间的依赖关系
  4. 4. 默认值设置:为未配置的项设置合理的默认值

4. Mapper 配置解析

4.1 XMLMapperBuilder 源码分析

XMLMapperBuilder 负责解析 Mapper XML 文件:

publicclassXMLMapperBuilderextendsBaseBuilder {privatefinal XPathParser parser;privatefinal MapperBuilderAssistant builderAssistant;privatefinal Map<String, XNode> sqlFragments;privatefinal String resource;publicXMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {this(newXPathParser(inputStream, true, configuration.getVariables(), newXMLMapperEntityResolver()), configuration, resource, sqlFragments);    }publicXMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {this(newXPathParser(reader, true, configuration.getVariables(), newXMLMapperEntityResolver()), configuration, resource, sqlFragments);    }privateXMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {super(configuration);this.builderAssistant = newMapperBuilderAssistant(configuration, resource);this.parser = parser;this.sqlFragments = sqlFragments;this.resource = resource;    }publicvoidparse() {if (!configuration.isResourceLoaded(resource)) {            configurationElement(parser.evalNode("/mapper"));            configuration.addLoadedResource(resource);            bindMapperForNamespace();        }        parsePendingResultMaps();        parsePendingCacheRefs();        parsePendingStatements();    }privatevoidconfigurationElement(XNode context) {try {Stringnamespace= context.getStringAttribute("namespace");if (namespace == null || namespace.isEmpty()) {thrownewBuilderException("Mapper's namespace cannot be empty");            }            builderAssistant.setCurrentNamespace(namespace);            cacheRefElement(context.evalNode("cache-ref"));            cacheElement(context.evalNode("cache"));            parameterMapElement(context.evalNodes("/mapper/parameterMap"));            resultMapElements(context.evalNodes("/mapper/resultMap"));            sqlElement(context.evalNodes("/mapper/sql"));            buildStatementFromContext(context.evalNodes("select|insert|update|delete"));        } catch (Exception e) {thrownewBuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);        }    }}

4.2 Mapper 接口和 XML 的绑定

4.2.1 命名空间绑定

privatevoidbindMapperForNamespace() {Stringnamespace= builderAssistant.getCurrentNamespace();if (namespace != null) {        Class<?> boundType = null;try {            boundType = Resources.classForName(namespace);        } catch (ClassNotFoundException e) {// ignore, bound type is not required        }if (boundType != null) {if (!configuration.hasMapper(boundType)) {                configuration.addLoadedResource("namespace:" + namespace);                configuration.addMapper(boundType);            }        }    }}

4.2.2 SQL 语句解析

privatevoidbuildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {        buildStatementFromContext(list, configuration.getDatabaseId());    }    buildStatementFromContext(list, null);}privatevoidbuildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {finalXMLStatementBuilderstatementParser=newXMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {            statementParser.parseStatementNode();        } catch (IncompleteElementException e) {            configuration.addIncompleteStatement(statementParser);        }    }}

4.3 SQL 语句的解析和存储

4.3.1 XMLStatementBuilder 源码分析

publicclassXMLStatementBuilderextendsBaseBuilder {privatefinal MapperBuilderAssistant builderAssistant;privatefinal XNode context;privatefinal String requiredDatabaseId;publicXMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {super(configuration);this.builderAssistant = builderAssistant;this.context = context;this.requiredDatabaseId = databaseId;    }publicvoidparseStatementNode() {Stringid= context.getStringAttribute("id");StringdatabaseId= context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;        }StringnodeName= context.getNode().getNodeName();SqlCommandTypesqlCommandType= SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));booleanisSelect= sqlCommandType == SqlCommandType.SELECT;booleanflushCache= context.getBooleanAttribute("flushCache", !isSelect);booleanuseCache= context.getBooleanAttribute("useCache", isSelect);booleanresultOrdered= context.getBooleanAttribute("resultOrdered"false);// 解析 SQL 语句XMLIncludeTransformerincludeParser=newXMLIncludeTransformer(configuration, builderAssistant);        includeParser.applyIncludes(context.getNode());        processSelectKeyNodes(id, parameterTypeClass, langDriver);SqlSourcesqlSource= langDriver.createSqlSource(configuration, context, parameterTypeClass);StatementTypestatementType= StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));IntegerfetchSize= context.getIntAttribute("fetchSize");Integertimeout= context.getIntAttribute("timeout");StringparameterMap= context.getStringAttribute("parameterMap");StringresultType= context.getStringAttribute("resultType");        Class<?> resultTypeClass = resolveClass(resultType);StringresultMap= context.getStringAttribute("resultMap");StringresultSetType= context.getStringAttribute("resultSetType");ResultSetTyperesultSetTypeEnum= resolveResultSetType(resultSetType);StringkeyProperty= context.getStringAttribute("keyProperty");StringkeyColumn= context.getStringAttribute("keyColumn");StringresultSets= context.getStringAttribute("resultSets");        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);    }}

5. 注解配置解析

5.1 MapperAnnotationBuilder 源码分析

MapperAnnotationBuilder 负责解析 Mapper 接口上的注解:

publicclassMapperAnnotationBuilder {privatefinal Configuration configuration;privatefinal MapperBuilderAssistant builderAssistant;privatefinal Class<?> type;publicMapperAnnotationBuilder(Configuration configuration, Class<?> type) {Stringresource= type.getName().replace('.''/') + ".java (best guess)";this.configuration = configuration;this.builderAssistant = newMapperBuilderAssistant(configuration, resource);this.type = type;    }publicvoidparse() {Stringresource= type.toString();if (!configuration.isResourceLoaded(resource)) {            loadXmlResource();            configuration.addLoadedResource(resource);            assistant.setCurrentNamespace(type.getName());            parseCache();            parseCacheRef();            Method[] methods = type.getMethods();for (Method method : methods) {try {if (!method.isBridge()) {                        parseStatement(method);                    }                } catch (IncompleteElementException e) {                    configuration.addIncompleteMethod(newMethodResolver(this, method));                }            }        }        parsePendingMethods();    }privatevoidparseStatement(Method method) {        Class<?> parameterTypeClass = getParameterType(method);LanguageDriverlanguageDriver= getLanguageDriver(method);SqlSourcesqlSource= getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);if (sqlSource != null) {Optionsoptions= method.getAnnotation(Options.class);finalStringmappedStatementId= type.getName() + "." + method.getName();finalSqlCommandTypesqlCommandType= getSqlCommandType(method);finalbooleanisSelect= sqlCommandType == SqlCommandType.SELECT;finalbooleanflushCache= !isSelect;finalbooleanuseCache= isSelect;            KeyGenerator keyGenerator;StringkeyProperty=null;StringkeyColumn=null;if (options != null) {if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {                    keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;                    keyProperty = options.keyProperty();                    keyColumn = options.keyColumn();                } else {                    keyGenerator = NoKeyGenerator.INSTANCE;                }            } else {                keyGenerator = NoKeyGenerator.INSTANCE;            }IntegerfetchSize=null;Integertimeout=null;StatementTypestatementType= StatementType.PREPARED;ResultSetTyperesultSetType= configuration.getDefaultResultSetType();booleanisSelect= sqlCommandType == SqlCommandType.SELECT;booleanflushCache= !isSelect;booleanuseCache= isSelect;if (options != null) {if (options.useCache() != null) {                    useCache = options.useCache();                }if (options.flushCache() != null) {                    flushCache = options.flushCache();                }if (options.fetchSize() > -1) {                    fetchSize = options.fetchSize();                }if (options.timeout() > -1) {                    timeout = options.timeout();                }if (options.statementType() != StatementType.DEFAULT) {                    statementType = options.statementType();                }if (options.resultSetType() != ResultSetType.DEFAULT) {                    resultSetType = options.resultSetType();                }            }StringresultMapId=null;if (method.getAnnotation(Results.class) != null) {                resultMapId = parseResults(method);            } elseif (isSelect) {                resultMapId = parseResultMap(method);            }            assistant.addMappedStatement(mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout, null, parameterTypeClass, resultMapId, getReturnType(method), resultSetType, flushCache, useCache, false, keyGenerator, keyProperty, keyColumn, null, languageDriver, null);        }    }}

5.2 注解与 XML 的优先级处理

MyBatis 处理注解和 XML 配置的优先级规则:

  1. 1. XML 优先:如果同时存在 XML 和注解配置,XML 配置优先
  2. 2. 注解补充:注解配置作为 XML 配置的补充
  3. 3. 冲突处理:相同配置项冲突时,XML 配置覆盖注解配置

5.3 动态 SQL 注解解析

MyBatis 支持通过注解实现动态 SQL:

@Select("<script>" +        "SELECT * FROM users WHERE 1=1" +        "<if test='name != null'> AND name = #{name}</if>" +        "<if test='email != null'> AND email = #{email}</if>" +        "</script>")List<User> findUsers(@Param("name") String name, @Param("email") String email);

6. 配置系统扩展

6.1 自定义配置项处理

MyBatis 支持通过插件系统扩展配置:

@Intercepts({    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})publicclassCustomConfigInterceptorimplementsInterceptor {@Overridepublic Object intercept(Invocation invocation)throws Throwable {// 自定义配置处理逻辑return invocation.proceed();    }}

6.2 插件系统的配置集成

插件系统与配置系统的集成:

privatevoidpluginElement(XNode parent)throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {Stringinterceptor= child.getStringAttribute("interceptor");Propertiesproperties= child.getChildrenAsProperties();InterceptorinterceptorInstance= (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();            interceptorInstance.setProperties(properties);            configuration.addInterceptor(interceptorInstance);        }    }}

6.3 配置系统的性能优化

MyBatis 配置系统的性能优化策略:

  1. 1. 懒加载:延迟加载非必需的配置项
  2. 2. 缓存机制:缓存解析后的配置对象
  3. 3. 批量处理:批量解析相关配置项
  4. 4. 内存优化:优化配置对象的内存使用

7. 实践案例

7.1 跟踪配置解析的完整流程

让我们通过一个完整的例子来跟踪配置解析流程:

publicclassConfigurationParseExample {publicstaticvoidmain(String[] args)throws IOException {// 1. 创建 XMLConfigBuilderStringresource="mybatis-config.xml";InputStreaminputStream= Resources.getResourceAsStream(resource);XMLConfigBuilderparser=newXMLConfigBuilder(inputStream, "development"null);// 2. 解析配置文件Configurationconfiguration= parser.parse();// 3. 验证配置        configuration.validate();// 4. 使用配置SqlSessionFactorysqlSessionFactory=newSqlSessionFactoryBuilder().build(configuration);SqlSessionsession= sqlSessionFactory.openSession();// 5. 获取 MapperUserMappermapper= session.getMapper(UserMapper.class);// 6. 执行查询Useruser= mapper.selectById(1);        System.out.println("查询结果: " + user);        session.close();    }}

执行流程分析

  1. 1. XMLConfigBuilder 创建:创建配置解析器
  2. 2. 配置文件解析:解析 mybatis-config.xml
  3. 3. Configuration 构建:构建 Configuration 对象
  4. 4. 配置验证:验证配置的正确性
  5. 5. SqlSessionFactory 创建:基于配置创建工厂
  6. 6. SqlSession 创建:创建数据库会话
  7. 7. Mapper 获取:获取 Mapper 接口
  8. 8. SQL 执行:执行数据库操作

7.2 分析配置项的生命周期

配置项的生命周期管理:

  1. 1. 解析阶段:从 XML 或注解解析配置项
  2. 2. 存储阶段:将配置项存储到 Configuration 对象
  3. 3. 验证阶段:验证配置项的正确性
  4. 4. 使用阶段:在运行时使用配置项
  5. 5. 销毁阶段:在应用关闭时清理配置项

7.3 自定义配置解析器

实现自定义配置解析器:

publicclassCustomConfigParser {publicvoidparseCustomConfig(Configuration configuration, String configFile) {// 解析自定义配置文件Propertiesprops= loadConfigFile(configFile);// 处理自定义配置项StringcustomProperty= props.getProperty("custom.property");if (customProperty != null) {// 设置自定义配置            configuration.setVariables(props);        }    }private Properties loadConfigFile(String configFile) {Propertiesprops=newProperties();try (InputStreamis= Resources.getResourceAsStream(configFile)) {            props.load(is);        } catch (IOException e) {thrownewRuntimeException("Failed to load config file: " + configFile, e);        }return props;    }}

思考题

  1. 1. 为什么 MyBatis 要设计如此复杂的配置系统?
  2. 2. 配置系统的扩展性体现在哪些方面?
  3. 3. 如何优化配置解析的性能?
  4. 4. 基于配置系统的理解,你认为应该从哪个组件开始深入源码分析?

下篇预告:在下一篇文章中,我们将深入分析 SqlSession 会话管理机制,并详细解答以上思考题,帮助大家更好地理解 MyBatis 的配置系统在整个架构中的作用。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 《别只会写XML了!从源码看懂MyBatis配置系统如何驱动整个框架》

评论 抢沙发

5 + 1 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮