Mybatis源码分析之(二)根据配置文件创建SqlSessionFactory(Configuration的创建过程)

SqlSessionFactoryBuilder.build创建SqlSessionFactory(粗略走一步流程)

看完上篇文章后,你对mybatis应该有个大概的了解了,那么我们知道new SqlSessionFactoryBuilder().build是框架的入口,我们到SqlSessionFactoryBuilder类里看到里面其实都是build函数的。

全都是build重载函数。上面几个重载其实最终都是调用了build(Reader reader, String environment, Properties properties)这个方法。下面的几个则是调用了build(InputStream inputStream, String environment, Properties properties) 。

最终都会调用最后一个build(Configuration config)方法,把创建好的Configuration赋给DefaultSqlSessionFactory里面的Configuration字段,所以这里的逻辑其实非常简单,就是解析传入配置文件的Reader或者inputStream,然后生成Configuration,赋值给新建出来的DefaultSqlSessionFactor中的configuration字段,然后返回DefaultSqlSessionFactor。

public class SqlSessionFactoryBuilder {public SqlSessionFactory build(Reader reader) {return build(reader, null, null);}public SqlSessionFactory build(Reader reader, String environment) {return build(reader, environment, null);}public SqlSessionFactory build(Reader reader, Properties properties) {return build(reader, null, properties);}public SqlSessionFactory build(Reader reader, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {reader.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);}public SqlSessionFactory build(InputStream inputStream, String environment) {return build(inputStream, environment, null);}public SqlSessionFactory build(InputStream inputStream, Properties properties) {return build(inputStream, null, properties);}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);}}

下面,我们来完整的走一个流程,就以上一篇的demo为例

//第一步public static SqlSession getSqlsession(){//获取mybatis,config的xml文件的输入流InputStream config = MybatisUtil.class.getClassLoader().getResourceAsStream("mybatis.cfg.xml");//使用SqlSessionFactory build(InputStream inputStream);来获取SqlSessionFactorySqlSessionFactory build = new SqlSessionFactoryBuilder().build(config);return build.openSession();}//第二步调用SqlSessionFactoryBuilder中的build(InputStream inputStream)方法public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);}//第三部调用SqlSessionFactoryBuilder中的build(InputStream inputStream, String environment, Properties properties)方法public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {//把输入流解析成可以用做分析的document,以及准备其他解析需要的东西XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);//parser.parse()生成出Configuration类,最后调用build(Configuration config)方法来生成DefaultSqlSessionFactory,并返回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.}}}//第四步调用SqlSessionFactoryBuilder中的 build(Configuration config) 方法public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}

这是最上面的一层,逻辑还是非常简单的,一句话就能概括,就是从给定的配置文件中解析出Configuration,然后生成DefaultSqlSessionFactory。(如果要指定环境,和特定的属性的话用另外2个build 的重载方法)

我们先进入最后一个build看一下,new DefaultSqlSessionFactory(config);究竟做了什么。

public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {//非常简单,就是生成了一个DefaultSqlSessionFactory 类,然后把里面的configuration字段,赋值为传入的configuration。this.configuration = configuration;}//后面的函数就不列出来了}

build是如何通过xml文件来生成Configuration的(比较详细的分析流程)

所以build的重点在解析xml成Configuration 类这个地方。
也就是下面这两句话

 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);parser.parse();

ok,第一句话是生成了一个XMLConfigBuilder类,我们来看看XMLConfigBuilder到底是什么样的。只列出字段和关键方法

public class XMLConfigBuilder extends BaseBuilder {private boolean parsed;private XPathParser parser;private String environment;private ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();//上层调用到这个方法,然后他调用到了下面的重载方法public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {//这里只有new XPathParser(inputStream, true, props, new XMLMapperEntityResolver())需要看一下this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}//其实就是给关键的几个字段赋值,这里的第一句调用了父类的构造函数,传入了一个新的Configuration,也就是说到这步的时候,我们用来返回的Configuration对象已经有了,但是Configuration的值还没有构造好。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 abstract class BaseBuilder {protected final Configuration configuration;protected final TypeAliasRegistry typeAliasRegistry;protected final TypeHandlerRegistry typeHandlerRegistry;}

接下来我们来看看XPathParser类

public class XPathParser {private Document document;private boolean validation;private EntityResolver entityResolver;private Properties variables;private XPath xpath;public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {//为类的其他字段赋值commonConstructor(validation, variables, entityResolver);//根据输入流创建一个Document ,并赋值给 document字段(之后的解析就靠这里面的信息了)this.document = createDocument(new InputSource(inputStream));}}

xml解析成Document的具体过程LZ就不带大家深入研究了,有兴趣的朋友可以自己跟下去看看。

接下来我们回到SqlSessionFactoryBuilder类。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {//现在我们已经拿到parser了XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);//接下来要去看parse()是怎么创建出Configuration的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 Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;//解析靠这步,首先拿到configuration节点parseConfiguration(parser.evalNode("/configuration"));return configuration;}public XNode evalNode(String expression) {return evalNode(document, expression);}public XNode evalNode(Object root, String expression) {//拿到configuration节点Node node = (Node) evaluate(expression, root, XPathConstants.NODE);if (node == null) {return null;}//返回封装成XNode类return new XNode(this, node, variables);}
public class XNode {private Node node;private String name;private String body;private Properties attributes;private Properties variables;private XPathParser xpathParser;public XNode(XPathParser xpathParser, Node node, Properties variables) {this.xpathParser = xpathParser;this.node = node;this.name = node.getNodeName();this.variables = variables;this.attributes = parseAttributes(node);this.body = parseBody(node);}
}

XPathParser类的实际值
XPathParser
封装好的Xnode的实际值
Xnode

//接下来就要解析并给configuration赋值拉。因为configuration在新建XMLConfigBuilder的时候就已经创建好了,解析的过程其实就是赋值的过程(我们在xml文件里配置的各个节点都在下面这个函数里解析)
private void parseConfiguration(XNode root) {try {//issue #117 read properties firstpropertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);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 #631environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}

这是xml文件中要的标签,和上面代码一对比,你就会清楚的明白,上面每一个方法其实就是把每一个标签转换成configuration。
xml

private void propertiesElement(XNode context) throws Exception {if (context != null) {//从子属性直接获取属性的键值对Properties defaults = context.getChildrenAsProperties();//从外部引入属性文件(其实就是获取resource属性的值)String resource = context.getStringAttribute("resource");//也可以从url下获取属性文件(引入网络路径或者磁盘路径下的资源)String url = context.getStringAttribute("url");if (resource != null && url != null) {throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");}//从resource 或者 url的属性文件中加载键值对,若和上面的defaults,键相同则替换defaults中的if (resource != null) {defaults.putAll(Resources.getResourceAsProperties(resource));} else if (url != null) {defaults.putAll(Resources.getUrlAsProperties(url));}Properties vars = configuration.getVariables();if (vars != null) {defaults.putAll(vars);}//设置parser的Variablesparser.setVariables(defaults);//设置configuration的Variablesconfiguration.setVariables(defaults);}}

其他的解析其实和properties的解析差不多。根据配置文件节点的信息设置configuration。

因为mybatis非常重要的一个点就是在mapper上,所以楼主之后会有一篇专门写mapper是什么解析和使用的。所以这里大家可以简单过一下mapper的解析过程。

  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) { //根据resource解析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) {//根据url 解析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) {//根据mapperClass解析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的mapper的实现方法还是要细细讨论的,让我们在之后的文章中在细致的学习以下。

小结

看了加载过程的源码,我们在这至少了解到了mybatis是如何解析xml文件的,还有properties,假如我们即在xml文件中直接配置的属性和又设置了resource(或者url)的话,resource(或者url)中的属性会覆盖xml文件中直接配的(key相同的情况下才会覆盖,否则是累加的情况),若详细看过mapper的创建过程的话,还会知道在xml中配的mapper的语句会被注解中配置的语句覆盖。为什么会有这些特性?因为源码的读取步骤以及策略如此,所以要善于利用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/555899.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis源码分析之(三)mapper接口底层原理(为什么不用写方法体就能访问到数据库)

mybatis是怎么拿sqlSession 在 上一篇的时候&#xff0c;我们的SqlSessionFactoryBuilder已经从xml文件中解析出了Configuration并且返回了sessionFactory。 然后我们要从session;中拿到sqlSession public class DefaultSqlSessionFactory implements SqlSessionFactory {pr…

mysql count distinct case when_统计符合条件的去重过的数量 - - count distinct if case

现有表结构&#xff1a;CREATE TABLE example_dataset (id int(11) unsigned NOT NULL AUTO_INCREMENT,tel bigint(11) DEFAULT NULL,gender varchar(11) DEFAULT NULL,PRIMARY KEY (id)) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4;插入数据INSERT INTO example_da…

Mybatis源码分析之(六)mybatis拦截器(Interceptor)的实现原理

文章目录前言InterceptorChain保存所有的Interceptor创建四大对象都走ConfigurationInterceptorChain增强对象方法Plugin封装动态代理&#xff0c;让你使用Mybatis拦截器更简单Invocation&#xff0c;让我们能在拦截器中使用动态代理类中的invoke方法中的对象调用时序图小结前言…

oauth2 java 获取token_OAuth2 Token 一定要放在请求头中吗?

Token 一定要放在请求头中吗&#xff1f; 答案肯定是否定的&#xff0c;本文将从源码的角度来分享一下 spring security oauth2 的解析过程&#xff0c;及其扩展点的应用场景。Token 解析过程说明当我们使用 spring security oauth2 时, 一般情况下需要把认证中心申请的 token …

java开发原则_java开发中,大家处理异常的原则是什么,是如何处理的?

展开全部最熟悉的陌生人&#xff1a;异常异常的类e5a48de588b63231313335323631343130323136353331333361326365型Throwable— Exception—- RuntimeException— Error需要注意的是&#xff0c;RuntimeException及其子类不需要在方法签名中显示注明异常抛出。例如&#xff1a;v…

java 线程 spring_java中spring里实现多线程

Spring通过任务执行器(TaskExecutor)来实现多线程和并发编程的可使用ThreadPoolTaskExecutor来实现基于线程池的TaskExecutor在实际开发中由于多是异步&#xff0c;所以使用EnableAsync来支持异步任务&#xff0c;且要在Bean的方法中使用Async来声明其是一个异步任务?????…

出现503错误 怎么办

展开全部 出现503错误原因及解决办法 原因&#xff1a;web服务器不能处理HTTP请求&#xff0c;可能是临时超载或者是服务器进行维护。 解决办法&#xff1a;用户需要等待服务器的临时处理。在这种状态下&#xff0c;一些服务器可以简单的拒绝socket连接&#xff0c;否则会发…

java枚举类中字段有没有必要加final____枚举类字段 Field ‘xxx‘ may be ‘final‘

java枚举类中字段有没有必要加final 今天在写一个系统统一返回码的枚举类时候&#xff0c;突然想到一个问题&#xff0c;当不小心手抖给枚举类自动生成了set方法&#xff0c;而恰巧在用的地方不小心用了set方法&#xff0c;从而修改了code值&#xff0c;由于枚举类是天然单例&a…

MySQL数据库索引及失效场景

文章目录1. MySQL索引概述1.1 索引的概念1.2 索引的特点1.3 索引的分类1.4 索引的使用场景2. 索引失效场景2.1 索引失效9种场景2.2 索引失效场景总结3. 索引失效验证3.1 全值匹配3.2 最佳左前缀3.3 索引计算3.4 索引范围&#xff1a;索引列上不能有范围查询3.5 索引覆盖&#x…

getLong java_java.lang.Long.getLong()方法实例

全屏java.lang.Long.getLong(String nm) 方法确定具有指定名称的系统属性的long值。如果没有具有指定名称的属性&#xff0c;如果指定名称为空或null&#xff0c;或者该属性没有正确的数字格式&#xff0c;则返回null。声明以下是java.lang.Long.getLong()方法的声明public sta…

@JsonProperty注解解析

1. 概述 来源: JsonPrpperty是jackson包下的一个注解&#xff0c;详细路径(com.fasterxml.jackson.annotation.JsonProperty;)作用:JsonProperty用在属性上&#xff0c;将属性名称序列化为另一个名称。例子&#xff1a;public class Person{JsonProperty(value "name&qu…

java swing panel问题_关于 Java swing Box 的使用问题

代码import javax.swing.*;import java.awt.*;public class C5Ex1_2 {final static int WIDTH 400;final static int HEIGHT 400;public C5Ex1_2() {JFrame jf new JFrame("program 1");jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);jf.setSize(WIDTH, HEI…

SpringMVC注解@RequestParam全面解析____ 注解@RequestParam如何使用加与不加的区别

SpringMVC注解RequestParam全面解析 在此之前&#xff0c;写项目一直用的是RequestParam&#xff08;value“aa” requiredfalse&#xff09;这个注解&#xff0c;但是并不知道它的意思。现在懂了&#xff0c;特来记录下。 1、可以对传入参数指定参数名 1 RequestParam Stri…

@requestbody和@requestparam到底什么作用

1、什么都不写 GET 可以自动封装为对象模型&#xff0c;没有的数值自动为0值 POST 请求体里面放了数据&#xff0c;但是还是使用了RequestParam里的数据 总结&#xff1a; 在不使用注解的情况下&#xff0c;相当于默认使用了RequestParam里的数据 &#xff08;这种理解是错…

linux mysql学习_Linux学习笔记(MySql操作)

忘记MySql密码&#xff1a;编辑mysql主配置文件 my.cnf 在[mysqld]字段下添加参数 skip-grant重启数据库服务,这样就可以进入数据库不用授权了 mysql -uroot修改相应用户密码 use mysql;update user setpasswordpassword(密码) where userroot;flushprivileges; (刷新)最后…

注解@RequestParam【不添加默认项注解】与@RequestBody的使用场景

一、前言 一直有这么一个疑问&#xff1a;在使用postman工具测试api接口的时候&#xff0c;如何使用 json 字符串传值呢&#xff0c;而不是使用 x-www-form-urlencoded 类型&#xff0c;毕竟通过 key-value 传值是有局限性的。假如我要测试批量插入数据的接口呢&#xff0c;使用…

SpringMVC参数的传递——接收List数组类型的数据

前言 本文主要是记录SpringMVC中当前台传过来数组的时候&#xff0c;如何把前台传过来的数据封装到Controller层方法的形参中。 在了解下面参数如何传递前先记住两个结论&#xff1a; 当Ajax以application/x-www-form-urlencoded编码格式上传数据&#xff0c;必须使用JSON对…

有了 IP 地址,为什么还要用 MAC 地址?

我认为&#xff0c;IP地址和MAC地址可以类比生活中寄快递的过程。 在整个网络中数据被封装成数据报文进行发送&#xff0c;就像我们生活中寄快递时将物品放进包裹中。而数据在路由器之间的跳转也可以看作是不同地区快递小哥对物流的交接。 IP地址 ip地址等价于快递包裹上的…

java运动员最佳配对_运动员最佳配对问题 - osc_y1pyjby5的个人空间 - OSCHINA - 中文开源技术交流社区...

这道题可以看为排列数的一个典型模块一、算法实现题&#xff1a;1、问题描述&#xff1a;羽毛球队有男女运动员各n人&#xff0c;给定2个nn矩阵P和Q。P[i][j]是男运动员i和女运动员j配对组成混合双打的男运动员竞赛优势&#xff1b;Q[i][j]则是女运动员i和男运动员j配合的女运动…

为什么POJO中变量不能用is开头

一、前言 在阿里编码规约中&#xff0c;有一个约定如下 【强制】POJO 类中的任何布尔类型的变量&#xff0c;都不要加 is 前缀&#xff0c;否则部分框架解析会引起序列 化错误。 但为什么类中的field不能用is开头呢&#xff1f;本文将从问题演示、框架源码&#xff08;本文使用…