最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得

个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybatis源码学习(也就是mybatis-spring这个jar包的相关源码),这边笔记,主要来学习原生mybatis;

还是先用描述一下,原生mybatis从解析xml到执行SQL的一个流程:

1.第一步:首先会通过sqlSessionFactoryBuilder来build一个SQLSessionFactory,在build的过程中,会对mybatis的配置文件、mapper.xml文件进行解析,

1.1将mapper.xml中对应的select/update/delete/insert节点,包装成mappedStatement对象,然后把这个类对象,存方到了一个map中(mappedStatements),key值是namespace的value+select节点的id,value就是mappedStatement;

1.2然后通过反射,根据当前namespace,获取到一个class,将class存到了另外一个map中,knownMappers中,key值是通过反射生成的class对象,value是根据class,生成的MapperProxyFactory对象,这两个map,在执行查询的时候,有用到

2.根据sqlSessionFactoryBuilder生成SqlSessionFactory,再根据sqlSesionFactory,创建sqlSession,这时候,就可以调用slqSession的selectOne();selectList()来执行查询,或者通过sqlSession.getMapper()来获取到接口的代理对象,在执行增删改查sql

下面,我们根据上述的步骤来解析mybatis源码,主要来说如何解析mybatis配置文件

 String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// List<OperChannel> list = sqlSession.selectList("com.springsource.study.mybatis.OperChannelMapper.selectAll");
// OperChannel operChannel = sqlSession.selectOne("com.springsource.study.mybatis.OperChannelMapper.selectAll",1);
// System.out.println(operChannel.toString()); System.out.println("+++++++++++++++++");
OperChannelMapper operChannelMapper = sqlSession.getMapper(OperChannelMapper.class);
System.out.println(operChannelMapper.selectAll(1));

这是我自己写的一个测试类,首先我们从sqlSessionFactoryBuilder().buidl(inputStream)说起:

1.首先会获取到一个XMLConfigBuilder(),然后调用XMLConfigBuilder的parse()方法来解析mybatis的配置文件;需要知道的是,对mybatis配置文件的解析,最后会存放到一个configuration.class中,可以简单理解为和配置文件对应的一个类,在后面,生成的environment、mappedStatements都是configuration的一个属性

 public Configuration parse() {
//在new XMLConfigBuilder的时候,默认置为了false,表示当前xml只能被解析一次
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//将配置文件解析成了configuration对象,在该方法中完成
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
} /**
* @param root
* 原生mybatis在执行的时候,解析mybatis配置文件
* 这里解析的节点,都是配置文件中配置的xml信息
*/
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
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);
// read it after objectFactory and objectWrapperFactory issue #631
//在这里解析environment信息,获取到数据源
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mapper配置信息,将SQL封装成mapperstatement
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

在parseConfiguration里面,主要是对配置文件中各个节点属性来进行解析;我们着重来说mapper节点的解析,其中对environment的解析也提一下吧,在获取数据源的时候,会先获取到<DataSource>节点中type,根据type创建一个DataSourceFactory,然后把DataSource节点中配置的property属性的value和name,存到properties中,然后从dataSourceFactory中获取到一个数据源;

我们来说对mappers的解析:

在mybatis配置文件中,对mapper.xml的配置方式有四种,分别是package、resource、URL、mapperClass,这四种优先级就是写的前后顺序,因为在源码中,是按照这四个顺序来进行解析的

解析的mappers节点之后,会对节点中的每个节点进行遍历,我们本次,只以resource为例:

 private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//在配置文件中,配置mapper.xml文件有四种方式,这里按照优先级记进行解析
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);
//实例化一个mapper解析器
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//解析SQL语句
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.");
}
}
}
}
}

在mapperParse.parse()方法中,会对<mapper>节点进行解析,然后获取到mapper节点对应xml文件中的所有属性

 private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
//这里是解析增删改查语句的
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);
}
}

这个方法就是对mapper.xml文件中各个节点的解析,其中,比较重要的是对增删改查节点的解析:

会获取到当前mapper中所有的增删改查节点,然后再遍历,遍历的时候,会获取到每个节点的所有参数信息,由于这个解析具体参数的篇幅比较长,就不粘贴了,org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode 在这个方法中;

其中需要提的是,以select为例,解析到select节点的所有参数和参数值之后,会把所以参数build成一个mappedStatement对象,然后存放到mappedStatements这个map中,key值就是namespace+id(这里的ID就是select/update...配置的id属性,一般配置成方法名);

把所有的增删改查存到mappedStatements之后,会进行另外一个操作,就是根据namespace,通过反射,创建一个Class,对象,然后把class对象存到另外一个map中,knownMappers

 private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}

put方法就在最后一行代码,configuration.addMapper(boundType);

这里也比较简单,就是new MapperProxyFactory()作为value,class作为key,然后存到map中,后面会用到


截止到这里,对配置文件,SQL的解析就完成了,下面我们来说如何执行SQL

1.List<OperChannel> list = sqlSession.selectList("com.springsource.study.mybatis.OperChannelMapper.selectAll");

我们先说这种方法来请求SQL,这里就是调用DefaultSqlSession 的selectList()方法,

首先会根据如此那终端额全类名+方法名,从mappedStatements这个map中获取到mappedStatement对象,这也就是为什么namespace一定要和接口的包名+类名一直的原因

获取到mappedStatement对象之后,就是先查询缓存,缓存中没有就从数据库查询,数据库查到之后,存到一级缓存中,这里的大致的原理是这样的,后面,会单独写一遍笔记。来记录,如何具体执行SQL的

2.OperChannelMapper operChannelMapper = sqlSession.getMapper(OperChannelMapper.class);

    System.out.println(operChannelMapper.selectAll(1));

这是第二种方式,和第一种方式稍微有些不同

这里的入参是mapperInterface.class,会根据入参,从上面提到的knownMappers中获取到mapperProxyFactory对象,然后再调用mapperProxyFactory.newInstance()方法来生成代理对象

 protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
} public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}

在使用jdk代理生成代理对象时,需要传三个入参,这里重要是mapperproxy,就是实现了invocationHandler接口的实现类,生成了接口的代理对象之后,

在执行selectAll()的时候,由于这时候,是代理对象,所以会执行mapperproxy的invoke方法,

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
} MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
} 在这里,mapperMethod.execute()方法中,会判断当前SQL是select?update?delete?等,然后调用DefaultSqlSession对应的方法;
也就是说,selSession.getMapper()这种方式其实是通过代理对象来执行SQL的; 截止到这里,基本上就是原生mybatis解析、执行的逻辑,后面有新的认识,会继续更新

mybatis源码学习(一) 原生mybatis源码学习的更多相关文章

  1. 【学习转载】MyBatis源码解析——日志记录

    声明:转载自前辈:开心的鱼a1 一 .概述 MyBatis没有提供日志的实现类,需要接入第三方的日志组件,但第三方日志组件都有各自的Log级别,且各不相同,但MyBatis统一提供了trace.deb ...

  2. MyBatis源码分析-IDEA新建MyBatis源码工程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  3. 原生JS研究:学习jquery源码,收集整理常用JS函数

    原生JS研究:学习jquery源码,收集整理常用JS函数: 1. JS获取原生class(getElementsByClass) 转自:http://blog.csdn.net/kongjiea/ar ...

  4. Mybatis 实现批量插入和批量删除源码实例

    Mybatis 实现批量插入数据和批量删除数据 学习内容: 准备工作 1.数据库新建表 2.新建 Maven 项目和设置编译版本及添加依赖 3.新建 db.properties 4.新建 mybati ...

  5. [spring源码学习]二、IOC源码——配置文件读取

    一.环境准备 对于学习源码来讲,拿到一大堆的代码,脑袋里肯定是嗡嗡的,所以从代码实例进行跟踪调试未尝不是一种好的办法,此处,我们准备了一个小例子: package com.zjl; public cl ...

  6. 一起学习jQuery2.0.3源码—1.开篇

    write less,do more jQuery告诉我们:牛逼的代码不仅精简而且高效! 2006年1月由美国人John Resig在纽约的barcamp发布了jQuery,吸引了来自世界各地众多Ja ...

  7. OAuth2学习及DotNetOpenAuth部分源码研究

    OAuth2学习及DotNetOpenAuth部分源码研究 在上篇文章中我研究了OpenId及DotNetOpenAuth的相关应用,这一篇继续研究OAuth2. 一.什么是OAuth2 OAuth是 ...

  8. memcached学习笔记——存储命令源码分析下篇

    上一篇回顾:<memcached学习笔记——存储命令源码分析上篇>通过分析memcached的存储命令源码的过程,了解了memcached如何解析文本命令和mencached的内存管理机制 ...

  9. memcached学习笔记——存储命令源码分析上篇

    原创文章,转载请标明,谢谢. 上一篇分析过memcached的连接模型,了解memcached是如何高效处理客户端连接,这一篇分析memcached源码中的process_update_command ...

随机推荐

  1. JVM(6) 字节码执行引擎

    编译器(javac)将Java源文件(.java文件)编译成Java字节码(.class文件). 类加载器负责加载编译后的字节码,并加载到运行时数据区(Runtime Data Area) 通过类加载 ...

  2. SpringBoot是如何加载配置文件的?

    前言 本文针对版本2.2.0.RELEASE来分析SpringBoot的配置处理源码,通过查看SpringBoot的源码来弄清楚一些常见的问题比如: SpringBoot从哪里开始加载配置文件? Sp ...

  3. 关于ArcGIS的OBJECTID生成策略拙见

    目录 诉求SDEOBJECTIDArcMap编辑重置OBJECTID 诉求 非GIS专业的人员可能很难理解ArcSDE中的表OBJECTID的重要性,要么总想着自己动手去维护,要么就想直接忽略它,导致 ...

  4. 如何往Spark社区做贡献,贡献代码

    随着社区正在努力准备Apache Spark的下一版本3.0,您可能会问自己“我如何参与其中?”.现在的Spark代码已经很庞大,因此很难知道如何开始自己做出贡献.Spark PMC & Co ...

  5. js 面试题解析(一)

    1.call和apply的区别. 当需要传的参数是一个数组时,使用apply更加方便;而使用call时需要将数组展开,将数组中的每一项单独传入. 当需要传入的参数大于3个时,call的性能要略优于ap ...

  6. 微服务SpringCloud之GateWay服务化和过滤器

    Spring Cloud Gateway 提供了一种默认转发的能力,只要将 Spring Cloud Gateway 注册到服务中心,Spring Cloud Gateway 默认就会代理服务中心的所 ...

  7. $color$有色图

    不想看题解的请速撤离 为防被骂灌输题解,撤离缓冲区 这里没字 $Ploya$神题一道,所以我自己做不出来,颓了一部分题解. 由于理(颓题)解不(没)深(脸)中途又拿了$std$对拍(输出中间结果并qj ...

  8. php 全文搜索搜索-讯搜使用

    相信很多朋友遇到过,需要全文搜索的场景,百度了一圈发现了一个xunsearch 首先本地采集了1万篇文章,发现效率还可以. 使用上也很简单,直接上代码 //接收关键词 $xs = new XS('xp ...

  9. Hadoop4-HDFS分布式文件系统原理

    一.简介 1.分布式文件系统钢结构 分布式文件系统由计算机集群中的多个节点构成,这些节点分为两类: 主节点(MasterNode)或者名称节点(NameNode) 从节点(Slave Node)或者数 ...

  10. 痞子衡嵌入式:串行EEPROM接口事实标准及SPI EEPROM简介

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是EEPROM接口标准及SPI EEPROM. 痞子衡之前写过一篇文章 <SLC Parallel NOR简介>,介绍过并行N ...