首页 > 编程语言 >mybatis源码解析1

mybatis源码解析1

时间:2022-11-18 23:33:50浏览次数:61  
标签:configuration resource 对象 源码 mybatis new 解析 root evalNode

一、mybatis的简单使用

根据mybatis官网提供的例子,需要这么几个步骤

1、获取mybatis配置文件的输入流对象

2、创建SqlSessionFactoryBuilder对象

3、调用SqlSessionFactoryBuilder.build方法,传入前面获取的输入流对象,得到SqlSessionFactory对象

4、获取SqlSession对象

InputStream resource = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlsessionFactory = builder.build(resource);
SqlSession sqlSession = sqlsessionFactory.openSession();

拿到sqlsession对象后就可以执行sql了,执行sql的方式有两种

第一种直接通过sqlsession调用select等方法

Object one = sqlSession.selectOne("com.lyy.mybatis_source.mapper.BankMapper.count");
System.out.println(one);

这里selectOne方法传入的是sql文件中的statmentId,

第二种方式需要先获取到mapper接口的代理对象,然后通过代理对象执行sql

BankMapper mapper = sqlSession.getMapper(BankMapper.class);
int count = mapper.count();
System.out.println(count);

上边的代码是在builder.build方法中完成了对配置文件的解析,mybatis提供了多个重载的build方法

这些重载方法要么提供了输入流参数,要么提供了Configuration对象作为参数。

上边演示的是传入输入流来获取SqlSessionFactory,其实也可以先构造一个Configuration对象,然后传入build方法

Configuration configuration = new Configuration();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/transaction_test");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
Environment environment = new Environment("dev",transactionFactory,dataSource);
configuration.setEnvironment(environment);
configuration.addMapper(BankMapper.class);

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(configuration);
SqlSession sqlSession = sqlSessionFactory.openSession();
Object o = sqlSession.selectOne("com.lyy.mybatis_source.mapper.BankMapper.count");

二、mybatis对配置文件的解析

以下是一个简单的mybatis配置文件

<?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.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/transaction_test"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/lyy/mybatis_source/mapper/BankMapper.xml"/>
    </mappers>
</configuration>

根据上边的代码可以知道对配置文件的解析是在SqlSessionFactoryBuilder.build方法中完成的,查看下这个方法的源码

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 {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

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

可以看到传入输入流的build方法是先创建了一个XMLConfigBuilder对象parser,在调用parser.parse()方法完成对输入流的解析,得到一个Configuration对象,最后把这个对象传给重载的build方法完成SqlSessionFactory对象的创建。这里的关键步骤就是解析配置文件得到Configuration对象。

接着看一下XMLConfigBuilder的构造方法

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) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

先根据输入流创建一个解析器对象XPathParser,在调用重载的构造方法把解析器对象赋值给类中的属性。解析器内部会根据输入流把xml文件封装成一个Document对象,后边的步骤中就是从这个Document对象中获取配置信息。

接着看一下XMLConfigBuilder.parse方法

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 这里evalNode方法就是从document对象中获取节点信息
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      // 解析properties标签
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      // 解析typeAliases标签
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(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"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析mappers标签
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

可以看到在parseConfiguration方法中会对mybaits配置文件中的各种标签进行解析。XMLConfigBuilder中有一个configuration属性,解析过程中会对这个属性赋值,解析完成后返回这个属性。然后回到SqlSessionFactoryBuilder.build方法中继续创建SqlSessionFactory对象。

整个过程涉及到两个对象

SqlSessionFactoryBuilder, ----> XMLConfigBuilder

三、sql文件的解析

上边的XMLConfigBuilder.parseConfiguration方法中有一句mapperElement(root.evalNode("mappers"));,这里就是在解析sql文件,接着看下这个方法的源码

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.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.");
          }
        }
      }
    }
  }

我们在mappers标签中可以配置多个mapper标签,所以有一个for循环,每一个mapper标签上可以有

package,resource,class,url这几种属性,所以这个方法中对这几种情况分别进行处理。

其中package,class这两种情况类似,都调用的是configuration对象中的添加Mapper的方法。

resource,url这两种都是先创建XMLMapperBuilder对象,在调用parse方法。

parse方法会完成对sql文件的解析最终会创建MappedStatement对象,

Configuration类中有一个属性mappedStatements

Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")

这是一个Map,里边存储Statement id和MappedStatement对象的对应关系

parse方法执行完后会把创建的MappedStatement对象添加到这个Map中,这样后续执行sql的时候就可以根据id找到这个MappedStatement

下面接着看下XMLMapperBuilder.parse对sql文件的解析过程。

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

这里这个buildStatementFromContext方法会对sql语句进行解析。

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

然后就会调转到一个新对象XMLStatementBuilder.parseStatementNode方法中继续解析

这个过程涉及这几个对象

XMLConfigBuilder,--->XMLMapperBuilder,--->XMLStatementBuilder

标签:configuration,resource,对象,源码,mybatis,new,解析,root,evalNode
From: https://www.cnblogs.com/chengxuxiaoyuan/p/16905245.html

相关文章

  • MyBatisPlus 多数据源动态切换
    一、官方文档https://baomidou.com/pages/a61e1b/#%E6%96%87%E6%A1%A3-documentation二、操作步骤1、引入依赖版本号在下面 懒得单独复制了 一起贴出来了<!......
  • simpread-(132 条消息) three.js 加载 stl 文件并解析_攻城狮 plus 的博客 - CSDN 博
    threejs加载STL文件效果:加载stl格式的文件需要设置材质material;stl、obj都是静态模型,不可以包含动画,fbx除了包含几何、材质信息,可以存储骨骼动画等数据。代码:......
  • MyBatis练习(初)
    目录环境准备库,表idea准备题目一、简单查询环境准备MySQL,JDK17,Maven,MyBatis库,表库:CREATEDATABASEStuDB;学生表:Student(Sno,Sname,Ssex,Sage,Sdept)其中Sno为......
  • Mybatis中的${}和#{}区别(转载)
    动态sql是mybatis的主要特性之一,在mapper中定义的参数传到xml中之后,在查询之前,mybatis会对其进行动态解析。mybatis为我们提供了两种支持动态sql的语法:#{}以及......
  • 数据库处理封装 GotDotNet.ApplicationBlocks.Data AdoHelper Dao 源码
    数据库处理封装GotDotNet.ApplicationBlocks.DataAdoHelperDao源码在GotDotNet.ApplicationBlocks.DataAdoHelper源码的基础上进行封装处理使调用更方便。支持所有常......
  • Seata 1.5.2 源码学习(Client端)
    在上一篇中通过阅读Seata服务端的代码,我们了解到TC是如何处理来自客户端的请求的,今天这一篇一起来了解一下客户端是如何处理TC发过来的请求的。要想搞清楚这一点,还得从Glob......
  • mybatis中公共字段的自动填充
    在需要自动填充的字段上添加注解@TableField@ApiModelProperty(value="创建时间")@TableField(fill=FieldFill.INSERT)privateDategmtCreate;@A......
  • Mybatis - 基础学习6
    一.CRUD我们可以在工具类中设置自动提交事务!publicstaticSqlSessiongetSqlSession(){returnsqlSessionFactory.openSession(true);} 1.编写......
  • xml解析_Jsoup_Document对象、Element对象
    xml解析_Jsoup_Document对象Document:文档对象,代表内存中的dom树获取Element对象getElementById(Stringid):根据id属性值获取唯一的elemtnt对象ge......
  • k8s源码分析2-命令行工具cobra的使用
    本节重点介绍:kubectl的职责和kubectl的代码原理cobra库的使用简介kubectl的职责主要的工作是处理用户提交的东西(包括,命令行参数,yaml文件等)然后其会把用户提交......