当前位置: 首页 > news >正文

【源码解读之 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 的配置系统采用分层设计:

配置系统
├── 主配置文件 (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 类的核心属性:

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 {@Overridepublic 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. 创建 XMLConfigBuilderString 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. 获取 MapperUserMapper 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 的配置系统在整个架构中的作用。

相关文章:

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

【源码解读之 Mybatis】【基础篇】-- 第2篇:配置系统深度解析第2篇:配置系统深度解析 1. 配置系统概述 1.0 第1篇思考题解答 在深入学习配置系统之前,让我们先回顾并解答第1篇中提出的思考题,这将帮助我们更好地理解配置系统在整个架构中的作用。 思考题1:为什么 MyBatis …...

eSIM笔记

1. 办理eSIM套餐 -> 获得eSIM套餐的激活码(包含SM-DP+地址,Profile)LPA:1$esim.wo.com.cn$A1B2C3D4 SM-DP+地址:sim.wo.com.cn 激活码:A1B2C3D4 确认码:空 qq:505645074...

拯救者Y7000过热降频解决方法

拯救者Y7000过热降频解决方法 一、型号描述 ​ 型号:拯救者Y7000 2020 ​ CPU:i5-10200H CPU @ 2.4GHz ​ 显卡:NVIDIA GeForce GTX 1650 二、问题描述现象:由于电脑已经使用四年,近期不定时会出现电脑卡顿等现象,但不影响使用就没咋管,经常卡一会,不使用一会就自动恢复…...

普通大语言模型(LLM)和向量化模型(Embedding Model)的区别以及其各自的作用

简单来说:普通大语言模型(LLM) 像是一个知识渊博、能说会道的专家。你问它问题,它生成答案、写文章、编代码。 向量化模型(Embedding Model) 像是一个高度专业化的图书管理员或档案管理员。它不生成内容,而是将文字转换成数字(向量),并理解它们之间的语义关系,从而帮…...

ios电脑如何改成windows系统

如何将iOS设备转换为Windows系统:一个不可能的任务? 一、引言 在日常生活中,我们经常听到“iOS”和“Windows”这两个术语,它们分别代表了两种不同的操作系统。iOS是由苹果公司开发的移动操作系统,主要用于iPhone和iPad等移动设备上,而Windows则是由微软公司开发的桌面操…...

PythonFlask 运用 DBUtils 创建通用连接池

Python&Flask 运用 DBUtils 创建通用连接池pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !…...

帧同步、快照同步与状态同步

https://zhuanlan.zhihu.com/p/564017214前段时间,在Epic举办的UnrealCircle会议上,我受邀分享了一场关于“UE4回放系统”的技术演讲。不过由于时长限制,很多细节都没有得到进一步的阐述。这篇文章会在演讲的基础上拓展更多内容,更好的帮助大家去理解虚幻引擎的回放系统,建…...

内存一致性模型

顺序一致性(Sequential Consistency)是计算机系统中保证多线程程序正确执行的一种内存一致性模型。 它要求所有操作的执行顺序与程序员的源码顺序一致,即每个线程的操作在其本地源码顺序中保持不变, 同时不同线程的操作全局排序也符合源码顺序。 ‌核心要求顺序一致性模型确…...

MahMetro 框架学习

学习建议: 1.从Demo开始:运行官方Demo,玩遍每一个功能,看看它是如何实现的。 2.动手实践:在自己的一个小项目中应用它,从改造MetroWindow和设置主题开始 3.逐个攻克:依次自学习一个控件(比如先学会用Flyout,再学HamburgerMenu),不要试图一下子掌握所有内容 4.善用搜索引…...

基于MATLAB的标准化降水蒸散指数(SPEI)实现

一、架构 %% 主程序框架 [prec, pet, time] = load_data(input.nc); % 加载降水与PET数据 prec_acc = accumulate(prec, 3); % 3个月时间尺度累积 pet_acc = accumulate(pet, 3); d = prec_acc - pet_acc; % 水分盈亏量 spei = calculate_spei(d, loglogistic); % 计算SPEI plo…...

Prometheus Probe 监控配置文档

概述 本文档描述了使用 Prometheus Operator 的 Probe 资源监控外部服务的配置方法。该配置通过静态目标地址直接监控多个服务端点,无需创建额外的 Service 和 Endpoints 资源。 前提条件Kubernetes 集群 Prometheus Operator v0.42 或更高版本 monitoring 命名空间已存在配置…...

客户案例|邦普循环x甄知科技,筑牢高效智能的IT运维底座

燕千云ITSM为邦普循环后续业务扩张与系统升级提供可扩展的IT服务框架,筑牢稳定、高效、智能的数字化底座,助力其在动力电池循环利用领域持续保持竞争优势。客户介绍 广东邦普循环科技有限公司(以下简称邦普循环),成立于2005年,总部位于广东省佛山市,地处粤港澳大湾区腹地,…...

VMware Exporter 指标转换方案

概述 本文档提供将 VMware Exporter 指标转换为标准 Node Exporter 格式的方案,实现监控基础设施的统一化和标准化。 背景 VMware Exporter 提供的原始指标格式与标准的 Node Exporter 不兼容,导致无法直接使用为 Node Exporter 设计的现有仪表板和告警规则。通过 Prometheus…...

可5V使用引脚兼容STM32F103C8T6的国产32位MCU

经常的型号如下,仅做记录 1.MM32F103C8T6 灵动微 2.CH32F103C8T6 沁恒微 3.FCM32F103C8T6 闪芯微 此料内核为M4, 4.CH32V103C8T6 沁恒微 此料内核为RISC-V 5.CW32F030C8T6 武汉芯源 此料内核为M0+ 6.待补充...

git clone操作报错diffie-hellman-group1-sha1的解决方案

在使用Git进行克隆操作的过程中,可能会遇到一个与加密算法相关的报错,尤其是当服务器使用了过时的安全算法 diffie-hellman-group1-sha1时。这通常发生在尝试克隆旧的或配置较老的Git服务器时,当代的SSH客户端默认不再支持这种较弱的加密方式,导致无法成功建立连接。 要解决…...

Celery inspect 常用命令手册

📘 Celery Inspect 常用命令清单 & 字段解释 运行格式: celery -A <app_name> inspect <command> 1. active 含义 显示 正在执行的任务(worker 正在跑的任务)。 示例 { "worker1@host": [ { "id": "f5e9b8c7-1234-5678-90ab-a…...

都可以!燕千云ITSM一站式接入全球主流AI大模型

燕千云ITSM基于对行业痛点的深度洞察,已实现对国内外多家主流大型语言模型的无缝接入与深度适配,构建覆盖多场景、高可用的企业级大模型矩阵,全面支持企业在AI技术选型与落地过程中的灵活性与可控性,实现真正的“全球优秀模型一站式接入”。在企业加速推进数智化转型升级的…...

删边最短路

今天写题的时候做到一个非常牛的东西。 给你一个图,\(q\) 次问你如果删掉一条边,\(1\) 到 \(n\) 的最短路会变成多少。 首先搞出来 \(1\) 出发的最短路树,然后如果这条边根本不在这棵树上,显然没有任何影响。 如果在的话,我们必然要绕路了。 给出一个性质:我们选择绕的路…...

问题解决模板

背景: 晚11点客户反馈,发送短信收不到了问题现象: 查看短信服务日志,发现日志提示: RocketMqMsgSender.java:41 - 发送短信消息到消息队列失败,CODE: 14 DESC: service not available now. It may be caused by one of the following reasons: the brokers disk is full …...

一站式接入全球股票数据:日本、美国、印度、马来西亚等多国API对接实战

一站式接入全球股票数据:日本、美国、印度、马来西亚等多国API对接实战 引言 在全球化资产配置的大背景下,开发者经常需要集成多国股票市场数据。本文将为您详解如何通过StockTV API快速接入日本、美国、印度、马来西亚等国家的实时股票行情、历史K线、指数数据等核心信息。 …...

基于MATLAB的图像处理程序

基于MATLAB的图像处理程序,结合傅里叶变换和滤波技术去除横条纹、渐变条纹及噪声干扰实现一、流程频域分析:通过傅里叶变换定位条纹频率成分 频域滤波:设计带阻/陷波滤波器抑制条纹 空域去噪:结合中值滤波/维纳滤波消除残留噪声 后处理优化:对比度增强与边缘锐化二、代码 …...

跨网文件安全交换系统推荐厂商详解

内容概要 在当今数字化时代,跨网文件交换已成为企业日常运营不可或缺的一部分,其安全性与效率直接关系到企业的数据安全与业务流畅度。因此,选择一家可靠的跨网文件安全交换系统推荐厂商尤为关键。本文将聚焦于飞驰云联及其出色的Ftrans Ferry跨网文件安全交换系统,该系统以…...

走迷宫

2025.9.15 题目内容 有一个 \(m\times n\) 格的迷宫(表示有 \(m\) 行、\(n\) 列),其中有可走的也有不可走的,如果用 \(1\) 表示可以走,\(0\) 表示不可以走,文件读入这 \(m\times n\) 个数据和起始点、结束点(起始点和结束点都是用两个数据来描述的,分别表示这个点的行号和…...

MVC 架构解析

认真对待每时、每刻每一件事,把握当下、立即去做。MVC 模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。下面主要对 MVC 架构下的优化方案以及其项目结构…...

鸿蒙应用开发从入门到实战(五):ArkUI概述

HarmonyOS提供了一套UI开发框架,即方舟开发框架(ArkUI框架)。方舟开发框架可为开发者提供应用UI开发所必需的能力,比如多种组件、布局计算、动画能力、UI交互、绘制等。​ 大家好,我是潘Sir,持续分享IT技术,帮你少走弯路。《鸿蒙应用开发从入门到项目实战》系列文章持…...

好用的跨网文件安全交换系统:守护企业数据流转的核心屏障!

在数字化时代,企业数据跨网流转需求日益频繁,然而内网与外网的隔离、多安全域的划分,让文件传输面临效率与安全的双重挑战。传统方式如U盘拷贝易导致病毒传播和数据泄露,FTP等工具缺乏合规审计能力,难以满足企业对数据安全的高要求。因此一款好用的跨网文件安全交换系统,…...

SIM笔记

SIM组成 金属触点 + 塑料基板 + 芯片模块(封装在透明的环养树脂)+ 极细小的内部电路金属触点 C1: 输送电力(5V/3V/1.8/); C5:接地 形成回路; C3: 提供时钟信号; C2:复位信号触点; C7:数据传输; C4、C8: 预留 芯片模块 如何上网qq:505645074...

2025第五届“长城杯”网络安全大赛暨京津冀蒙网络安全技能竞赛 WP Web全

文曲签学 首先随便输入一个指令尝试,发现提示输入help查看帮助输入help,发现有list命令查看笔记和read命令读取笔记list查看笔记列表,发现HINT,read查看关注公众号后提示写的很明确了,目录穿越加双写绕过拿到flag EZ_upload 随便上传一个文件,跳转至upload.php查看源码 &…...

FTP替代工具哪个产品好,高效安全之选

内容概要 在探讨FTP替代工具哪个产品好时,我们不得不先概览一下当前的市场情况。随着企业对数据传输效率和安全性要求的日益提升,FTP这一传统工具已难以满足需求。市场上涌现出众多FTP替代产品,其中Ftrans SFT文件安全传输系统‌备受瞩目。本文将从高效传输速度和数据安全性…...

c++之内存对齐模板类aligned_storage

始于c++11,c++23弃用 aligned_storage 是 C++ 标准库中用于管理对齐内存的模板类,定义在 <type_traits> 头文件中。它通过模板参数指定内存大小和对齐方式,提供未初始化的内存区域,适用于需要精确控制内存布局的场景。核心功能 ‌内存对齐管理‌:通过模板参数设置…...

ABC 423先慢慢改吧题解

被模拟题狙击了,数组越界为啥不爆 RE 啊啊啊啊 整场白打,这是真导管了 C - Lock All Doors 想了半天是不是被边界情况卡了,鼓捣半天写了一堆等价的东西,屋檐了 记得检查数组大小 D - Long Waiting 可以维护一个小根堆来判断已经进入餐厅的客人离开的顺序,再记一个人数 \(s…...

汇聚层交换机的替换要考虑到的因素

背景: 当汇聚层交换机的流量端口承受不住现有流量,需要替换交换机实现业务正常工作。 考虑因素:现有网络架构:先梳理清现有网络架构,为后续工作打下坚实的基础。需要考虑业务需求、结构层次(汇聚层、接入层、核心层)、未来需求等; 交换机选型:硬件方面需要考虑适配性,…...

git 常见使用

取消git commit git reset --soft HEAD~1 文件名 取消git add git reset HEAD 文件名 强制分支B覆盖分支A git checkout A git reset --hard origin/B git push -f origin A...

python UV 包管理工具安装

安裝 uv uv 本身並不需要 Python,所以不建議用 pip 或是 pipx 安裝,這樣都會跟特定的 Python 環境綁在一起,Windows 上就直接透過 PowerSehll 安裝即可: powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | scoop 安裝:scoop install u…...

什么是网络分区

目录背景和价值一、先搞懂:什么是Redis场景下的“网络分区”?二、同一交换机下,Redis主从发生网络分区的5个常见原因1. 节点自身的“网络硬件故障”2. 节点到交换机的“链路故障”3. 交换机自身的“功能故障”4. “网络风暴/拥堵”导致的“暂时性分区”5. 防火墙/安全软件的…...

完整教程:《驾驭云原生复杂性:隐性Bug的全链路防御体系构建》

完整教程:《驾驭云原生复杂性:隐性Bug的全链路防御体系构建》pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New&quo…...

从机器的角度来说ECS为何性能好

ECS的写法,让数据的命中变高了,从而提高了CPU的使用数据的性能,因为ECS的数据是连续,批量的 而OOP的写法,数据是没有组织的,数据比较离散,要去多个cache里面去找,命中率低...

人生最幸福的时刻也就几个瞬间

1...

网络流笔记

流网络:有向图,有两个特殊点:源点,汇点。每条边有个流量。(不考虑反向边)我们可以假设流网络中不存在自环,即对于任意的节点 \(v\),\((v,v) /∈E\)。 我们同样可以假设流网络中不存在重边,即对于任意的节点 \(u\), \(v\),如果 \((u,v)∈E\), 那么 \((v,u) /∈ E\)。…...

实用指南:经典动态规划题解

实用指南:经典动态规划题解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-si…...

2025杭电多校(2)

F https://acm.hdu.edu.cn/showproblem.php?pid=7996 题意 有两场比赛,统计对于每个 \(i\) ,有多少个人排在 \(i\) 的前面,需要去重。 思路 第一思路是统计每个位置 \(i\) 前面有多少人数,发现有个小容斥在这里,两场比赛排名前的总人数减去两场都在排名前的人数。 用树状…...

latex 打印生僻字

默认的字体格式很难打出生僻字. 我们可以使用ctex的其他字体. 首先要知道有哪些字体, 参考: https://www.cnblogs.com/wodedow/p/13845213.html. 比如我们要使用字体名称为AR PL KaitiM GB, 我们需要在usepackage区域加入下面的代码 \setCJKfamilyfont{font01}{AR PL KaitiM GB…...

CSP-S 2025 游记(The Last CSP ver.)

【洛谷专栏】。 前言 前作:CSP-S 2024 游记。 上一篇文章:2025 年南京大学计算机学科体验专题营 游记。 停课最早的一次,但是没有去年写的早,不过小问题。 与文化课告别的不突然,但仍有些不舍吧。也许未来不会再担任化学课代表了,化学老师真的对我很好(可怜。如果不是现…...

电机ADC采集

正点原直流有刷驱动板的硬件解说_直流有刷电机电流检测电路-CSDN博客电平移位电路设计(常用于将双极性的宽动态范围信号变成单极性窄动态范围的信号供ADC采集)-CSDN博客运放实现交流信号的放大与平移-CSDN博客...

道德经

1.道可道,非常道。名可名,非常名。无名天地之始;有名万物之母。2.天下皆知美之为美,斯恶已。皆知善之为善,斯不善已。3.有无相生,难易相成,长短相形,高下相盈,音声相和,前后相随。恒也。4.不尚贤,使民不争;不贵难得之货,使民不为盗;不见可欲,使民心不乱。是以圣…...

TokenFlow: Unified Image Tokenizer for Multimodal Understanding and Generation - jack

https://github.com/ByteVisionLab/TokenFlow https://arxiv.org/abs/2412.03069...

digitalworld.local: TORMENT - 实践

digitalworld.local: TORMENT - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important;…...

8.25-9.2周报六

1111...

Go by Example(3.Variables)

package mainimport "fmt"func main() {var a = "initial"fmt.Println(a)var b, c int = 1, 2fmt.Println(b, c)var d = truefmt.Println(d)var e intfmt.Println(e)f := "apple"fmt.Println(f) }运行结果: $ go run variables.go initial 1 2 …...

小程序分包方法

1、 图片上云 2、 删除不用的代码、函数和文件 3、 只有子包需要的接口移到子包中 4、 代码复用。效果不明显,实现两个页面复用一个大组件,可减少10kB大小 5、 还未实践见到效果的备选方案:把node_modules、uni_modules(在微信开发者工具的依赖分析看项目依赖这两个目录中的…...