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

124 阅读11分钟

第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 的配置系统采用分层设计:

配置系统
├── 主配置文件 (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 XMLXMLMapperBuilder
注解解析器解析 Mapper 注解MapperAnnotationBuilder
配置验证器验证配置正确性内置验证逻辑

2. Configuration 类深度解析

2.1 Configuration 类的结构和职责

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

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

2.2 核心属性分析

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

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 配置项管理方法
// 添加 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 配置验证方法
// 验证配置完整性
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:

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 配置解析
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 配置解析
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 文件:

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 命名空间绑定
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 语句解析
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 源码分析
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 接口上的注解:

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:

@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})
})
public class CustomConfigInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 自定义配置处理逻辑
        return invocation.proceed();
    }
}

6.2 插件系统的配置集成

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

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 跟踪配置解析的完整流程

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

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 自定义配置解析器

实现自定义配置解析器:

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 的配置系统在整个架构中的作用。