前言

  开心一刻

    本人幼教老师,冬天戴帽子进教室,被小朋友看到,这时候,有个小家伙对我说:老师你的帽子太丑,赶紧摘了吧。我逗他:那你好好学习,以后给老师买个漂亮的?这孩子想都没想立刻回答:等我赚钱了,带你去韩国整形

简单示例

  我们先来看一个纯粹的mybatis示例(不集成spring等其他框架),代码很简单,结构如下

  完整代码地址:mybatis;mapper层和我们平时说的dao层指的是同一个内容,都是数据库操作的封装,但是在没有集成mybatis时,dao层的接口都是需要我们手动去写其实现类,可在上图中我们却发现:我们并没有手动去实现PersonMapper接口,但工程却能实实在在的查询数据库,获取我们需要的数据,如下图所示

  从上图我们发现,PersonMapper实例是一个代理对象,我们操作的其实是PersonMapper的代理实现;也就是说不用我们手动去实现PersonMapper接口,mybatis会动态生成PersonMapper的代理实例,然后由代理实例完成数据库的操作

  那么问题来了,mybatis是何时、何地、如何生成mapper代理实例的呢?我们接着往下看

源码分析

  针对上述问题,我们来跟下mybatis源码

  SqlSessionFactory的创建

    XMLConfigBuilder解析Mybatis配置文件(mybatis-config.xml),将配置文件中各个属性解析到Configuration实例中,然后以Configuration实例构建SqlSessionFactory(实际是DefaultSqlSessionFactory);其中parseConfiguration方法是解析的具体过程,有兴趣的可以更深一步的去探究

/**
* root是以configuration标签开始的文档树
* 解析配置文件中的各个标签,并存放到Configuration实例对应的属性中
* 解析完成之后,配置文件中的内容全部解析到了Configuration实例中
* @param root
*/
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties")); // 解析配置文件中的properties标签
Properties settings = settingsAsProperties(root.evalNode("settings")); // 解析配置文件中的settings标签
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases")); // 解析配置文件中的typeAliases标签
pluginElement(root.evalNode("plugins")); // 解析配置文件中的plugins标签
objectFactoryElement(root.evalNode("objectFactory")); // 解析配置文件中的objectFactory标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析配置文件中的objectWrapperFactory标签
reflectorFactoryElement(root.evalNode("reflectorFactory")); // 解析配置文件中的reflectorFactory标签
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments")); // 解析配置文件中的environments标签
databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析配置文件中的databaseIdProvider标签
typeHandlerElement(root.evalNode("typeHandlers")); // 解析配置文件中的typeHandlers标签
mapperElement(root.evalNode("mappers")); // 解析配置文件中的mappers标签
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

    上述代码中的mapperElement(root.evalNode("mappers"));是不是很诱人?与我们的mapper有关系,是不是在这里就生成了mapper的代理实例,还是只是读取了mapper配置文件的内容?暂时还不敢肯定,那么我们跟进去看看

    其中有两个方法值得重点关注下,具体如下,里面的注释可以重点看下,有兴趣的可以更进一步的跟进去

public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper")); // 解析映射文件Person.xml
configuration.addLoadedResource(resource);
bindMapperForNamespace(); // 将mapper与namespace绑定起来; 将PersonMapper接口与MapperProxyFactory关联起来
} parsePendingResultMaps(); // 解析Configuration的incompleteResultMaps到Configuration的resultMaps
parsePendingCacheRefs(); // 解析Configuration的incompleteCacheRefs到Configuration的cacheRefMap
parsePendingStatements(); // 解析Configuration的incompleteStatements到Configuration的mappedStatements
} /**
* context是映射文件:Person.xml的文档树,以mapper标签开始
* 解析映射文件中的各个标签,并存放到MapperBuilderAssistant实例对应的属性中
*/
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace"); // 解析mapper标签的namespace属性
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace); // namespace属性值解析到Configuration的mapperRegistry中
cacheRefElement(context.evalNode("cache-ref")); // 解析cache-ref标签到Configuration的cacheRefMap中
cacheElement(context.evalNode("cache")); // 解析cache标签到Configuration的caches中
parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析parameterMap标签到Configuration的parameterMaps中
resultMapElements(context.evalNodes("/mapper/resultMap")); // 解析resultMap标签到Configuration的resultMaps中
sqlElement(context.evalNodes("/mapper/sql")); // 解析sql标签到XMLMapperBuilder的sqlFragments中
buildStatementFromContext(context.evalNodes("select|insert|update|delete")); // 解析select|insert|update|delete标签到Configuration的mappedStatements中
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}

    此时SqlSessionFactory已经创建,但PersonMapper的代理实例还没有创建;期间准备了很多东西,包括读取配置文件和映射文件的内容,并将其放置到Configuration实例的对应属性中

  SqlSession的创建

    实例化了Transaction(JdbcTransaction)、Executor(SimpleExecutor)和SqlSession(DefaultSqlSession),此时mapper代理实例仍未被创建

  Mapper代理对象的创建

    可以看到,最终还是利用了JDK的动态代理

protected T newInstance(MapperProxy<T> mapperProxy) {
// 利用JDK的动态代理生成mapper的代理实例
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

    生成了mapper的代理实例,后续就可以利用此代理实例进行数据库的操作了

    更多动态代理的信息请查看:设计模式之代理,手动实现动态代理,揭秘原理实现

总结

  1、我们用mytabis操作数据库,有一个固定流程:先创建SqlSessionFactory,然后创建SqlSession,然后再创建获取mapper代理对象,最后利用mapper代理对象完成数据库的操作;一次数据库操作完成后需要关闭SqlSession;

  2、创建SqlSessionFactory实例的过程中,解析mybatis配置文件和映射文件,将内容都存放到Configuration实例的对应属性中;创建SqlSession的过程中,有创建事务Transaction、执行器Executor,以及DefaultSqlSession;Mapper代理对象的创建,利用的是JDK的动态代理,InvocationHandler是MapperProxy,后续Mapper代理对象方法的执行都会先经过MapperProxy的invoke方法;

  3、很多细节没有讲到,但大体流程就是这样;另外提下,实际应用中,mybatis往往不会单独使用,绝大多数都是集成在spring中;关于在spring的集成下,mapper代理对象的创建过程查看:springboot集成下,mybatis的mapper代理对象究竟是如何生成的

Mybatis源码解析 - mapper代理对象的生成,你有想过吗的更多相关文章

  1. Mybatis源码解析-MapperRegistry代理注册mapper接口

    知识储备 SqlsessionFactory-mybatis持久层操作数据的前提,具体的解析是通过SqlSessionFactoryBean生成的,可见>>>Spring mybat ...

  2. Mybatis源码解析(三) —— Mapper代理类的生成

    Mybatis源码解析(三) -- Mapper代理类的生成   在本系列第一篇文章已经讲述过在Mybatis-Spring项目中,是通过 MapperFactoryBean 的 getObject( ...

  3. Mybatis源码解析,一步一步从浅入深(六):映射代理类的获取

    在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们提到了两个问题: 1,为什么在以前的代码流程中从来没有addMapper,而这里却有getMapper? 2,UserDao ...

  4. mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)

    目录 1. 简介 2. 解析 3 StrictMap 3.1 区别HashMap:键必须为String 3.2 区别HashMap:多了成员变量 name 3.3 区别HashMap:key 的处理多 ...

  5. mybatis源码-解析配置文件(四)之配置文件Mapper解析

    在 mybatis源码-解析配置文件(三)之配置文件Configuration解析 中, 讲解了 Configuration 是如何解析的. 其中, mappers作为configuration节点的 ...

  6. Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例

    在Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们看到了XMLConfigBuilder(xml配置解析器)的实例化.而且这个实例化过程在文章:Mybatis源码解析,一步一步从浅 ...

  7. Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析

    在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,envir ...

  8. mybatis源码-解析配置文件(三)之配置文件Configuration解析

    目录 1. 简介 1.1 系列内容 1.2 适合对象 1.3 本文内容 2. 配置文件 2.1 mysql.properties 2.2 mybatis-config.xml 3. Configura ...

  9. Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码

    在文章:Mybatis源码解析,一步一步从浅入深(一):创建准备工程,中我们为了解析mybatis源码创建了一个mybatis的简单工程(源码已上传github,链接在文章末尾),并实现了一个查询功能 ...

随机推荐

  1. 各个模块的刷新js

    // 更新页面中的subgrid function refreshSubGrid(subgridName) { Xrm.Page.ui.controls.get(subgridName).refres ...

  2. ubuntu18.04新体验

    虽然ubuntu18.04LST版本早出来了,但自己原来的ubuntu16.04还可以用,就懒得折腾了. 但最近ubuntu崩了,就想尝尝鲜...结果发现还挺好用的,准确地说,ubuntu是越来越好用 ...

  3. PageHelper分页插件的使用

    大家好!今天写ssm项目实现分页的时候用到pageHelper分页插件,在使用过程中出现了一些错误,因此写篇随笔记录下整个过程 1.背景:在项目的开发的过程中,为了实现所有的功能. 2.目标:实现分页 ...

  4. 【高并发架构】Redis特点及构件模型

    数据结构 redis 相比 memcached 来说,拥有更多的数据结构,能支持更丰富的数据操作.如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择. redis 主要有以下几种数据 ...

  5. Python-常用字符串操作

    name = 'shanbaoliang.exe' print(name.capitalize()) #将字符串首字母大写 print(name.center(50,'-')) #把字符串居中,并用特 ...

  6. FCC(ES6写法) Map the Debris

    返回一个数组,其内容是把原数组中对应元素的平均海拔转换成其对应的轨道周期. 原数组中会包含格式化的对象内容,像这样 {name: 'name', avgAlt: avgAlt}. 思路: 直接使用公式 ...

  7. Mesos源码分析(6): Mesos Master的初始化

      Mesos Master的初始化在src/master/master.cpp中     在Mesos Master的log中,是能看到这一行的.   1.初始化role,并设置weight权重   ...

  8. DCOS实践分享(3):基于Mesos 和 Docker 企业级移动应用实践分享

    2016年1月24日 8:00—19:00 北京万豪酒店(东城区建国门南大街7号) @Container大会是由国内容器社区DockOne组织的专为一线开发者和运维工程师设计的顶级容器技术会议,会议强 ...

  9. 实战经验丨PHP反序列化漏洞总结

    又到了金三银四跳槽季,很多小伙伴都开始为面试做准备,今天小编就给大家分享一个网安常见的面试问题:PHP反序列化漏洞. 虽然PHP反序列化漏洞利用的条件比较苛刻,但是一旦被利用就会产生很严重的后果,所以 ...

  10. 使用Task

    http://www.cnblogs.com/Charltsing/p/taskpoolthread.html task默认对线程的调度是逐步增加的,连续多次运行并发线程,会提高占用的线程数,而等若干 ...