精通MyBatis原理,看这两篇就够了!|原创

news2024/7/6 19:39:25

本文详细介绍了阅读MyBatis源码的学习思路,并且对源码做了详细注释,讲解了整个Mybatis的启动过程。

本文篇幅较长,建议收藏阅读,非常适合用于面试前的重点复习。

点击上方“后端开发技术”,选择“设为星标” ,优质资源及时送达

为什么学?如何学?

为什么要阅读MyBatis源码?因为作为当前最热门的经典ORM框架,在所有框架里Mybatis源码可以说是最简单易懂的,非常适合上手阅读,而且Mybatis也是面试中常问的知识点,必须要掌握。

如何去学习MyBatis源码呢?

所有框架和技术的学习都一样,按照"大胆假设,小心求证"的思路,先问自己几个问题。

在MyBatis出现之前软件开发中存在什么问题?怎么解决这个问题?如果是你怎么做?别人(MyBatis)怎么做?

需要下载源码,可以后台回复 “mybatis

JDBC 执行流程

因为所有的ORM框架其根本都需要依赖于JDBC与MySQL服务器进行交互,所以再次回顾一下JDBC的整个执行流程。

public static void main(String[] args) throws Exception {

    //1. 加载驱动程序
    Class.forName("com.mysql.jdbc.Driver");
    //2. 获得数据库连接
    Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
    //3. 操作数据库,实现增删改查
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT * FROM emp");
    while(rs.next()){
        System.out.println(rs.getString("name")+" -"+rs.getInt("age"));
    }
}

从上述JDBC的demo可以看出,JDBC执行主要有以下核心流程。

  1. 加载驱动程序

  2. 连接数据源,建立数据库连接connection。

  3. 准备SQL,建立statement

  4. 向数据库发送命令,执行SQL。

  5. 处理数据库响应并返回结果。

  6. 关闭资源。

3f29df0a5184d2496b4b0d6593d81125.png

存在了什么问题?

通常来说,一个框架就是解决了两个问题。一是降低代码冗余度,降低使用难度;二是解耦,让开发者更专注于业务开发。

目前在项目生产中,直接使用到JDBC的时候大家可以明显感觉到存在几个问题:

  1. JDBC的API使用繁琐,接口使用难度高,方法不便于复用,对业务侵入度高。

  2. 数据库的配置信息难以维护。

  3. 对于表结构和Java对象的关系难以维护。

  4. 编写原生SQL繁琐容易出错且不安全,难以复用。

我会如何设计?

类似的,如果我去实现Mybatis 如何去做呢?核心步骤一定脱离不开JDBC,这是一切ORM框架和数据库操作的基础,所以扩展应该基于此展开。

既然需要解决配置繁琐的问题,那就需要将配置信息封装到一起;JDBC使用复杂,那就将JDBC接口做更高一层的封装;Java对象和表结构的关系难以维护那就采用ORM思想增加对应配置,作为实体与表结构的映射;SQL难以复用和编写,那就将SQL独立出来配置,让开发人员专注于SQL的编写和优化。

总结来说如果需要解耦合,那就增加一个中间层,需要灵活复用,就增加映射配置。

所以我们可以推断MyBatis的流程如下:

  1. 配置实体关系映射文件,以及对应SQL配置映射文件,配置数据库配置文件。

  2. 读取配置,并且解析,将配置实例化为对象。

  3. 通过配置信息建立transaction,建立statement。

  4. 对于SQL的执行流程,因为对于SQL的解析逻辑和流程是一样的,可以用动态代理模式+模版模式生成执行类,代理执行。

  5. 替换SQL中的参数,生成实际SQL,执行SQL

  6. 拿到执行结果,通过映射文件解析结果,并且生成Java对象。

1-3是启动流程,4-5是每次的执行流程。

MyBatis 核心流程如何做?

Mybatis的核心流程氛围两个阶段,启动准备阶段和执行SQL阶段。

  1. 加载配置XMl文件。

  2. 读取mybatis的dtd描述文件,并且解析xml标签

  3. 通过读取的XMl配置信息生成对应的全局配置对象,以及生成mapper不同方法的SQL映射。

  4. 创建 SqlSessionFactory 完成,使用 SqlSessionFactory 创建 Session。

  5. 根据方法签名,获得mapper对应的代理对象。

  6. 通过JDK动态代理找到实现类,获得数据库连接,然后执行SQL。

  7. 拿到执行结果,并且根据映射关系处理为Java对象。

  8. 执行结束,关闭资源。

其中重点在于5个点:解析xml、生产代理对象、获得代理对象、执行SQL、处理结果集。

源码调试用例

首先准备数据库数据:

CREATE TABLE `user_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT '',
  `age` int(11) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into user_info values(1,'后端开发技术',20,'2022-06-14 12:00:00');

建立测试用例,看一下如何手动启动MyBatis,以下代码依据官网给出的用例进行了改造。具体代码不完整贴出来了,比较简单。

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 根据全局配置文件创建出SqlSessionFactory
// SqlSessionFactory:负责创建SqlSession对象的工厂
// SqlSession:表示跟数据库建议的一次会话
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// =================================================================
// 获取数据库的会话,创建出数据库连接的会话对象(事务工厂,事务对象,执行器,如果有插件的话会进行插件的解析)
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取要调用的接口类,创建出对应的mapper的动态代理对象
UserDao mapper = sqlSession.getMapper(UserDao.class);
// 调用方法开始执行
User userInfo = mapper.findUserInfo(1);

上半部分是启动和准备过程,下半部分是执行SQL过程。

启动准备阶段流程

这个阶段我们重点解读以下流程:

  1. 加载配置XMl文件

  2. 读取mybatis的dtd描述文件,并且解析xml标签

  3. 通过读取的XMl配置信息生成对应的全局配置对象,以及生成需要mapper的SQL映射。

  4. 创建 SqlSessionFactory 完成,使用 SqlSessionFactory 创建 Session。

在准备阶段有几个重点类,其关系如下图:

6d30e92d08bfdf6fc92c0abe69651177.png
  1. congfiguration:是Mybatis初始化过程的核心对象,mybatis中几乎全部的配置信息会保存到Configuration中,全局生效。

  2. XMLConfigBuilder:用于创建configuration,解析MyBatis配置文件中 configuration 节点的配置信息并填充到 Configuration 对象中

  3. XMLMapperEntityResolver :包含于 XMLConfigBuilder中,用于读取本地DTD文件。

  4. XPathParser :XPath解析器,对JDK的类做了封装,包含于 XMLConfigBuilder中。XPath即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。

  5. document :包含于XPathParser 中,方便后续结合 XPathParser 读取配置文件内容。

1.创建 SQlSessionFactory

建立Session就需要工厂类,建立SqlSessionFactory就需要SqlSessionFactoryBuilder,但是建立Factory就需要先读取并解析XML,因为可以看到build方法依赖于全局配置 configuration。

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    // 准备阶段:将配置文件加载到内存中并生成document对象,然后创建初始化Configuration对象
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  // 解析document对象并生成 SqlSessionFactory
    return build(parser.parse());
}

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

2.创建XMLConfigBuilder

创建 SqlSessionFactory 需要依赖 XMLConfigBuilder 来读取并且解析配置文件,将配置文件转化为Document对象,初始化 configuration 对象并且根据配置文件填充属性。

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
   this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
   // 调用父类初始化configuration
   super(new Configuration());
   ErrorContext.instance().resource("SQL Mapper Configuration");    
   // 将Properties全部设置到configuration里面去
   this.configuration.setVariables(props);
   this.parsed = false;
   this.environment = environment;
  // 绑定 XPathParser
   this.parser = parser;
}

初始化 Configuration 对象

初始化 Configuration 时,会将要用到的 class 和其别名注册到 typeAliases 这个map中。

private final Map<String, Class<?>> typeAliases = new HashMap<>();
public Configuration() {
  typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
  typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

  typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
  typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
  typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

  typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
  typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
  typeAliasRegistry.registerAlias("LRU", LruCache.class);
  typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
  typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

  typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

  typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
  typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

  typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
  typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
  typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
  typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
  typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
  typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
  typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

  typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
  typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

  languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
  languageRegistry.register(RawLanguageDriver.class);
}

3.创建 XPathParser

XPathParser 里面包含了很多重要的对象,包括用于读取本地DTD文件的 XMLMapperEntityResolver 对象、Xpath对象、存储xml文件的document对象以及properties标签定义的键值对集合对象variables。

1dd11e90feda2e08c316332915f5d99e.png

在这里最重要的流程是读取输入流,然后根据DTD描述文件,将xml的内容转换为 document 对象,这样就方便了之后对于xml中不同节点的配置读取。

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
  this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
  // 初始化属性
  commonConstructor(validation, variables, entityResolver);
  // 读取xml文件,保存为 document
  this.document = createDocument(new InputSource(inputStream));
}
public class XPathParser {
  private final Document document;
  private EntityResolver entityResolver;
  private Properties variables;
  private XPath xpath;
}

4.解析并设置 configuration 中的属性

在从磁盘中读取并转化为Java对象之后,就可以开始配置内容的解析工作了。

重点在于这行XMLConfigBuilder.parse 这行代码parseConfiguration(parser.evalNode("/configuration"));,由此可以看出MyBatis的配置解析都是基于 <configuration> 标签下。

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());

// XMLConfigBuilder
public Configuration parse() {
  // 根据parsed变量的值判断是否已经完成了对mybatis-config.xml配置文件的解析
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  // 在mybatis-config.xml配置文件中查找<configuration>节点,并开始解析
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

从这里可以看出,配置文件的解析需要 xpath的表达式和 document 对象。根据标签名字匹配到节点,取出相应的value。找到相应的节点后会返回 XNode 对象,初始化Configuration工作正式开始。

//XPathParser
public XNode evalNode(String expression) {
  return evalNode(document, expression);
}

解析 Configuration 中每个节点

这里是准备阶段最最重要的方法,一切配置都在这里解析并完成属性的设置。比如设置properties、创建PooledDataSourceFactory、解析插件、处理mapper映射等等,其中最重要的是处理mapper映射。

private void parseConfiguration(XNode root) {
  // issue #117 read properties first
  // 解析properties,并设置variables属性,同时设置在parser和configuration中
  propertiesElement(root.evalNode("properties"));
  // 解析settings
  Properties settings = settingsAsProperties(root.evalNode("settings"));
  // 设置vfsImpl字段
  loadCustomVfs(settings);
  loadCustomLogImpl(settings);
  // 解析类型别名
  typeAliasesElement(root.evalNode("typeAliases"));
  // 解析插件
  pluginElement(root.evalNode("plugins"));
  // 对象工厂
  objectFactoryElement(root.evalNode("objectFactory"));
  // 对象包装工厂
  objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  // 反射工厂
  reflectorFactoryElement(root.evalNode("reflectorFactory"));
  settingsElement(settings);//设置具体的属性到configuration对象
  // read it after objectFactory and objectWrapperFactory issue #631
  // 环境 设置transactionManager 、dataSource
  environmentsElement(root.evalNode("environments"));
  // databaseIdProvider
  databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  // 类型处理器
  typeHandlerElement(root.evalNode("typeHandlers"));
    // 映射器 最重要!
  mapperElement(root.evalNode("mappers"));
}

由于标签太多,这里只挑几个节点举例。

解析 envirmonent 节点

由于我们的XML文件中配置了environment 节点,所以会对其进行解析。配置文件如下

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
</environments>

对environment节点的解析代码如下:

private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    // 未指定XMLConfigBuilder.environment字段,则使用default属性
    if (environment == null) {
      environment = context.getStringAttribute("default");
    }
    // 遍历子节点
    for (XNode child : context.getChildren()) {
      String id = child.getStringAttribute("id");
      // 与XmlConfigBuilder.environment字段匹配
      if (isSpecifiedEnvironment(id)) {
        // 创建TransactionFactory
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        // 创建DataSourceFactory和DataSource
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        // 创建Environment
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        // 将Environment对象记录到Configuration.environment字段中
        configuration.setEnvironment(environmentBuilder.build());
        break;
      }
    }
  }
}

从上述代码可以看出,找到environments节点以后,会便利其子节点,然后获取每个environment标签下 transactionManager 的配置value,创建 TransactionFactory,通过 dataSource 节点的配置,会创建PooledDataSource,并且将配置的关于datasource的属性设置其中。

其他节点的解析都与其类似,不多做详细解释。

5.解析Mappers标签

除去其他标签的解析,最重要的部分一定是Mappers标签的解析,这里以配置为package路径为例子。

如果需要引入多个配置文件,可以分别指定多个mapper标签,也可以直接定义包的名称,resource目录下配置的映射文件必须要具体相同的目录。xml配置文件如下。

<mappers>
    <package name="com.daley.dao"/>
</mappers>

解析mappers节点的代码如下,注释已标注。

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    // 处理mapper子节点
    for (XNode child : parent.getChildren()) {
      // package子节点
      if ("package".equals(child.getName())) {
        // 自动扫描包下所有映射器
        String mapperPackage = child.getStringAttribute("name");
        // 扫描指定的包,并向mapperRegistry注册mapper接口
        configuration.addMappers(mapperPackage);
      } else {
        
        // 直接指定Mapper属性
        // 获取mapper节点的resource、url、class属性,三个属性互斥
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        // 如果mapper节点指定了resource或者url属性,则创建XmlMapperBuilder对象,并通过该对象解析resource或者url属性指定的mapper配置文件
        if (resource != null && url == null && mapperClass == null) {
          // 使用类路径
          ErrorContext.instance().resource(resource);
          try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
            // 创建XMLMapperBuilder对象,解析映射配置文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          }
        } else if (resource == null && url != null && mapperClass == null) {
          // 使用绝对url路径
          ErrorContext.instance().resource(url);
          try(InputStream inputStream = Resources.getUrlAsStream(url)){
            // 创建XMLMapperBuilder对象,解析映射配置文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          }
        } else if (resource == null && url == null && mapperClass != null) {
          // 如果mapper节点指定了class属性,则向MapperRegistry注册该mapper接口
          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.");
        }
      }
    }
  }
}

从代码可以看出,如果节点名等于package,会自动扫描包路径下的所有mapper,configuration中的mapperRegistry会add对应的package路径,其中ResolverUtil会通过VFS.list(虚拟文件系统)查找packageName包下所有资源,并且保存到 ResolverUtil 中的 matches 集合中,这样最终会将mapper对应的 MapperProxyFactory 添加到 knownMappers 中。

38afd6cd2722b40200fe54d964df6d47.png
//MapperRegistry
public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
public void addMappers(String packageName, Class<?> superType) {
  // 查找包下所有父类是 Objects.class 的类
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
  resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
  for (Class<?> mapperClass : mapperSet) {
    addMapper(mapperClass);
  }
}
// MapperRegistry.addMapper()
public <T> void addMapper(Class<T> type) {
 ……
    boolean loadCompleted = false;
    // 将Mapper接口对应的Class对象和MapperProxyFactory对象添加到knownMappers集合
    knownMappers.put(type, new MapperProxyFactory<>(type));
    
    // 对被注解接口的解析逻辑
    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
    parser.parse();
    loadCompleted = true;
}

在添加完mapper映射关系后,会开始对Mapper接口中相关注解的解析工作。代码如下,主要会读取mapper class对应的mapper xml文件,并且在读取后对文件中的内容进行解析。之后会遍历 mapper class 中的每一个方法,读取其中标注的注解进行解析,如selectKey、resultMap等。

public void parse() {
  String resource = type.toString();
  // 检测是否已经加载过该接口
  if (!configuration.isResourceLoaded(resource)) {
    // 检测是否加载过对应的映射配置文件,如果未加载,则创建XMLMapperBuilder对象解析对应的mapper映射xml文件
    loadXmlResource();
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    // 解析@CacheNamespace注解
    parseCache();
    // 解析@CacheNamespaceRef注解
    parseCacheRef();
    //开始解析每个方法
    for (Method method : type.getMethods()) {
      if (!canHaveStatement(method)) {
        continue;
      }
      if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
          && method.getAnnotation(ResultMap.class) == null) {
        parseResultMap(method);
      }
      try {
        // 解析@SelectKey,@ResultMap等注解,并创建MappedStatement对象
        parseStatement(method);
      } catch (IncompleteElementException e) {
        // 如果解析过程出现IncompleteElementException异常,可能是引用了未解析的注解,此处将出现异常的方法添加到incompleteMethod集合中保存
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  // 遍历incompleteMethods集合中记录的未解析的方法,并重新进行解析
  parsePendingMethods();
}

其中最重要的是 loadXmlResource 方法,里面会调用parse方法将对应xml中的 mapper 中内容取出来解析。resource 指的就是xml文件的全路径名。

public void parse() {
  // 判断是否已经加载过该映射文件
  if (!configuration.isResourceLoaded(resource)) {
    // 处理mapper节点
    configurationElement(parser.evalNode("/mapper"));
    // 将resource添加到Configuration.loadedResources集合中保存,他是hashset类型的集合,其中记录了已经加载过的映射文件
    configuration.addLoadedResource(resource);
    // 绑定映射器到namespace
    bindMapperForNamespace();
  }

  // 处理ConfigurationElement方法中解析失败的resultMap节点
  parsePendingResultMaps();
  // 处理ConfigurationElement方法中解析失败的cache-ref节点
  parsePendingCacheRefs();
  // 处理ConfigurationElement方法中解析失败的SQL语句节点
  parsePendingStatements();
}

configurationElement 方法中对sql、resultMap、parameterMap 等节点进行解析,具体内容请看注释。

private void configurationElement(XNode context) {
  try {
    // 获取mapper节点的namespace属性
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    // 设置MapperBuilderAssistant的currentNamespace字段,记录当前命名空间
    builderAssistant.setCurrentNamespace(namespace);
    // 解析cache-ref节点
    cacheRefElement(context.evalNode("cache-ref"));
    // 解析cache节点
    cacheElement(context.evalNode("cache"));
    // 解析parameterMap节点
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    // 解析resultMap节点
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    // 解析sql节点
    sqlElement(context.evalNodes("/mapper/sql"));
    // 解析select、update、insert、delete等SQL节点
    // 就在这里开始创建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);
  }
}

对于SQL的绑定过程,详细内容在 XMLStatementBuilder.parseStatementNode() 中,内容太多大家自行查阅。总之这里关于sql的所有配置,解析完毕后会保存在 MapperBuilderAssistant 中。

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    // 构建所有语句,一个mapper下可以有很多select
    // 语句比较复杂,核心都在这里面,所以调用XMLStatementBuilder
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      // 核心XMLStatementBuilder.parseStatementNode
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      // 如果出现SQL语句不完整,把它记下来,塞到configuration去
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

……
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }

id 就是方法签名,在本例中等于 com.daley.dao.UserDao.findUserInfo,mappedStatements 属于Configuration 中的属性,保存了方法签名和映射的SQL语句。

47724ec23073ef357294db44f0a802ee.png

在这一切都准备就绪之后,Configuration 创建完毕,会返回创建好的SqlSessionFactory。

后面就简单了,无非是通过前面一系列读取的配置,创建后续需要的Transaction、Executor和DefaultSqlSession。

SqlSession sqlSession = sqlSessionFactory.openSession();
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    // 获取mybatis-config.xml配置文件中配置的Environment对象,
    final Environment environment = configuration.getEnvironment();
    // 获取TransactionFactory对象
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    // 创建Transaction对象
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    // 根据配置创建Executor对象
    final Executor executor = configuration.newExecutor(tx, execType);
    return new DefaultSqlSession(configuration, executor, autoCommit);
}

到这里 MyBatis的启动准备阶段就算是完成了,一切准备工作已经就绪,摩拳擦掌就等着开始执行SQL!

下篇文章将讲解 MyBatis 的执行阶段,请继续关注。有什么理解不对的地方,各位大佬求轻喷。

需要下载源码,可以后台回复 “mybatis

如果觉得对你有帮助,欢迎点赞、标🌟分享

大厂程序员常用的几款「高效工具」,已整理资源!

2022-12-20

b8268e8de9c5d221096120788af8ca93.jpeg

Dubbo SPI机制核心原理,你掌握了吗?|原创

2022-12-08

07caebd112d2a0f9dcb57eb65f33ff67.jpeg

被问到可重入锁条件队列,看这一篇就够了!|原创

2022-11-26

82823ba37b4313cdb1fa54554d8f0c8d.jpeg

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

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

相关文章

BSV 上的零知识隐私机器学习

此前&#xff0c;我们已经演示了在 BSV 上运行一个成熟的深度神经网络&#xff0c;其中机器学习 (ML) 算法的输入和模型都是公开的。在实践中&#xff0c;通常希望将输入或模型保持在链下并因此保持私有&#xff0c;同时仍确保 ML 算法如实运行。我们通过将零知识证明 (ZKP) 应…

你确定没有滥用 goroutine 吗

写在前面 学习 golang &#xff0c;路还很长呢&#xff0c;犹记得刚开始学习 golang 的时候&#xff0c;写起来确实非常简单&#xff0c;有很多包和工具使用&#xff0c;不需要重复造轮子&#xff0c;但是要真的学好一门语言作为工具&#xff0c;对于其原理是非常有必要学懂的…

极限多标签学习综述(Extreme Multi-label Learning)

A Survey on Extreme Multi-label Learning 先给地址&#xff1a; https://arxiv.org/abs/2210.03968 博主曾整理过Multi-Label Image Classification&#xff08;多标签图像分类&#xff09;&#xff0c;但这类任务中所用的数据集往往较小&#xff0c;分类数量并不多。但在更…

JavaScript的原型链

JavaScript的原型链 JavaScript的继承主要是通过原型链实现的&#xff0c;所以理解原型链是掌握JavaScript继承的关键一环。原型链的继承的基本思想是通过原型链继承多个引用类型的属性和方法。 理解原型链 关于原型链的定义与理解&#xff1a; 每个构造函数都有一个原型对…

Python实现将位图描摹为彩色矢量 svg 图片的源代码,Python实现位图转彩色矢量代码

Color Trace 这是一个将位图描摹为彩色矢量 svg 图片的程序&#xff0c;是一个命令行工具&#xff0c;使用 Python 脚本实现&#xff0c;运行环境 Python3.8。 ✨ 效果 以一个字帖图片为例&#xff0c;这是 png 格式的位图&#xff08;370KB&#xff09;&#xff1a; 这是颜…

多智能体强化学习环境【星际争霸II】SMAC环境配置

多智能体强化学习这个领域中&#xff0c;很多Paper都使用的一个环境是——星际争多智能体挑战(StarCraft Multi-Agent Challenge, SMAC)。最近也配置了这个环境&#xff0c;把中间一些步骤记录下来。2022.12.26 文章目录1 环境介绍1.1 相关论文1.2 项目代码地址2 安装过程3 相关…

2023年pmp的考试时间是什么时候?(含pmp资料)

不出意外&#xff0c;按照原计划&#xff0c;就是3、6、9、12月&#xff0c;22年11月延期考试地区的考生或者退考的估计会在3月或者6月考。具体就及时关注官网消息。 ​新版中文报名网站&#xff1a;中国国际人才交流基金会 这里说一下PMP的基本考试情况&#xff1a; 【考试注…

模型实战(2)之YOLOv5 实时实例分割+训练自己数据集

模型实战&#xff08;2&#xff09;之YOLOv5 实时实例分割训练自己数据集 本文将详解YOLOv5实例分割模型的使用及从头训练自己的数据集得到最优权重&#xff0c;可以直接替换数据集进行训练的训练模型可通过我的gitcode进行下载&#xff1a;https://gitcode.net/openmodel/yolo…

使用matplotlib画图 + python色彩大全

目录画线画点散点画点的形状、线的形状画点线在特定位置写文字plt.legend()中图例的位置方法一 plt.legend(loc4)方法二 plt.legend(bbox_to_anchor(num1, num2))方法三 bbox_to_anchor(1.05, 1), loc2, borderaxespad0保存图片指定图片大小网格线根据自己的需求做了一个画图的…

图的最短路径

文章目录单源最短路径-Dijkstra算法单源最短路径--Bellman-Ford算法多源最短路径--Floyd-Warshall算法单源最短路径-Dijkstra算法 针对一个带权有向图G&#xff0c;将所有结点分为两组S和Q&#xff0c;S是已经确定最短路径的结点集合&#xff0c;在初始时为空&#xff08;初始…

如何使用监控诊断工具Arthas(阿尔萨斯)

Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法执行耗时&#xff0c;类加载信…

【python】实现精美圣诞树-拿下女神不是梦

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 &#x1f3c5;阿里云ACE认证高级工程师 &#x1f3c5;阿里云开发者社区专…

Java Web基础面试题

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java面试题…

【K3s】第4篇 一篇文章带你了解使用Kompose

目录 1、Kompose介绍 2、安装Kompose 3、docker-compose文件转换为k8s文件 1、Kompose介绍 kompose是一个帮助熟悉 Kubernetes 的用户迁移到k8s的工具。 获取 Docker Compose 文件并将其转换为 Kubernetes 资源。 kompose是一个方便的工具&#xff0c;可以从本地 Docker …

Ffuf爆破神器(超详细)

目录为什么是Ffuf基本使用最基本的使用多个字典同时使用带cookie扫描&#xff08;-b&#xff09;静默模式&#xff08;-s&#xff09;递归扫描&#xff08;-recursion&#xff09;指定扩展名&#xff08;-e&#xff09;POST请求爆破方式1&#xff1a;指明请求地址和请求体【不推…

iOS 自动化测试踩坑(一): 技术方案、环境配置与落地实践

移动端的自动化测试&#xff0c;最常见的是 Android 自动化测试&#xff0c;我个人觉得 Android 的测试优先级会更高&#xff0c;也更开放&#xff0c;更容易测试&#xff1b;而 iOS 相较于 Android 要安全稳定的多&#xff0c;但也是一个必须测试的方向&#xff0c;这个系列文…

Android实现雪花特效自定义view

一、前言 这个冬天&#xff0c;老家一直没有下雨&#xff0c; 正好圣诞节&#xff0c;就想着制作一个下雪的特效。 圣诞祝福&#xff1a;平安夜&#xff0c;舞翩阡。雪花飘&#xff0c;飞满天。心与心&#xff0c;永相伴。 圣诞节是传统的宗教节日&#xff0c;对于基 督徒&…

前端自学你还在浪费时间吗?

其实最主要不是学的过程&#xff0c;而是学完后&#xff0c;你有没有把今天的练习题自己在重新敲个2&#xff0c;3遍&#xff0c;这样印象就会更加深刻&#xff0c;以后自己写代码的时候也会更加的得心应手。 手抄笔记让我打好了HTML基础和良好的CSS能力&#xff0c;当然这不一…

Cesium打包入门(gulp与esbuild)

本文针对Cesium源码包的打包工具gulp和esbuild进行了初步探讨&#xff0c;属于入门篇。 首先简要介绍采用gulpesbuild如何为多个源代码文件打包成一个单独文件&#xff0c;然后介绍了下Cesium中的源码包的结构&#xff0c;并简要分析了其打包的相关函数。 本文编译环境IDE使用…

【并发编程学习】一、线程的基本认识

一、线程的基本认识 1.1线程的基本介绍 线程是什么&#xff1f; 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程中的实际运行单位。 为什么会有多线程&#xff1f; ①因为其是CPU的最小调度单位&#xff0c;所以在多核CPU中&#xff0c…