文档章节

Mybatis深入源码分析之SqlSessionFactoryBuilder源码分析

须臾之余
 须臾之余
发布于 07/26 00:30
字数 1989
阅读 39
收藏 0

一:源码分析代码片段

public static void main(String[] args) {

    try {
        // 基本mybatis环境
        // 1.定义mybatis_config文件地址
        String resources = "mybatis_config.xml";
        // 2.获取InputStreamReaderIo流
        Reader reader = Resources.getResourceAsReader(resources);
        // 3.获取SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        // 4.获取Session
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 5.操作Mapper接口
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        UserEntity user = mapper.getUser(2);
        System.out.println(user.getName());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

首先对步骤2进行分析

// 2.获取InputStreamReaderIo流

Reader reader = Resources.getResourceAsReader(resources);

public static Reader getResourceAsReader(String resource) throws IOException {
    InputStreamReader reader;
    if (charset == null) {
        reader = new InputStreamReader(getResourceAsStream(resource));
    } else {
        reader = new InputStreamReader(getResourceAsStream(resource), charset);
    }

    return reader;
}

通过上述代码可知:使用了门面模式:定义了Resource类,把复杂过程封装起来,方便用户使用,返回reader为InputStreamReader,指的是读取的mybatis_config.xml文件,断点调试结果如下:

第三步源码分析

// 3.获取SqlSessionFactory

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

进入SqlSessionFactoryBuilder()构造函数如下:

public SqlSessionFactoryBuilder() {
}

可知,无参构造函数没用做任何事情,再进入build(reader)源码,reader参数为InputStream流

public SqlSessionFactory build(Reader reader) {
    return this.build((Reader)reader, (String)null, (Properties)null);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    SqlSessionFactory var5;
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
        var5 = this.build(parser.parse());
    } catch (Exception var14) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
        ErrorContext.instance().reset();

        try {
            reader.close();
        } catch (IOException var13) {
            ;
        }
    }
    return var5;
}

我们来分析下XMLConfigBuilder这个类是干嘛的,进入XMLConfigBuilder构造函数如下:

public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    this.localReflectorFactory = new DefaultReflectorFactory();
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}

进入super()代码如下:

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

通过上述代码可知:this.parsed = false;后面有用,这里先提下。返回原先执行处:var5 = this.build(parser.parse());

var5 = this.build(parser.parse());

进入parser.parse()这个方法,代码如下:

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

由前面设置了this.parsed = false,可知this.parsed为false,就进入else分支,读者这个时候就有疑问了,为啥要设置this.parsed = false呢?

我们通过else分支可知,又设置了 this.parsed = true;说明再下一次再次进入parse方法的时候,this.parsed=true会直接抛出异常。

这里我们可以总结下:

为什么XMLConfigBuilder只能被使用一次呢?
答:因为我们的Configuration是一个全局的,所以只能被解析一次。
多次解析的话,会抛出:Each XMLConfigBuilder can only be used once.异常,防止用户私自调用parse()方法再去重复解析,因为配置文件是全局的,不能多次解析。

进入else分支的下面这个代码中:

this.parseConfiguration(this.parser.evalNode("/configuration"));
private void parseConfiguration(XNode root) {
    try {
        this.propertiesElement(root.evalNode("properties"));
        this.typeAliasesElement(root.evalNode("typeAliases"));
        this.pluginElement(root.evalNode("plugins"));
        this.objectFactoryElement(root.evalNode("objectFactory"));
        this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        this.reflectionFactoryElement(root.evalNode("reflectionFactory"));
        this.settingsElement(root.evalNode("settings"));
        this.environmentsElement(root.evalNode("environments"));
        this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        this.typeHandlerElement(root.evalNode("typeHandlers"));
        this.mapperElement(root.evalNode("mappers"));
    } catch (Exception var3) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
    }
}

我们先看看mybatis_config.xml配置文件的内容:

<configuration>
    <!-- 环境配置 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!-- 数据库连接相关配置 ,这里动态获取config.properties文件中的内容-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!-- mapping文件路径配置 -->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

我们先进入下面这行代码:因为这个environments在我们配置文件中配置了,我们先分析它:

this.environmentsElement(root.evalNode("environments"))
private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        if (this.environment == null) {
            this.environment = context.getStringAttribute("default");
        }
        Iterator i$ = context.getChildren().iterator();
        while(i$.hasNext()) {
            XNode child = (XNode)i$.next();
            String id = child.getStringAttribute("id");
            if (this.isSpecifiedEnvironment(id)) {
                TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
                DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
                DataSource dataSource = dsFactory.getDataSource();
                Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
                this.configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}

通过断点调试environmentsElement()代码结果如下:

我们看下这段代码:

this.configuration.setEnvironment(environmentBuilder.build());
public void setEnvironment(Environment environment) {
    this.environment = environment;
}

到这里我们就明白了:这里将解析的XML结点封装成Environment对象,再把Environment对象设置给Configuration对象中。也就是解析XML,再把XML转为Configuration实体类

到这里我们再来分析:mappers结点在配置文件中配置了,我们也来分析下,下面是mapper.xml配置文件的内容,看下是如何转化为实体对象保存起来的:

<mapper namespace="com.mayikt.mapper.UserMapper">
    <!-- 在select标签中编写查询的SQL语句, 设置select标签的id属性为getUser,id属性值必须是唯一的,不能够重复
    使用parameterType属性指明查询时使用的参数类型,resultType属性指明查询返回的结果集类型
    resultType="com.mayikt.entity.User"就表示将查询结果封装成一个User类的对象返回
    User类就是users表所对应的实体类
    -->
    <!--
        根据id查询得到一个user对象
     -->
    <select id="getUser" parameterType="int"
            resultType="com.mayikt.entity.UserEntity">
        select * from user where id=#{id}
    </select>
</mapper>
this.mapperElement(root.evalNode("mappers"));
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        Iterator i$ = parent.getChildren().iterator();
        while(true) {
            while(i$.hasNext()) {
                XNode child = (XNode)i$.next();
                String resource;
                if ("package".equals(child.getName())) {    //注解方式配置扫包package
                    resource = child.getStringAttribute("name");
                    this.configuration.addMappers(resource);
                } else {    //resource 方式
                    resource = child.getStringAttribute("resource");
                    String url = child.getStringAttribute("url");
                    String mapperClass = child.getStringAttribute("class");
                    XMLMapperBuilder mapperParser;
                    InputStream inputStream;
                    if (resource != null && url == null && mapperClass == null) {
                        ErrorContext.instance().resource(resource);
                        inputStream = Resources.getResourceAsStream(resource);
                        mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                        mapperParser.parse();
                    } else if (resource == null && url != null && mapperClass == null) {
                        ErrorContext.instance().resource(url);
                        inputStream = Resources.getUrlAsStream(url);
                        mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                        mapperParser.parse();
                    } else {
                        if (resource != null || url != null || mapperClass == null) {
                            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                        }
                        Class<?> mapperInterface = Resources.classForName(mapperClass);
                        this.configuration.addMapper(mapperInterface);
                    }
                }
            }
            return;
        }
    }
}

通过上述代码可知,配置方式有两种:一种是注解形式扫包,第二种是resource方式

我们是resource方式的配置,所以进入else分支:

由上面断点分析可知,这里会读取mapper.xml配置文件的内容,转化为inputStream流,再解析mapper.xml配置文件

XMLMapperBuilder类的作用:解析mapper配置文件得到Configuration对象,我们看下XMLMapperBuilder怎么去解析mapper配置文件

public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    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;
}

最终进入:

mapperParser.parse()
public void parse() {
    if (!this.configuration.isResourceLoaded(this.resource)) {
        this.configurationElement(this.parser.evalNode("/mapper"));
        this.configuration.addLoadedResource(this.resource);
        this.bindMapperForNamespace();
    }

进入addLoadedResource()方法:

public void addLoadedResource(String resource) {
    this.loadedResources.add(resource);
}
protected final Set<String> loadedResources;
public Configuration() {
    this.loadedResources = new HashSet();
}

通过上述代码可知:loadedResources存放的都是mybatis映射的文件路径地址【mapper.xml】, 使用HashSet集合存放

存放进去之后,断点如下:

我们进入下面这个方法:

this.bindMapperForNamespace();
private void bindMapperForNamespace() {
    String namespace = this.builderAssistant.getCurrentNamespace(); //拿到mapper.xml里面配置的namespace,这里是com.mayikt.mapper.UserMapper
    if (namespace != null) {
        Class boundType = null;
        try {
            boundType = Resources.classForName(namespace);    //通过Java反射机制帮我去查找,这里得到interface com.mayikt.mapper.UserMapper
        } catch (ClassNotFoundException var4) {
            ;
        }
        if (boundType != null && !this.configuration.hasMapper(boundType)) {//判断mapper.xml配置文件是否注册过
            this.configuration.addLoadedResource("namespace:" + namespace);
            this.configuration.addMapper(boundType);
        }
    }
}

先看看addMapper方法:

this.configuration.addMapper(boundType);
public <T> void addMapper(Class<T> type) {
    this.mapperRegistry.addMapper(type);
}
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {    //判断是否是接口类型
        if (this.hasMapper(type)) { //再次判断是否注册过,如果注册过,则抛出异常
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            this.knownMappers.put(type, new MapperProxyFactory(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                this.knownMappers.remove(type);
            }
        }
    }
this.knownMappers.put(type, new MapperProxyFactory(type));
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

由上述代码可知:mapperRegistry作用是:存放dao层mapper接口,debug结果如下:

最后,我们来看看loadedResources里面的东西:存放的是userMapper的配置文件

再看看mapperRegistery里面的东西:存放的是mapper接口

最后,我们回到开始的parse()方法,上述代码执行完this.parseConfiguration(this.parser.evalNode("/configuration"))方法之后,返回configuration对象

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

到这里,我们就结束了源码分析,下面总结下大体流程:

 总结:

  1. 获取本地InputStreamReader对象(mybatis配置文件)
  2. 调用SqlSessionFactoryBuilder
  3. ###再使用XMLConfigBuilder解析mybatis配置文件,装配到Configuration中。
  4. 将配置文件中的Mapper添加到Configuration mapperRegistry实现注册。
  5. 备注:mapperRegistry存放当前所有的mapper接口。

  6. loadedResources里面的东西:存放的是userMapper的配置文件

本文参考

蚂蚁课堂:http://www.mayikt.com/

© 著作权归作者所有

须臾之余
粉丝 125
博文 68
码字总数 178724
作品 0
吉安
程序员
私信 提问
mybatis之SqlSessionFactory

版本:mybatis-3 主要分析SqlSessionFactory的构建过程 SqlSessionFactoryBuilder从XML中构建SqlSessionFactory SqlSessionFactoryBuilder用来创建SqlSessionFactory实例,一旦创建了SqlSessio......

udbwcso
2016/04/06
1K
0
Mybatis源码分析之SqlSessionFactory(一)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 https://blog.csdn.net/t4i2b10X4c22nF6A/article/details/100137596...

JAVA高级架构v
08/28
0
0
MyBatis 源码分析——介绍

笔者第一次接触跟MyBatis框架是在2009年未的时候。不过那个时候的他并不叫MyBatis,而是叫IBatis。2010年的时候改为现在的名字——MyBatis。这几年过去了,对于笔者来讲有一点陌生了。而且那...

Java小铺
2018/08/10
223
0
【深入浅出MyBatis系列一】MyBatis入门

深入浅出MyBatis系列 【深入浅出MyBatis系列一】MyBatis入门 【深入浅出MyBatis系列二】配置简介(MyBatis源码篇) 【深入浅出MyBatis系列三】Mapper映射文件配置 【深入浅出MyBatis系列四】...

陶邦仁
2015/12/22
6K
0
MyBatis源码解读之SqlSession

目的 通过源码分析SqlSession功能实现、如何创建以及在Spring中是如何集成的。 2. SqlSession 功能介绍 MyBatis工作的主要Java接口,通过这些接口你可以执行命令,获取mapper和管理事务 --代...

无忌
2018/03/08
774
0

没有更多内容

加载失败,请刷新页面

加载更多

Podman 使用指南

> 原文链接:Podman 使用指南 Podman 原来是 CRI-O 项目的一部分,后来被分离成一个单独的项目叫 libpod。Podman 的使用体验和 Docker 类似,不同的是 Podman 没有 daemon。以前使用 Docker...

米开朗基杨
42分钟前
5
0
拯救 项目经理个人时间的5个技巧

优秀的项目经理都有一个共同点,那就是良好的时间管理能力。专业的项目经理会确保他们的时间投入富有成效,尽可能避免时间浪费。 时间管理叫做GTD,即Getting Things Done——“把事情做完”...

Airship
今天
6
0
LNMP环境介绍,Mariadb安装,服务管理,mariadb安装3

LNMP环境介绍 Nginx 处理的请求有两种,分为 静态与动态 图片,js,css,视频,音频,flash 等都是静态请求,这些数据都不是保存在数据库里面的 动态请求一般来说,需要的数据是在数据库里面...

doomcat
今天
1
0
前端技术之:Prisma Demo服务部署过程记录

安装前提条件: 1、已经安装了docker运行环境 2、以下命令执行记录发生在MackBook环境 3、已经安装了PostgreSQL(我使用的是11版本) 4、Node开发运行环境可以正常工作 首先需要通过Node包管...

popgis
今天
7
0
数组和链表

数组 链表 技巧一:掌握链表,想轻松写出正确的链表代码,需要理解指针获引用的含义: 对指针的理解,记住下面的这句话就可以了: 将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指...

code-ortaerc
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部