mybatis原理解析1

大家应该都使用过JDBC,传统的JDBC工作流程比较繁琐,需要先连接,然后处理JDBC底层事务,还需要操作Connection对象、Statement对象去拿取数据,并且还要捕捉各种可能出现的异常,十分复杂,因此我们一般使用ORM框架对数据进行处理,这就引出了我们今天的主题——Mybatis。

Mybatis相信熟悉Java的朋友们都不会陌生,是一个半自动映射ORM框架,使用的非常广泛,话不多说,我们来探究一下它的工作原理:

Mybtis主要的工作流程分为两个阶段:构建阶段和执行阶段

1 构建阶段:初始化来准备运行环境,根据xml文件构建Configuration对象,这一阶段的主要主要构建器如下:

SqlSessionFatcory:管理数据库会话、聚合Configuration对象

SqlSessionFactoryBuilder: SqlSessionFatcory的构造器,可以读取配置文件构建SqlSessionFactory

Configuration:存储Mybatis运行时的所有配置

BaseBuilder:构造器基类,用来处理Configuration、typeAlias与typeHandler对象

XMLConfigBuilder:解析XML文件构建的Configuration对象

XMLMapperBuilder:解析XML定义的SQL映射配置对象集合

 

    public SqlSessionFactory getSqlSessionFactory() throws IOException {
		String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		return new SqlSessionFactoryBuilder().build(inputStream);
	}

这是一段常用的获取SqlSessionFactory对象的代码,它和之前描述的一样,通过使用SqlSessionFactoryBuilder构造器来构建SqlSessionFactory对象,实际上这一段代码包含的逻辑还是比较繁琐的:

//该类包含很多重载的build方法,下面我只列出了主要调用的方法
public class SqlSessionFactoryBuilder {

  //样例代码调用的构造器,会调用另一个构造方法
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  //被调用的构造方法
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
       //创建解析XML文件的XMLConfiguBuilder对象
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        //将构建好的config回传给build方法
      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.
      }
    }
  }


}

这段代码主要有两个工作,初始化XMLConfigBuilder类的实例,使用这个实例解析XML配置文件,下面我们来看如何构建XMLConfigBuilder对象:

//初始化一个XpathParser对象,该类是Mybatis对JDK自带DOM工具的封装
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);
    this.document = createDocument(new InputSource(inputStream));
  }

下一步我们看如何使用这个解析器解析XML配置文件,在这声明一下,因为源码的分析会涉及到很多的类,所有我会在代码上方标出类的全名;Config类的对象大部分构建都是在XMLConfigBuilder类实现的:

XMLConfigBuilder类

  private boolean parsed;
  private final XPathParser parser;
  private String environment;
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

 public Configuration parse() {
    //避免重复解析
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
       从配置文件的ConfigUration标签处开始解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

    //开始解析工作
  private void parseConfiguration(XNode root) {
    try {
      propertiesElement(root.evalNode("properties"));
        //加载属性配置
      Properties settings = settingsAsProperties(root.evalNode("settings"));
        //加载vfs(我也不是很了解这个东西)
      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);
        //加载环境标志
      environmentsElement(root.evalNode("environments"));
        //加载数据库厂商标志
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        //加载类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
        //加载SQL Mapper,也是下文会继续讲解的重点
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

到这一步大家就应该了解XML文件是如何解析的,至于更具体的解析各个标签甚至子标签,我就不仔细讲了,一来非常繁琐,二来我自己也不是很熟悉这块,大家有兴趣的话可以阅读源码进行学习;下一步我们讲解SQl Mapper的初始化

XMLConfigBuilder类

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);
            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);
            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) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            //对自定义Mapper执行addMapper方法
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

XMLConfigBuilder在解析SQL Mapper配置时支持扫描包和指定类的方式,这两种方式都会反射Mybatis的SQL Mapper注释:

MapperRegistry类

public void addMappers(String packageName, Class<?> superType) {
    //扫描包
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    将包内的类处理成MappedStatement
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

public <T> void addMapper(Class<T> type) {
    //只处理接口,我们的方法都是写在接口中
    if (type.isInterface()) {
        //判断重复加载
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        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.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        //加载失败则清理Mapper
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

暂时就写到这了,关于XMLBuilder如何加载配置内容,使MapperRegistry将构建好的Mapper注册到configuratin对象中以后有时间会继续更新的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值