欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

mybatis 源码解析

程序员文章站 2022-04-21 23:47:59
...

mybatis 初始化流程
步骤:
1、通过 ClassLoader 类加载器读取某个路径的 xml 文件来获取 InputStream 流对象.
2、通过 SqlSessionFactoryBuilder 对象来解析流, 返回工厂
3、通过 SqlSessionFactory 工厂获取 SqlSession对象
4、通过 SqlSession 可以操作. (查询、删除、修改、添加)

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sessionFactory.openSession();
sqlSession.insert(".....");

我们通过 SqlSesseionFactoryBuilder 类来分析,它是如何通过InputStream来构建工厂的.

	public SqlSessionFactory build(InputStream inputStream) {
		return build(inputStream, null, null); //方法重载
	}
	
	public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            //创建XMLConfigBuilder对象
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            //封装Configuration对象成SqlSessionFactory
            return build(parser.parse());   //parser.parse()解析返回Configuration 对象
        } 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.
            }
        }
    }
    
    //通过Configuration 创建 SqlSessionFactory 工厂  (此方法相当于中转)
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

可以发现 XMLConfigBuilder 对象中的parse() 方法的作用。主要是把 InputStream 解析成一个 Configuration 对象。然Configuration 对象在 mybatis 中是非常重要的。它是 mybatis 全局唯一个配置对象。通过它可以获取到所有的配置及信息。咱先暂时不解读 Configuration 对象.

XMLConfigBuilder 对象解析, 先看其构造器:

	//调用的构造器(实例化 XPathParser)
    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
    }
    
    //parser 为mybatis 解析器. 暂不分析
	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;
    }

	//解析开始的地方. 也就是我们开始解析配置文件的第一步.
	public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;  //标识解析已开始 
        parseConfiguration(parser.evalNode("/configuration"));   //解析<configuration>标签
        return configuration;
    }

	// 解析文件推荐先看下 mybatis 官方文档:  http://www.mybatis.org/mybatis-3/zh/getting-started.html
	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
            // 在objectFactory和objectWrapperFactory第631期之后阅读它
            //解析environments节点, 如若为空配置默认的
            environmentsElement(root.evalNode("environments"));
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            typeHandlerElement(root.evalNode("typeHandlers"));
            //解析mappers 节点对应的映射文件
            //解析<mappers>标签 			** 解析映射文件 (我们重点解析这个节点下的, 有兴趣的话可以看下其它的解析步骤)
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
	//解析映射文件  如: TestDao.xml 
	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"); //获取到xml路径
                    String url = child.getStringAttribute("url");  //空
                    String mapperClass = child.getStringAttribute("class");  //空
                    if (resource != null && url == null && mapperClass == null) {
                        ErrorContext.instance().resource(resource);
                        //获取 mapper.xml 文件InputStream
                        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) {
                        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<?> 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.");
                    }
                }
            }
        }
    }

发现在解析下的节点时, mybatis 会通过 XMLMapperBuilder 对象来解析. 在构造 XMLMapperBuilder 对象会创建一个 XPathParser 对象及一个 XMLMapperEntityResolver 对象来帮助解析. 这二个对象暂时不分析, 有兴趣的话可以去了解下。 继续看源码

	public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
		//创建了 XPathParser  与  XMLMapperEntityResolver 
    	this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments);
	}

	private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
	    super(configuration);
	    this.builderAssistant = new MapperBuilderAssistant(configuration, resource); //命名空间构建助手
	    this.parser = parser;
	    this.sqlFragments = sqlFragments;
	    this.resource = resource;
  	}

映射文件解析

	public void parse() {
		// 如果当前的映射文件未被加载, 就开始解析.  (resource xml的命名空间)
	    if (!configuration.isResourceLoaded(resource)) {
	      //配置mapper节点下的属性
	      configurationElement(parser.evalNode("/mapper"));
	      configuration.addLoadedResource(resource); //储存映射文件的命名空间
	      bindMapperForNamespace(); //绑定命名空间路径对应的 Class 对象
	    }
	
	    //解析未解决的语句及结果集对象映射
	    parsePendingResultMaps();
	    parsePendingCacheRefs();
	    parsePendingStatements();
  	}

	private void configurationElement(XNode context) {
        try {
            //mapper.xml的命名空间路径
            String namespace = context.getStringAttribute("namespace");
            if (namespace == null || namespace.equals("")) {  // 不设置命名空间会抛异常
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
            builderAssistant.setCurrentNamespace(namespace); // 給构建器助手设置当前命名空间

            //解析<mapper>标签下的 <cache-ref>标签 ======>> 不经常用不分析
            cacheRefElement(context.evalNode("cache-ref")); 
            cacheElement(context.evalNode("cache"));
            
            //解析<parameterMap>节点
            parameterMapElement(context.evalNodes("/mapper/parameterMap"));
            //解析<resultMap>节点
            resultMapElements(context.evalNodes("/mapper/resultMap"));
            //解析<sql>节点
            sqlElement(context.evalNodes("/mapper/sql"));
            //解析<select> <insert> <update> <delete> 节点
            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);
        }
    }
  

//////////////未完待续