【源码解读之 Mybatis】【基础篇】-- 第2篇:配置系统深度解析

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

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

1.2 配置文件的层次结构

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

scss 复制代码
配置系统
├── 主配置文件 (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. 配置存储:存储所有 MyBatis 配置项
  2. 配置管理:提供配置项的增删改查功能
  3. 配置验证:验证配置的正确性和完整性
  4. 配置扩展:支持插件和自定义配置
  5. 性能优化:提供配置缓存和懒加载

2.2 核心属性分析

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

java 复制代码
public class Configuration {
    // 环境配置
    protected Environment environment;
  
    // 数据库相关配置
    protected boolean safeRowBoundsEnabled;
    protected boolean safeResultHandlerEnabled;
    protected boolean mapUnderscoreToCamelCase;
    protected boolean aggressiveLazyLoading;
    protected boolean multipleResultSetsEnabled;
    protected boolean useGeneratedKeys;
    protected boolean useColumnLabel;
    protected boolean callSettersOnNulls;
    protected boolean useActualParamName;
    protected boolean returnInstanceForEmptyRow;
  
    // 日志配置
    protected String logPrefix;
    protected Class<? extends Log> logImpl;
  
    // 缓存配置
    protected boolean 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 配置项管理方法
java 复制代码
// 添加 MappedStatement
public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
}

// 获取 MappedStatement
public MappedStatement getMappedStatement(String id) {
    return mappedStatements.get(id);
}

// 添加 Mapper
public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
}

// 获取 Mapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}
2.3.2 配置验证方法
java 复制代码
// 验证配置完整性
public void validate() {
    // 验证必要的配置项
    if (environment == null) {
        throw new IllegalStateException("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:

java 复制代码
public class XMLConfigBuilder extends BaseBuilder {
    private boolean parsed;
    private final XPathParser parser;
    private String environment;
    private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
  
    public XMLConfigBuilder(Reader reader) {
        this(reader, null, null);
    }
  
    public XMLConfigBuilder(Reader reader, String environment) {
        this(reader, environment, null);
    }
  
    public XMLConfigBuilder(Reader reader, String environment, Properties props) {
        super(new Configuration());
        this.environment = environment;
        this.parser = new XPathParser(reader, true, props, new XMLMapperEntityResolver());
    }
  
    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
  
    private void parseConfiguration(XNode root) {
        try {
            // 解析 properties 配置
            propertiesElement(root.evalNode("properties"));
          
            // 解析 settings 配置
            Properties settings = 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) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
}

3.2 主配置文件解析过程

3.2.1 Properties 配置解析
java 复制代码
private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        Properties defaults = context.getChildrenAsProperties();
        String resource = context.getStringAttribute("resource");
        String url = context.getStringAttribute("url");
        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) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
        } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
        }
        Properties vars = configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }
        parser.setVariables(defaults);
        configuration.setVariables(defaults);
    }
}
3.2.2 Settings 配置解析
java 复制代码
private void settingsElement(Properties props) throws Exception {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.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. 必需配置验证:检查必需的配置项是否存在
  2. 配置值验证:验证配置值的有效性和范围
  3. 依赖关系验证:检查配置项之间的依赖关系
  4. 默认值设置:为未配置的项设置合理的默认值

4. Mapper 配置解析

4.1 XMLMapperBuilder 源码分析

XMLMapperBuilder 负责解析 Mapper XML 文件:

java 复制代码
public class XMLMapperBuilder extends BaseBuilder {
    private final XPathParser parser;
    private final MapperBuilderAssistant builderAssistant;
    private final Map<String, XNode> sqlFragments;
    private final String resource;
  
    public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments);
    }
  
    public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments);
    }
  
    private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
        super(configuration);
        this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
        this.parser = parser;
        this.sqlFragments = sqlFragments;
        this.resource = resource;
    }
  
    public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
            configurationElement(parser.evalNode("/mapper"));
            configuration.addLoadedResource(resource);
            bindMapperForNamespace();
        }
      
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
    }
  
    private void configurationElement(XNode context) {
        try {
            String namespace = context.getStringAttribute("namespace");
            if (namespace == null || namespace.isEmpty()) {
                throw new BuilderException("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) {
            throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
        }
    }
}

4.2 Mapper 接口和 XML 的绑定

4.2.1 命名空间绑定
java 复制代码
private void bindMapperForNamespace() {
    String namespace = 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 语句解析
java 复制代码
private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
        buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
            statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
        }
    }
}

4.3 SQL 语句的解析和存储

4.3.1 XMLStatementBuilder 源码分析
java 复制代码
public class XMLStatementBuilder extends BaseBuilder {
    private final MapperBuilderAssistant builderAssistant;
    private final XNode context;
    private final String requiredDatabaseId;
  
    public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
        super(configuration);
        this.builderAssistant = builderAssistant;
        this.context = context;
        this.requiredDatabaseId = databaseId;
    }
  
    public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
      
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            return;
        }
      
        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
      
        // 解析 SQL 语句
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
      
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        Integer fetchSize = context.getIntAttribute("fetchSize");
        Integer timeout = context.getIntAttribute("timeout");
        String parameterMap = context.getStringAttribute("parameterMap");
        String resultType = context.getStringAttribute("resultType");
        Class<?> resultTypeClass = resolveClass(resultType);
        String resultMap = context.getStringAttribute("resultMap");
        String resultSetType = context.getStringAttribute("resultSetType");
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
        String keyProperty = context.getStringAttribute("keyProperty");
        String keyColumn = context.getStringAttribute("keyColumn");
        String resultSets = 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 接口上的注解:

java 复制代码
public class MapperAnnotationBuilder {
    private final Configuration configuration;
    private final MapperBuilderAssistant builderAssistant;
    private final Class<?> type;
  
    public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
        String resource = type.getName().replace('.', '/') + ".java (best guess)";
        this.configuration = configuration;
        this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
        this.type = type;
    }
  
    public void parse() {
        String resource = 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(new MethodResolver(this, method));
                }
            }
        }
        parsePendingMethods();
    }
  
    private void parseStatement(Method method) {
        Class<?> parameterTypeClass = getParameterType(method);
        LanguageDriver languageDriver = getLanguageDriver(method);
        SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
      
        if (sqlSource != null) {
            Options options = method.getAnnotation(Options.class);
            final String mappedStatementId = type.getName() + "." + method.getName();
            final SqlCommandType sqlCommandType = getSqlCommandType(method);
            final boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            final boolean flushCache = !isSelect;
            final boolean useCache = isSelect;
          
            KeyGenerator keyGenerator;
            String keyProperty = null;
            String keyColumn = 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;
            }
          
            Integer fetchSize = null;
            Integer timeout = null;
            StatementType statementType = StatementType.PREPARED;
            ResultSetType resultSetType = configuration.getDefaultResultSetType();
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            boolean flushCache = !isSelect;
            boolean useCache = 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();
                }
            }
          
            String resultMapId = null;
            if (method.getAnnotation(Results.class) != null) {
                resultMapId = parseResults(method);
            } else if (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. XML 优先:如果同时存在 XML 和注解配置,XML 配置优先
  2. 注解补充:注解配置作为 XML 配置的补充
  3. 冲突处理:相同配置项冲突时,XML 配置覆盖注解配置

5.3 动态 SQL 注解解析

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

java 复制代码
@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 支持通过插件系统扩展配置:

java 复制代码
@Intercepts({
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class CustomConfigInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 自定义配置处理逻辑
        return invocation.proceed();
    }
}

6.2 插件系统的配置集成

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

java 复制代码
private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            String interceptor = child.getStringAttribute("interceptor");
            Properties properties = child.getChildrenAsProperties();
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
            interceptorInstance.setProperties(properties);
            configuration.addInterceptor(interceptorInstance);
        }
    }
}

6.3 配置系统的性能优化

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

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

7. 实践案例

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

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

java 复制代码
public class ConfigurationParseExample {
    public static void main(String[] args) throws IOException {
        // 1. 创建 XMLConfigBuilder
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, "development", null);
      
        // 2. 解析配置文件
        Configuration configuration = parser.parse();
      
        // 3. 验证配置
        configuration.validate();
      
        // 4. 使用配置
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        SqlSession session = sqlSessionFactory.openSession();
      
        // 5. 获取 Mapper
        UserMapper mapper = session.getMapper(UserMapper.class);
      
        // 6. 执行查询
        User user = mapper.selectById(1);
        System.out.println("查询结果: " + user);
      
        session.close();
    }
}

执行流程分析

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

7.2 分析配置项的生命周期

配置项的生命周期管理:

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

7.3 自定义配置解析器

实现自定义配置解析器:

java 复制代码
public class CustomConfigParser {
    public void parseCustomConfig(Configuration configuration, String configFile) {
        // 解析自定义配置文件
        Properties props = loadConfigFile(configFile);
      
        // 处理自定义配置项
        String customProperty = props.getProperty("custom.property");
        if (customProperty != null) {
            // 设置自定义配置
            configuration.setVariables(props);
        }
    }
  
    private Properties loadConfigFile(String configFile) {
        Properties props = new Properties();
        try (InputStream is = Resources.getResourceAsStream(configFile)) {
            props.load(is);
        } catch (IOException e) {
            throw new RuntimeException("Failed to load config file: " + configFile, e);
        }
        return props;
    }
}

思考题

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

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

相关推荐
森林-3 小时前
MyBatis 从入门到精通(第一篇)—— 框架基础与环境搭建
java·tomcat·mybatis
森林-4 小时前
MyBatis 从入门到精通(第三篇)—— 动态 SQL、关联查询与查询缓存
sql·缓存·mybatis
java干货6 小时前
MyBatis 的“魔法”:Mapper 接口是如何找到并执行 SQL 的?
数据库·sql·mybatis
嬉牛13 小时前
项目日志输出配置总结(多数据源MyBatis+Logback)
mybatis·logback
哈喽姥爷2 天前
Spring Boot--yml配置信息书写和获取
java·数据库·spring boot·mybatis
奔跑你个Run3 天前
mybatis plus 使用wrapper输出SQL
mybatis
躲在云朵里`4 天前
Spring Scheduler定时任务实战:从零掌握任务调度
java·数据库·mybatis
Java小白程序员5 天前
MyBatis基础到高级实践:全方位指南(中)
数据库·mybatis
山楂树下懒猴子5 天前
ChatAI项目-ChatGPT-SDK组件工程
人工智能·chatgpt·junit·https·log4j·intellij-idea·mybatis