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

Stella981
• 阅读 668

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

一:源码分析代码片段

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文件,断点调试结果如下:

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

第三步源码分析

// 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配置文件的内容:

我们先进入下面这行代码:因为这个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()代码结果如下:

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

我们看下这段代码:

this.configuration.setEnvironment(environmentBuilder.build());

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

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

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

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

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

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分支:

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

由上面断点分析可知,这里会读取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 loadedResources;

public Configuration() { this.loadedResources = new HashSet(); }

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

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

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

我们进入下面这个方法:

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 void addMapper(Class type) { this.mapperRegistry.addMapper(type); }

public void addMapper(Class 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结果如下:

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

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

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

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

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

最后,我们回到开始的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/

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Mybatis深入源码分析之Mapper与接口绑定原理源码分析
!(https://www.w3cschool.cn/attachments/image/20170807/1502093784622523.png)紧接上篇文章:Mybatis深入源码分析之SqlSessionFactoryBuilder源码分析(https://my.oschina.net/u/3995125/blog/3079296),这里
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
Mybatis深入源码分析之SQLSession一级缓存原理分析
!(https://www.w3cschool.cn/attachments/image/20170807/1502093784622523.png)通过前面几篇文章,Mybatis深入源码分析之SqlSessionFactoryBuilder源码分析(https://my.oschina.net/u/3995125/blog/3079296),
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这