【Mybatis】Mybatis初识-通过源码学习执行流程

文章目录

  • 1.Mybatis核心组件
    • 1.1 SqlSession
    • 1.2 SqlSessionFactory
    • 1.3 Mapper
    • 1.4 MappedStatement
    • 1.5 Executor
  • 2. Mybatis各组件之间关系
  • 3. 构建SqlSessionFactory
    • 3.1 从XML文件中构建
    • 3.2 不使用XML构建SqlSessionFactory
  • 4. 如何从SqlSessionFactory获取SqlSession
  • 5.获取Mapper
    • 缓一下!缓一下! 买杯咖啡好嘛!
    • 5.1 解析environments
    • 5.2 解析mapper
      • 5.2.1 解析配置parameterMap
      • 5.2.2 ResultMap
      • 5.2.3 SQL
      • 5.2.4 select|insert|update|delete
  • 6. 总结

Mybatis作为一个优秀的持久层框架,免除了几乎所有的JDBC代码已经设置参数和结果获取的工作。那Mybatis是如何做到的呢?本文主要介绍Mybatis中的一些重要概念。

1.Mybatis核心组件

1.1 SqlSession

类似于JDBC中的Connection,表示和数据库交互的会话。SqlSession提供了一系列的操作数据库的API,包括查询、插入和删除数据等操作。

1.2 SqlSessionFactory

SqlSessionFactory是mybatis的核心组件之一,可以依据配置文件以及JAVA API的方式生成SqlSession对象。
SqlSessionFactory是SqlSession的工厂类,采用工厂模式设计,封装对象创建的过程。

1.3 Mapper

Mapper是Mybatis中的一个抽象概念,表示一类DAO类的接口。每个Mapper接口中定义了对应的SQL操作方法。每个 Mapper 接口中定义了对应 SQL 操作的方法。Mapper 接口中的方法会被 MyBatis 解析成 MappedStatement 对象,与该 SQL 语句对应。

1.4 MappedStatement

MappedStatement 是 MyBatis 用于存储 SQL 语句、入参、出参等相关信息的核心组件。在 MyBatis 中,Mapper 接口中的每个方法都会被解析成一个 MappedStatement 对象。MappedStatement 对象是一个有状态(stateful)对象,包含了 SQL 语句的语法、入参映射、结果映射等相关信息。

1.5 Executor

Executor 是 MyBatis 中的核心组件之一,它主要负责查询语句的执行和结果的返回。Executor 的实现类有三种:SimpleExecutor、ReuseExecutor、BatchExecutor,分别对应于简单执行器、重复执行器和批处理执行器。Executor 提供了追踪和缓存查询结果的功能,能够提高执行效率。

2. Mybatis各组件之间关系

请添加图片描述

3. 构建SqlSessionFactory

从mybatis官网的入门示例中,可以清楚的看到两种不同的SqlSessionFactory创建方式:

  • 从XML中构建SqlSessionFactory
  • 不使用XML构建

3.1 从XML文件中构建

既然是从XML文件中构建,那就一定会有xml文件,官网有给出的示例文件,这里我们参照示例,适当修改如下(放在src/test/resource下):

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/bookstore?serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="UserMapper.xml"/>
  </mappers>
</configuration>

还是使用之前一篇文章中的数据,创建一个测试用例,来测试sqlSession创建成功,并能查询到数据库的数据。这里还需要这样一个UserMapper.xml文件(也放在src/test/resource下)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0// EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.ssm.mapper.UserMapper">
        <select id="findUserByName">
            select * from user
        </select>
</mapper>

这样就可以编写测试用例了:

@org.junit.jupiter.api.Test
public void testSqlSession() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    User tom = sqlSession.getMapper(UserMapper.class).findByName("tom");
    sqlSession.close();
    assertEquals(23, tom.getAge());
}

测试通过!通过XML配置文件构建SqlSessionFactory成功。

3.2 不使用XML构建SqlSessionFactory

当然了,配置的内容不仅可以从xml中读取,也可以从JAVA代码中获取。

@org.junit.jupiter.api.Test
    public void testSqlSessionWithoutXml() throws SQLException {
        DataSource dataSource = new SimpleDriverDataSource(new Driver(), "jdbc:mysql://localhost:3306/bookstore?serverTimezone=UTC", "root", "Yuanyao@123");
        JdbcTransactionFactory factory = new JdbcTransactionFactory();
        Environment environment = new Environment("test", factory, dataSource);
        Configuration configuration = new Configuration(environment);
        configuration.addMapper(UserMapper.class);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User tom = sqlSession.getMapper(UserMapper.class).findByName("tom");
        sqlSession.close();
        assertEquals(23, tom.getAge());
    }

测试用例也还是一次通过!!

4. 如何从SqlSessionFactory获取SqlSession

在创建完SqlSessionFactory后,通过opensession方法就获取到了sqlSession。那么这个方法里到底包含了哪些内容?

@Override
public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

调用了openSessionFromDataSource方法,还传了3个参数:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
      boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

三个参数的含义分别是Executor的类别,事务的隔离级别已经是否自动提交事务。很明显,这里的事务隔离级别是null,不自动提交事务。而ExecutorType则是从Configuration中获取DefaultExecutorType值SIMPLE。

在这个方法中,既然已经有了事务隔离级别的存在,那么事务必然少不了。这里是通过TransactionFactory来获取,而TransactionFactory又是根据environment配置来的:

private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
      return new ManagedTransactionFactory();
    }
    return environment.getTransactionFactory();
  }

当然了,如果没有配置,那就new一个默认的喽。
现在,有了transaction和execType,那就可以从Configuration中构建出一个Executor了,有了Configuration和Executor,并且也知道了如何处理transaction,那此时就可以构建一个DefaultSqlSession对象了。

5.获取Mapper

然后从SqlSession中获取mapper,这里SqlSession的接口,定义了方法

<T> T getMapper(Class<T> type);

其实现类包括DefaultSqlSession,这里使用的正是这个:

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

而Configuration中的getMapper方法是:

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

可以看到这里是由configuration的getMapper方法根据mapper的类来获取的。但是这个configuration是什么时候被初始化的呢?
从代码中看,是通过构造函数初始化的:

public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

而构造函数又是什么时候被调用的?
在构建SqlSessionFactory的时候,调用了构造器构建的时候!

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

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        if (inputStream != null) {
          inputStream.close();
        }
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

而在通过构造器构建XMLConfigBuilder的时候

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(Configuration.class, inputStream, environment, props);
}

public XMLConfigBuilder(Class<? extends Configuration> configClass, InputStream inputStream, String environment,
      Properties props) {
    this(configClass, new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

private XMLConfigBuilder(Class<? extends Configuration> configClass, XPathParser parser, String environment,
      Properties props) {
    super(newConfig(configClass));
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}

在调用XMLConfigBuilder的时候,传入了一个XPathParser类型的参数。这个参数也是通过构造器传入的:

new XPathParser(inputStream, true, props, new XMLMapperEntityResolver())

public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(reader));
}

private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
 }

这里的构造器设置了一些基本信息:

  • validation为true,需要校验数据
  • entityResolver就是一个XMLMapperEntityResolver,用来解析这个配置的xml文件
  • variables为null,没有设置
  • 通过工厂模式,获取一个Xpath实例

然后调用createDocument解析配置的xml配置文件:

private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
          // NOP
        }
      });
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

这里主要是读取并解析mybatis-config.xml文件为一个Document(解析xml文件过程暂时不作深入探讨,如有必要,另开一篇详细探讨)。
XMLConfigBuilder的父类构造器,super(new Config(configuration)),这里new了一个Config对象,传入的参数是一个配置类,这个类的就是org.apache.ibatis.session.Configuration。而这个类里的MapperRegistry参数是这样初始化的

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

而newConfig(configClass)方法是个静态方法:

private static Configuration newConfig(Class<? extends Configuration> configClass) {
    try {
      return configClass.getDeclaredConstructor().newInstance();
    } catch (Exception ex) {
      throw new BuilderException("Failed to create a new Configuration instance.", ex);
    }
  }

只是将Configuration类实例化了。
在super(newconfig(configuration))方法中只是初始化:

public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}

到这里,总算把XMLConfigBuilder构建出来啦!

缓一下!缓一下!
买杯咖啡好嘛!

如果我填坑了,给我也买一杯好嘛!
喝完咖啡继续!

饶了很远,但是目标不能忘,构建SqlSessionFactory!接着看build(parser.parse())方法:
首先看XMLConfigBuilder的pares方法:

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
 }

其中

parser.evalNode(“/configuration”)

就是读取Document中configuration节点的内容,然后这个XNode被parseConfiguration用来解析其中的配置:

private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfsImpl(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginsElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlersElement(root.evalNode("typeHandlers"));
      mappersElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

从这里可以看到,可以配置的内容很多,包括properties、settings、plugins等等。我这里暂时只配置了environments和mappers节点。

5.1 解析environments

environments节点中包含了连接数据所需要的基本信息,主要包括两部分内容,一部分就是transactionManager,一部分是dataSource

private void environmentsElement(XNode context) throws Exception {
    if (context == null) {
      return;
    }
    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"));
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory)
            .dataSource(dataSource);
        configuration.setEnvironment(environmentBuilder.build());
        break;
      }
    }
  }

我这里配置了transactionManager为JDBC类型,在dataSource下配置了连接数据的地址,用户名和密码。

5.2 解析mapper

private void mappersElement(XNode context) throws Exception {
    if (context == null) {
      return;
    }
    for (XNode child : context.getChildren()) {
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        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) {
          ErrorContext.instance().resource(resource);
          try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
                configuration.getSqlFragments());
            mapperParser.parse();
          }
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          try (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<?> 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.");
        }
      }
    }
  }

可以看到,通过mapper标签配置mybatis至少有2种配置方式:

  • 1.配置package,并添加属性name:

    <package name=“org.example.ssm.mapper”/>

  • 2.配置 resource, url, class,三个其中一个

    <mappers>
    <mapper resource=“UserMapper.xml”/>
    <mapper class=“org.example.ssm.mapper.UserMapper.class”/>
    <mapper url=“http://userMapper.xml”/>
    </mappers>
    这样都可以读取,以resource方式读取为例:

ErrorContext.instance().resource(resource);
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
  XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
      configuration.getSqlFragments());
  mapperParser.parse();
}

这里首先通过构造函数创建了一个XMLMapperBuilder,同样,也会创建一个XPathParser去解析xml文件。
由XMLMapperBuilder.parse()方法来构建mapper:

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);
    }
  }

可以看到,还会去解析mapper这个element下的各个元素:

  • cache-ref
  • cache
  • parameterMap
  • resultMap
  • sql
  • select|insert|update|delete

5.2.1 解析配置parameterMap

ParameterMap 是用来定义 SQL 语句中的参数映射关系的。通过 ParameterMap,可以将 Java 对象中的属性映射到 SQL 语句中的参数,从而实现参数的传递和绑定。

ParameterMap 的作用包括:

    1. 简化 SQL 语句中的参数设置:通过 ParameterMap 可以将 Java 对象中的属性直接映射到 SQL 语句中的参数,避免了在 SQL 语句中重复设置参数。
    1. 提高代码的可维护性:将 SQL 语句中的参数映射关系集中在 ParameterMap 中管理,便于统一维护和修改。
    1. 提高代码的重用性:可以在多个 SQL 语句中重复使用同一个 ParameterMap,减少重复的设置参数的工作。
    1. 支持更复杂的参数映射关系:ParameterMap 可以定义更复杂的参数映射关系,例如多个参数的组合、嵌套对象等。
private void parameterMapElement(List<XNode> list) {
    for (XNode parameterMapNode : list) {
      String id = parameterMapNode.getStringAttribute("id");
      String type = parameterMapNode.getStringAttribute("type");
      Class<?> parameterClass = resolveClass(type);
      List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
      List<ParameterMapping> parameterMappings = new ArrayList<>();
      for (XNode parameterNode : parameterNodes) {
        String property = parameterNode.getStringAttribute("property");
        String javaType = parameterNode.getStringAttribute("javaType");
        String jdbcType = parameterNode.getStringAttribute("jdbcType");
        String resultMap = parameterNode.getStringAttribute("resultMap");
        String mode = parameterNode.getStringAttribute("mode");
        String typeHandler = parameterNode.getStringAttribute("typeHandler");
        Integer numericScale = parameterNode.getIntAttribute("numericScale");
        ParameterMode modeEnum = resolveParameterMode(mode);
        Class<?> javaTypeClass = resolveClass(javaType);
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
        ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property,
            javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
        parameterMappings.add(parameterMapping);
      }
      builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
    }
  }

可以看到,这里可以配置的属性有很多,有property,javaType,jdbcType等等。
不过从官网得知,这个元素已经被废弃,后序不再使用了。

5.2.2 ResultMap

resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。

ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

private void resultMapElements(List<XNode> list) {
    for (XNode resultMapNode : list) {
      try {
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }


private ResultMap resultMapElement(XNode resultMapNode) {
    return resultMapElement(resultMapNode, Collections.emptyList(), null);
  }

  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings,
      Class<?> enclosingType) {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType",
        resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator,
        resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

官网的解释很到位,这里我不做无用摘抄,直接上链接!mybatis官网

5.2.3 SQL

这里也就是解析配置的内容,挨个按顺序解析:

private void sqlElement(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
  }

  private void sqlElement(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      id = builderAssistant.applyCurrentNamespace(id, false);
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        sqlFragments.put(id, context);
      }
    }
  }

5.2.4 select|insert|update|delete

这个是去构建statement,以便可以具体execute语句,可以对数据库进行增删改查。通过statement或者实现类preparedStatement操作已经是JDBC链接操作数据库的基本操作了。

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);
      }
    }
  }

至此,我们似乎已经把需要的东西全部准备好了:

  • 连接数据库的基本信息,包括数据库地址、用户名、密码、transactionManager
  • mapper,定义用什么数据作参数,获得什么样的数据,并如何转化为什么形式。

可别忘了,咱这是在干嘛,是在创建SqlSessionFactory!有了配置文件,就直接调用DefaultSqlSessionFactory的构造函数创建就好了。

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

创建好了SqlSessionFactory,那就可以获取SqlSession了,然后就终于可以获取Mapper了!

接着看SqlSession的getMapper方法:

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}

跟到Configuration类中的实现方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

而MapperRegistry的getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

从knownMappers中根据type获取MapperProxyFactory,然后创建其实例,即UserMapper。

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

最终获取到这个mapper,然后调用mapper的findByName方法。

6. 总结

mybatis本身看起来并不复杂,学习难度与大名鼎鼎的hibernate相比,要小很多。整体流程非常清晰,其核心就是SqlSession。围绕SqlSession的创建,以及通过mapper定义Sql语句,通过SqlSession创建statement对数据库进行操作,然后对返回数据作映射。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机专业课面试常见问题-编程语言篇

目录 1. 程序的编译执行流程&#xff1f; 2. C浅拷贝和深拷贝的区别&#xff1f; 3. C虚函数&#xff1f; …

Linux --账号和权限管理

目录 1、 管理用户账号和组账概述 1.1 用户账号分类 1.2 组账号 1.3 UID 和 GID 2、用户账号文件 2.1 passwd 2.2 shadow 3、管理目录和文件属性 3.1 chage 命令 3.2 useradd 命令 3.3 passwd 命令 ​编辑3.4 usermod 命令 3.5 userdel 命令 4、用户账户的初始配置…

全面体验ONLYOFFICE 8.1版本桌面编辑器

ONLYOFFICE官网 在当今的数字化办公环境中&#xff0c;选择合适的文档处理工具对于提升工作效率和团队协作至关重要。ONLYOFFICE 8.1版本桌面编辑器&#xff0c;作为一款集成了多项先进功能的办公软件&#xff0c;为用户提供了全新的办公体验。今天&#xff0c;我们将深入探索…

【分布式系列】分布式锁的设计与实现

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

2024年【电工(初级)】考试内容及电工(初级)模拟考试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 电工&#xff08;初级&#xff09;考试内容参考答案及电工&#xff08;初级&#xff09;考试试题解析是安全生产模拟考试一点通题库老师及电工&#xff08;初级&#xff09;操作证已考过的学员汇总&#xff0c;相对有…

博途TIA Portal「集成自动化软件」下载安装,TIA Portal 灵活多变的编程环境

在编程领域&#xff0c;博途TIA Portal以其卓越的编程工具和灵活多变的编程环境&#xff0c;为众多用户提供了前所未有的便利。这款软件不仅支持多种编程语言&#xff0c;如梯形图&#xff08;Ladder Diagram&#xff09;、功能块图&#xff08;Function Block Diagram&#xf…

linux的CP指令

实现 CP 指令 src 源文件 des 目标文件 执行流程&#xff1a; 打开源文件&#xff08; src &#xff09; open 打开目标文件&#xff08; des &#xff09; open 写入目标文件 write 读取 src 文件到缓存数组 read 关闭目标文件和源文件 close ./a.out src.c de…

Linux开发讲课22---I2C读写 EEPROM 实验(含代码)

EEPROM 是一种掉电后数据不丢失的存储器&#xff0c;常用来存储一些配置信息&#xff0c;以便系统重新上电的时候加载之。 EEPOM 芯片最常用的通讯方式就是 I2C 协议&#xff0c;本小节以 EEPROM的读写实 验为大家讲解 STM32 的 I2C 使用方法。实验中 STM32 的 I2C 外设采用主模…

漫步5G-A City,一份独属于上海的浪漫

作家亨利詹姆斯曾写道&#xff0c;“城市漫步&#xff0c;让我接触到了这个世界上最好的东西”。 用漫无目的地行走&#xff0c;来体验和观察一座城市&#xff0c;上海凭借丰富多元的文化特质&#xff0c;成为citywalk这种浪漫生活方式的流行地。 无论你是漫步在美术馆、画廊林…

Linux shell编程学习笔记60:touch命令

0 前言 在csdn技能树Linux入门的练习题中&#xff0c;touch是最常见的一条命令。这次我们就来研究它的用法。 1 touch命令的功能、格式和选项说明 我们可以使用touch --help命令查看touch命令的帮助信息。 [purpleendurer bash ~ ]touch --help Usage: touch [OPTION]... …

解决java中时间参数的问题

在java的日常开发中&#xff0c;我们经常需要去接收前端传递过来的时间字符串&#xff0c;同时给前端返回数据时&#xff0c;也会涉及到时间字段的数据传递&#xff0c;那么我们要如何处理这些字段呢&#xff1f; 知识铺垫&#xff1a;java最后返回的时间是时间世界&#xff0…

吉利银河L6(官方小订送的3M) 对比 威固vk70+ks15

吉利送的号称价值2000的3M效果 撕膜重贴 威固vk70ks15 之后的效果 // 忘记测反射的热量了 可以验证金属膜是反射热而不是吸热 金属膜 手机GPS还能用吗 亲测 能用 太阳能总阻隔率 3M貌似20%出头 威固前档55% 侧后挡高一点不超过60% 夏天真实太阳发热能量 即阻隔率55%到60% …

时序数据中的孤立野点、异常值识别及处理方法

目录 参考资料 对时序数据做差分&#xff1b; 参考资料 [1] 离群点&#xff08;孤立点、异常值&#xff09;检测方法 2017.6&#xff1b;

重温react-06(初识函数组件和快速生成格式的插件使用方式)

开始 函数组件必然成为未来发展的趋势(个人见解),总之努力的去学习,才能赚更多的钱.加油呀! 函数组件的格式 import React from reactexport default function LearnFunction01() {return (<div>LearnFunction01</div>) }以上是函数式组件的组基本的方式 快捷生…

前端开源项目Vuejs:让前端开发如虎添翼!

文章目录 引言一、Vue.js的优势二、Vue.js实战技巧三、Vue.js社区与资源结语 引言 在前端开发的世界里&#xff0c;Vue.js凭借其简洁、轻量且功能强大的特性&#xff0c;逐渐崭露头角&#xff0c;成为众多开发者心中的首选框架。 一、Vue.js的优势 Vuejs项目地址 Vue.js之…

Linux开发讲课19--- SPI原理

一、概述 SPI&#xff08;Serial Peripheral Interface&#xff09;&#xff0c;是一种高速串行全双工的接口。最早由Motorola首先提出的全双工同步串行外围接口&#xff0c;采用主从模式(Master—Slave)架构&#xff0c;支持一个或多个Slave设备。SPI接口主要应用在EEPROM、F…

深入 SSH:解锁本地转发、远程转发和动态转发的潜力

文章目录 前言一、解锁内部服务&#xff1a;SSH 本地转发1.1 什么是 SSH 本地转发1.2 本地转发应用场景 二、打开外部访问大门&#xff1a;SSH 远程转发2.1 什么是 SSH 远程转发2.2 远程转发应用场景 三、动态转发&#xff1a;SSH 让你拥有自己的 VPN3.1 什么是 SSH 动态转发3.…

仓库管理系统17--客户管理

原创不易&#xff0c;打字不易&#xff0c;截图不易&#xff0c;多多点赞&#xff0c;送人玫瑰&#xff0c;留有余香&#xff0c;财务自由明日实现 1、添加用户控件 <UserControl x:Class"West.StoreMgr.View.CustomerView"xmlns"http://schemas.microsof…

镭速是如何做到对涉密文件进行大数据迁移的?

随着公司业务的扩展和技术创新&#xff0c;企业经常需要在不同的系统和云服务之间转移庞大的数据量&#xff0c;以适应业务需求和提高资源使用效率。但这一过程中&#xff0c;安全问题尤为突出&#xff0c;成为IT部门的首要挑战。 本文将探讨在大规模数据迁移中可能遇到的安全风…

C语言入门课程学习笔记8:变量的作用域递归函数宏定义交换变量

C语言入门课程学习笔记8 第36课 - 变量的作用域与生命期&#xff08;上&#xff09;第37课 - 变量的作用域与生命期&#xff08;下&#xff09;实验—局部变量的作用域实验-变量的生命期 第38课 - 函数专题练习第39课 - 递归函数简介实验-递归小结 第40课 - C 语言中的宏定义实…