Mybatis解析mybatis-config.xml过程

文章目录

  • 1. 相关代码
  • 2. 加载资源
  • 3. 创建SqlSessionFactory
    • 3.1 整体创建过程
    • 3.2. 创建XMLConfigBuilder.
      • 3.2.1 XMLConfigBuilder 的核心字段
      • 3.2.2 BaseBuilder
    • 3.3 创建Configuration
    • 3.4 全局配置文件解析过程
      • 3.4.1 parseConfiguration方法
      • 3.4.2 properties标签解析
      • 3.4.3 settings标签解析
      • 3.4.4 typeAliases解析
      • 3.4.5 plugins解析
      • 3.4.6 objectFactory,objectWrapperFactory及reflectorFactory解析
      • 3.4.7 settings 子标签赋值
      • 3.4.8 environments标签
      • 3.4.9 databaseIdProvider
      • 3.4.10 typeHandlers标签解析
      • 3.4.11 mapper解析
    • 3.5 映射文件解析
      • 3.5.1 configurationElement()——解析所有的子标签,最终获得MappedStatement对象。
      • 3.5.2 bindMapperForNamespace(); // 注册 Mapper 接口
  • 4. 总结

1. 相关代码

@Testpublic void test2() throws Exception{// 1.获取配置文件InputStream in = Resources.getResourceAsStream("mybatis-config.xml");// 2.加载解析配置文件并获取SqlSessionFactory对象SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);// 3.根据SqlSessionFactory对象获取SqlSession对象SqlSession sqlSession = factory.openSession();// 4.通过SqlSession中提供的 API方法来操作数据库UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.selectUserById(1);System.out.println(user);// 5.关闭会话sqlSession.close();}

2. 加载资源

Mybatis有自己的资源加载工具Resources,从classpath加载mybatis-config.xml配置文件。

3. 创建SqlSessionFactory

3.1 整体创建过程

SqlSessionFactoryBuilder从名称末尾的Builder可知,这是一个运用建造者模式的类,专门用来创建SqlSessionFactory。SqlSessionFactoryBuilder只是用来创建SqlSessionFactory,完成创建后就可以销毁。

进入build(in)方法查看:

 public SqlSessionFactory build(InputStream inputStream) {// test 6666 --> 999 0000return build(inputStream, null, null);}

这里重载了build方法:

/*** 1.创建了一个XMLConfigBuilder对象 会完成很多初始化操作 最终的是创建了 Configuration 对象* 2.parser.parse() 完成全局配置文件的加载解析 并将相关的信息封装到 Configuration 对象中*                  同时会完成映射文件(UserMapper.xml)的加载解析,相关的信息同样的会被保存到 Configuration对象中*                  select/insert/udpate/delete 标签的信息会被封装到 MapperdStatement对象中* 3.build(parser.parse()) ==》直接创建了 DefaultSqlSessionFactory对象*** 1.创建了一个解析器* 2.通过解析器解析全局配置文件* 3.通过得到的Configuration对象来创建DefaultSqlSessionFactory实例* @param inputStream* @param environment* @param properties* @return*/public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {// 用于解析 mybatis-config.xml,同时创建了 Configuration 对象 >> ok  完成了很多的初始化操作XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);// 解析XML,最终返回一个 DefaultSqlSessionFactory >>//  全局配置文件中的信息都被封装到了 Configuration对象中//  映射文件中的 配置信息 同样的也被封装到了 Configuration 对象中//  一个具体的 CRUD 标签的信息 被封装到 MappedStatment 对象中return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}

总结:XMLConfigBuilder对象完成全局配置文件和映射文件的解析,得到Configuration对象,Configuration作为入参创建DefaultSqlSessionFactory对象。

3.2. 创建XMLConfigBuilder.

从XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties)按F7进入。

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {// EntityResolver的实现类是XMLMapperEntityResolver 来完成配置文件的校验,根据对应的DTD文件来实现this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}

然后重载构造函数,这个函数是private,非常有意思的设计,其他构造函数最终都是调用这个函数。

 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {super(new Configuration()); // 完成了Configuration的初始化  类型别名的注册ErrorContext.instance().resource("SQL Mapper Configuration");this.configuration.setVariables(props); // 设置对应的Properties属性this.parsed = false; // 设置 是否解析的标志为 falsethis.environment = environment; // 初始化environmentthis.parser = parser; // 初始化 解析器}

3.2.1 XMLConfigBuilder 的核心字段

 // 标识是否解析过mybatis-config.xml文件private boolean parsed;// 用于解析mybatis-config.xml 配置文件的 XPathParser对象private final XPathParser parser;// 标签定义的环境名称private String environment;// 心功能是实现对 Reflector 对象的创建和缓存private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

3.2.2 BaseBuilder

super(new Configuration())

XMLConfigBuilder继承了抽象类BaseBuilder,这里super就是调用父类的构造函数。F7跟入:

 // Configuration 是MyBatis初始化过程的核心对象,MyBatis中的几乎全部配置信息会保存在 Configuration 对象中protected final Configuration configuration;// 别名的注册器protected final TypeAliasRegistry typeAliasRegistry;// 类型处理器的注册器protected final TypeHandlerRegistry typeHandlerRegistry;public BaseBuilder(Configuration configuration) {this.configuration = configuration;this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();}

BaseBuilder持有三个成员变量:

  • configuration,配置核心对象,MyBatis 的初始化过程就是围绕 Configuration 对象展开的,我们可以认为 Configuration 是一个单例对象,MyBatis 初始化解析到的全部配置信息都会记录到 Configuration 对象中。
  • typeAliasRegistry,别名注册器
  • typeHandlerRegistry,类型注册器。

3.3 创建Configuration

    super(new Configuration()); // 完成了Configuration的初始化  类型别名的注册

Configuration持有很多变量,其创建过程值得探究。

3.4 全局配置文件解析过程

parser.parse()
public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;// XPathParser,dom 和 SAX 都有用到 >>parseConfiguration(parser.evalNode("/configuration"));return configuration;}

3.4.1 parseConfiguration方法

/*** 全局配置文件中的配置信息都被加载到 Configuration 对象中* @param root*/private void parseConfiguration(XNode root) {try {//issue #117 read properties first// 对于全局配置文件各种标签的解析   都被保存到了 Configuration 对象中。propertiesElement(root.evalNode("properties"));// 解析 settings 标签  返回的结果就是 settings 中定义的信息  自定义的属性Properties settings = settingsAsProperties(root.evalNode("settings"));// 读取文件loadCustomVfs(settings);// 日志设置loadCustomLogImpl(settings);// 类型别名typeAliasesElement(root.evalNode("typeAliases"));// 插件pluginElement(root.evalNode("plugins"));// 用于创建对象objectFactoryElement(root.evalNode("objectFactory"));// 用于对对象进行加工objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));// 反射工具箱reflectorFactoryElement(root.evalNode("reflectorFactory"));// settings 子标签赋值,默认值就是在这里提供的 >>  加载默认设置settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631// 创建了数据源 >>environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));// 自定义的类型处理器的解析typeHandlerElement(root.evalNode("typeHandlers"));// 解析引用的Mapper映射器 ===》 映射文件的加载解析mapperElement(root.evalNode("mappers")); // 映射文件中的信息 加载解析出来后保存到了哪个对象中?} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}

安装顺序解析标签:

  • properties
  • settings
  • typeAliases
  • typeHandlers
  • objectFactory
  • plugins
  • environments
    • environment
      • transactionManager
      • dataSource
  • databaseIdProvider
  • mappers

3.4.2 properties标签解析

  /*** 加载解析 properties 标签  并且将相关的属性信息更新保存到了 Configuration 对象中了* @param context* @throws Exception*/private void propertiesElement(XNode context) throws Exception {if (context != null) {// 创建了一个 Properties 对象,后面可以用到Properties defaults = context.getChildrenAsProperties();String resource = context.getStringAttribute("resource");String url = context.getStringAttribute("url");if (resource != null && url != null) {// url 和 resource 不能同时存在throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");}// 加载resource或者url属性中指定的 properties 文件if (resource != null) {defaults.putAll(Resources.getResourceAsProperties(resource));} else if (url != null) {defaults.putAll(Resources.getUrlAsProperties(url));}// 配置文件中 已经有的 属性信息Properties vars = configuration.getVariables();if (vars != null) {// 和 Configuration中的 variables 属性合并defaults.putAll(vars);}// 更新对应的属性信息parser.setVariables(defaults);// 解析出来的 属性信息 被保存到了 configuration 对象中configuration.setVariables(defaults);}}

3.4.3 settings标签解析

/*** 将 sttings 标签中配置的 信息封装到了 Properties 对象中,并且返回了* @param context* @return*/private Properties settingsAsProperties(XNode context) {if (context == null) {return new Properties();}// 获取settings节点下的所有的子节点Properties props = context.getChildrenAsProperties();// Check that all settings are known to the configuration class  用户到了 反射工具箱 中的内容 实现反射处理// 创建Configuration对应的MetaClass对象MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);for (Object key : props.keySet()) {// 检验配置的拼写是否准确,是否有setter方法if (!metaConfig.hasSetter(String.valueOf(key))) {throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");}}return props;}
  • 读取文件
 // loadCustomVfs是获取Vitual File System的自定义实现类,比如要读取本地文件,// 或者FTP远程文件的时候,就可以用到自定义的VFS类。 (用到的机会很少)loadCustomVfs(settings);
  • 日志设置
loadCustomLogImpl(settings);
 private void loadCustomLogImpl(Properties props) {// 获取 logImpl设置的 日志 类型Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));// 设置日志  这块代码是我们后面分析 日志 模块的 关键代码configuration.setLogImpl(logImpl);}

3.4.4 typeAliases解析

typeAliasesElement(root.evalNode("typeAliases"));
private void typeAliasesElement(XNode parent) {// 放入 TypeAliasRegistryif (parent != null) {for (XNode child : parent.getChildren()) {// 解析包下面的类,默认别名的类名小写,如果类上有@Alias,就取注释的valueif ("package".equals(child.getName())) {String typeAliasPackage = child.getStringAttribute("name");configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);} else {String alias = child.getStringAttribute("alias");String type = child.getStringAttribute("type");try {Class<?> clazz = Resources.classForName(type);if (alias == null) {// 扫描 @Alias 注解使用typeAliasRegistry.registerAlias(clazz);} else {// 直接注册typeAliasRegistry.registerAlias(alias, clazz);}} catch (ClassNotFoundException e) {throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);}}}}}

3.4.5 plugins解析

pluginElement(root.evalNode("plugins"));
private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {// 获取<plugin> 节点的 interceptor 属性的值String interceptor = child.getStringAttribute("interceptor");// 获取<plugin> 下的所有的properties子节点Properties properties = child.getChildrenAsProperties();// 初始化 Interceptor 对象Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();// 设置 interceptor的 属性interceptorInstance.setProperties(properties);//  将Interceptor对象添加到Configuration的插件链中保存,等待后续使用configuration.addInterceptor(interceptorInstance);}}}

3.4.6 objectFactory,objectWrapperFactory及reflectorFactory解析

  • objectFactory
    加载自定义的ObjectFactory。ObjectFactory用来创建返回的对象。
private void objectFactoryElement(XNode context) throws Exception {if (context != null) {// 获取<objectFactory> 节点的 type 属性String type = context.getStringAttribute("type");// 获取 <objectFactory> 节点下的配置信息Properties properties = context.getChildrenAsProperties();// 获取ObjectFactory 对象的对象 通过反射方式ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();// ObjectFactory 和 对应的属性信息关联factory.setProperties(properties);// 将创建的ObjectFactory对象绑定到Configuration中configuration.setObjectFactory(factory);}}
  • objectWrapperFactory
    ObjectWrapperFactory用来对对象做特殊的处理。比如:select没有写别名,查询返回的是一个Map,可以在自定义的objectWrapperFactory中把下划线命名变成驼峰命名。
private void objectWrapperFactoryElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type");ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();configuration.setObjectWrapperFactory(factory);}}
  • reflectorFactory
    ReflectorFactory是反射的工具箱,对反射的操作进行了封装。
  private void reflectorFactoryElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type");ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();configuration.setReflectorFactory(factory);}}

3.4.7 settings 子标签赋值

// settings 中的属性信息都是直接保存在 Configuration 对象的属性中的private void settingsElement(Properties props) {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.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.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));configuration.setLogPrefix(props.getProperty("logPrefix"));configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));}

3.4.8 environments标签

private void environmentsElement(XNode context) throws Exception {if (context != null) {if (environment == null) {environment = context.getStringAttribute("default");}for (XNode child : context.getChildren()) {String id = child.getStringAttribute("id");if (isSpecifiedEnvironment(id)) {// 事务工厂TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));// 数据源工厂(例如 DruidDataSourceFactory )DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));// 数据源DataSource dataSource = dsFactory.getDataSource();// 包含了 事务工厂和数据源的 EnvironmentEnvironment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);// 放入 Configuration   Environment 存储了 DataSourceFactory TransactionFactoryconfiguration.setEnvironment(environmentBuilder.build());}}}}

3.4.9 databaseIdProvider

解析databaseIdProvider标签,生成DatabaseIdProvider对象(用来支持不同厂商的数据库)。

在 MyBatis 中编写的都是原生的 SQL 语句,而很多数据库产品都会有一些 SQL 方言,这些方言与标准 SQL 不兼容。

在 mybatis-config.xml 配置文件中,我们可以通过 标签定义需要支持的全部数据库的 DatabaseId,在后续编写 Mapper 映射配置文件的时候,就可以为同一个业务场景定义不同的 SQL 语句(带有不同的 DataSourceId),来支持不同的数据库,这里就是靠 DatabaseId 来确定哪个 SQL 语句支持哪个数据库的。

3.4.10 typeHandlers标签解析

 typeHandlerElement(root.evalNode("typeHandlers"));
private void typeHandlerElement(XNode parent) {if (parent != null) {for (XNode child : parent.getChildren()) { // 处理全部<typeHandler>子标签if ("package".equals(child.getName())) { // 如果指定了package属性,则扫描指定包中所有的类,// 并解析@MappedTypes注解,完成TypeHandler的注册String typeHandlerPackage = child.getStringAttribute("name");typeHandlerRegistry.register(typeHandlerPackage);} else {// 如果没有指定package属性,则尝试获取javaType、jdbcType、handler三个属性String javaTypeName = child.getStringAttribute("javaType");String jdbcTypeName = child.getStringAttribute("jdbcType");String handlerTypeName = child.getStringAttribute("handler");// 根据属性确定TypeHandler类型以及它能够处理的数据库类型和Java类型Class<?> javaTypeClass = resolveClass(javaTypeName);JdbcType jdbcType = resolveJdbcType(jdbcTypeName);Class<?> typeHandlerClass = resolveClass(handlerTypeName);// 调用TypeHandlerRegistry.register()方法注册TypeHandlerif (javaTypeClass != null) {if (jdbcType == null) {typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);} else {typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);}} else {typeHandlerRegistry.register(typeHandlerClass);}}}}}

3.4.11 mapper解析

private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) { // 循环遍历 <mappers> 标签的所有的子节点// 不同的定义方式的扫描,最终都是调用 addMapper()方法(添加到 MapperRegistry)。这个方法和 getMapper() 对应// package	包if ("package".equals(child.getName())) { // <package>String mapperPackage = child.getStringAttribute("name");// 扫描指定的包,并向MapperRegistry注册Mapper接口// 每一个类型 创建一个对应的 MapperProxyFactory 对象configuration.addMappers(mapperPackage);} else {String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) {// resource	相对路径ErrorContext.instance().resource(resource);// 读取映射文件InputStream inputStream = Resources.getResourceAsStream(resource);// XMLMapperBuilder 解析映射文件 XMLConfigurationBuilderXMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());// 解析 Mapper.xml,总体上做了两件事情 >>mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {// url	绝对路径ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {// class 	单个接口Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}}

3.5 映射文件解析

mapperParser.parse();
 public void parse() {// 总体上做了两件事情,对于语句的注册和接口的注册// 判断是否已经加载过了 映射文件if (!configuration.isResourceLoaded(resource)) {// 1、具体增删改查标签的解析。// 一个标签一个MappedStatement。 >>configurationElement(parser.evalNode("/mapper"));configuration.addLoadedResource(resource);// 2、把namespace(接口类型)和工厂类绑定起来,放到一个map。// 一个namespace 一个 MapperProxyFactory >>bindMapperForNamespace(); // 注册 Mapper 接口}// 处理 configurationElement 方法中解析失败的 <resultMap> 节点parsePendingResultMaps();// 处理 configurationElement 方法中解析失败的 <cache-ref> 节点parsePendingCacheRefs();// 处理 configurationElement 方法中解析失败的 SQL 语句节点parsePendingStatements();}

3.5.1 configurationElement()——解析所有的子标签,最终获得MappedStatement对象。

private void configurationElement(XNode context) {try {String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);// 添加缓存对象 如果我们希望多个 namespace 共用同一个二级缓存 就可以使用cacheRefElement(context.evalNode("cache-ref"));// 解析 cache 属性,添加缓存对象cacheElement(context.evalNode("cache"));// 创建 ParameterMapping 对象 以废弃  不推荐parameterMapElement(context.evalNodes("/mapper/parameterMap"));// 创建 List<ResultMapping>resultMapElements(context.evalNodes("/mapper/resultMap"));// 解析可以复用的SQL  includsqlElement(context.evalNodes("/mapper/sql"));// 解析增删改查标签,得到 MappedStatement >> 一个 CRUD标签中的信息都被封装到了  MappedStatement 对象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);}}

在buildStatementFromContext()方法中,创建了用来解析增删改查标签的XMLStatementBuilder,并且把创建的MappedStatement添加到mappedStatements中。

 private void buildStatementFromContext(List<XNode> list) {// 多数据库if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());}// 解析 Statement >>buildStatementFromContext(list, null);}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {// 用来解析增删改查标签的 XMLStatementBuilderfinal XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {// 解析 Statement,添加 MappedStatement 对象 >>// 解析具体的 insert/update/delete/selectstatementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}}}

关键步骤:

 MappedStatement statement = statementBuilder.build();// 最关键的一步,在 Configuration 添加了 MappedStatement >> 对应的就是一个 CRUD 标签configuration.addMappedStatement(statement);return statement;

3.5.2 bindMapperForNamespace(); // 注册 Mapper 接口

private void bindMapperForNamespace() {// 约定 namespace 要和对应的接口的全类路径名称保持一致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) {// 判断 在MapperRegistry中是否注册的有当前类型的 MapperProxyFactory对象if (!configuration.hasMapper(boundType)) {// Spring may not know the real resource name so we set a flag// to prevent loading again this resource from the mapper interface// look at MapperAnnotationBuilder#loadXmlResourceconfiguration.addLoadedResource("namespace:" + namespace);// 添加到 MapperRegistry,本质是一个 map,里面也有 Configuration >>configuration.addMapper(boundType);}}}}

通过源码分析发现主要是是调用了addMapper()。addMapper()方法中,把接口类型注册到MapperRegistry中:
实际上是为接口创建一个对应的MapperProxyFactory(用于为这个type提供工厂类,创建MapperProxy)。

 public <T> void addMapper(Class<T> type) {if (type.isInterface()) { // 检测 type 是否为接口if (hasMapper(type)) { // 检测是否已经加装过该接口throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {// !Map<Class<?>, MapperProxyFactory<?>> 存放的是接口类型,和对应的工厂类的关系knownMappers.put(type, new MapperProxyFactory<>(type));// It's important that the type is added before the parser is run// otherwise the binding may automatically be attempted by the// mapper parser. If the type is already known, it won't try.// 注册了接口之后,根据接口,开始解析所有方法上的注解,例如 @Select >>MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}}

再进入parse()方法:

 public void parse() {String resource = type.toString();if (!configuration.isResourceLoaded(resource)) {// 先判断 Mapper.xml 有没有解析,没有的话先解析 Mapper.xml(例如定义 package 方式)loadXmlResource();configuration.addLoadedResource(resource);assistant.setCurrentNamespace(type.getName());// 处理 @CacheNamespaceparseCache();// 处理 @CacheNamespaceRefparseCacheRef();// 获取所有方法Method[] methods = type.getMethods();for (Method method : methods) {try {// issue #237if (!method.isBridge()) {// 解析方法上的注解,添加到 MappedStatement 集合中 >>parseStatement(method);}} catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));}}}parsePendingMethods();}

4. 总结

总结:

  1. 我们主要完成了config配置文件、Mapper文件、Mapper接口中注解的解析。
  2. 我们得到了一个最重要的对象Configuration,这里面存放了全部的配置信息,它在属性里面还有各种各样的容器。
  3. 最后,返回了一个DefaultSqlSessionFactory,里面持有了Configuration的实例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/5014.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

flink on yarn 中的flink-conf.yaml参数

在 Flink on YARN 中,flink-conf.yaml 是 Flink 配置文件,用于配置 Flink 应用程序在 YARN 上的运行。通过修改 flink-conf.yaml 文件中的参数,你可以调整 Flink 集群的行为和性能。以下是一些常见的在 flink-conf.yaml 中设置的参数: yarn.application.name: 指定 Flink 应…

Go速成-常量

1.常量的定义 Go语言常量&#xff0c;定义的值是不能进修修改的&#xff0c;定义常量就是const&#xff0c;常量定义建议全部大写 const PI float32 3.1415946 //显式定义 const (x int 16ys "abc"z)fmt.Print(x,y,s,z) 在定义常量的时候如果没有声明值&#xff…

深入理解Spring中的立即加载和延迟加载

引言 在使用Spring框架进行开发时&#xff0c;掌握加载策略是至关重要的。Spring框架提供了两种主要的加载策略&#xff1a;立即加载&#xff08;Eager Loading&#xff09;和延迟加载&#xff08;Lazy Loading&#xff09;。这两种加载策略在不同的场景下有各自的优势和适用性…

Gradle和Aritifactory,实现上传Apk并发送消息到钉钉

Gradle和Aritifactory 本文链接&#xff1a;https://blog.csdn.net/feather_wch/article/details/131746580 文章目录 Gradle和AritifactoryGradle基本介绍Gradle插件开发流程本地仓库artifactory搭建添加仓库使用本地仓库gradle插件仓库引入 插件buildSrc开发步骤xxxPluginPg…

五、DQL-2.基本查询

一、数据准备 1、删除表employee&#xff1a; drop table employee; 2、创建表emp&#xff1a; 3、添加数据&#xff1a; 4、查看表数据&#xff1a; 【代码】 -- 查询数据--------------------------------------------------------- drop table emp;-- 数据准备-----------…

linux之Ubuntu系列 find 、 ln 、 tar、apt 指令 软链接和硬链接 snap

查找文件 find 命令 功能非常强大&#xff0c;通常用来在 特定的目录下 搜索 符合条件的文件 find [path] -name “.txt” 记得要加 “ ” 支持通配符 &#xff0c;正则表达式 包括子目录 ls 不包括 子目录 如果省略路径&#xff0c;表示 在当前路径下&#xff0c;搜索 软链接…

LabVIEW开发航空电子设备嵌入式诊断半物理仿真系统

LabVIEW开发航空电子设备嵌入式诊断半物理仿真系统 航电集成系统是现代战争飞机的重要组成部分&#xff0c;包括惯性导航系统、飞行控制系统、机电管理系统和任务计算机等子系统。战机的作战性能与航电系统息息相关&#xff0c;可以说&#xff0c;没有高性能的空电系统&#x…

【Python爬虫开发基础⑭】Scrapy架构(组件介绍、架构组成和工作原理)

&#x1f680;个人主页&#xff1a;为梦而生~ 关注我一起学习吧&#xff01; &#x1f4a1;专栏&#xff1a;python网络爬虫从基础到实战 欢迎订阅&#xff01;后面的内容会越来越有意思~ &#x1f4a1;往期推荐&#xff1a; ⭐️前面比较重要的基础内容&#xff1a; 【Python爬…

[pyqt5]QListView增删改查和添加右键菜单

将一批数据list加入到QListView slm QStringListModel() # 创建modelslm.setStringList([111, 222, 333]) # 将数据设置到modelself.listView.setModel(slm) 设置某行被选中&#xff0c;这里以第一行选中为例子 model_index slm.index(0, 0)self.listView.setCurrentIndex…

古代政府机构及官员品级

我国古代官员职位各历史阶段不同&#xff0c;分类繁多。 中央官制历史沿袭 战国时&#xff0c;各国国君之下设相与将&#xff0c;分掌文武权柄。我们熟知的例子有&#xff0c;赵慧文王以蔺相如为相&#xff0c;廉颇为将。但同时期楚国最高长官为令尹&#xff0c;武官是上柱国…

[QT编程系列-22]:基本框架 - QT常见数据结构:QString、QList、QVector、QMap、QHash、QSet、QPair快览

目录 1. QT常见数据结构概述 2. QList与QVector比较 3. QPair和QMap比较 4. QT数据结构与STL库数据结构比较 5. QT数据结构与STL库数据结构性能比较 1. QT常见数据结构概述 在Qt中&#xff0c;有几个主要的数据结构常用于处理和组织数据&#xff0c;包括&#xff1a; QS…

数据库用户管理

数据库用户管理 一、创建&#xff1a; 1.新建用户&#xff1a; CREATE USER 用户名来源地址 [IDENTIFIED BY [PASSWORD] 密码];‘用户名’&#xff1a;指定将创建的用户名. ‘来源地址’&#xff1a;指定新创建的用户可在哪些主机上登录&#xff0c;可使用IP地址、网段、主机…

Docker 应用容器引擎

Docker 应用容器引擎 一、Docker是什么二、Docker安装和查看1、docker安装2、docker版本信息查看3、docker信息查看 三、镜像操作四、容器操作1、容器创建2、创建并启动容器3、容器的进入4、复制5、容器的导入和导出6、删除容器 一、Docker是什么 是一个开源的应用容器引擎&…

ROS:nodelet

目录 一、前言二、概念三、作用四、使用演示4.1案例简介4.2nodelet 基本使用语法4.3内置案例调用 五、nodelet实现5.1需求5.2流程5.3准备5.4创建插件类并注册插件5.5构建插件库5.6使插件可用于ROS工具链5.6.1配置xml5.6.2导出插件 5.7执行 一、前言 ROS通信是基于Node(节点)的…

【动手学深度学习】--15.含并行连结的网络GoogLeNet

文章目录 含并行连结的网络GoogLeNet1.Inception块2.GoogLeNet模型3.训练模型 含并行连结的网络GoogLeNet 学习视频&#xff1a;含并行连结的网络 GoogLeNet / Inception V3【动手学深度学习v2】 官方笔记&#xff1a;含并行连结的网络&#xff08;GoogLeNet&#xff09; 1.…

【云原生】容器镜像

v ljx97609760 一起沟通学习 容器镜像 容器镜像&#xff08;Image&#xff09;所承载的是封装了应用程序及其所有软件依赖的二进制数据。 容器镜像是可执行的软件包&#xff0c;可以单独运行 你通常会创建应用的容器镜像并将其推送到某仓库&#xff08;Registry&#xff09;&…

Appium-Python-Client 源码剖析 (一) driver 的元素查找方法

目录 前言 源码版本:0.9 结构图&#xff1a; mobileby.py appium 的 webdriver.py selenium 的 webdriver.py seleniumdriver appiumdriver 前言 Appium-Python-Client是一个用于Python语言的Appium客户端库&#xff0c;它提供了丰富的API和功能&#xff0c;用于编写和…

【LeetCode】594. 最长和谐子序列

594. 最长和谐子序列&#xff08;简单&#xff09; 方法&#xff1a;哈希表计数 思路 题目规定的「和谐子序列」中的最值差值正好为 1&#xff0c;因而子序列排序后必然符合[a,a,.., a 1,a1]形式&#xff0c;即符合条件的和谐子序列长度为相邻两数(差值为 1)的出现次数之和。…

国产单片机(沁恒微WCH)CH32V307评估板初探

国产单片机(沁恒微WCH)CH32V307评估板初探 关于沁恒微&#xff1a;国产芯厂家、官网链接 公司简介 - 南京沁恒微电子股份有限公司 (wch.cn) 开发板资源&#xff1a; 评估板应用于 CH32V307 芯片的开发&#xff0c;IDE 使用 MounRiver 编译器&#xff0c;可选择使用板载或独…

为何异地销号这么难?这些注意事项要熟记!

最近有不少小伙伴私信小编&#xff0c;他们在网上办理的大流量手机号卡&#xff0c;用了一段时间之后想换其他的卡&#xff0c;所以想注销当前用的卡&#xff0c;但是注销的时候确实屡屡碰壁&#xff0c;程序还比较繁琐&#xff0c;有的甚至申请注销了几个月还注销不掉&#xf…