其实我们有在XML配置文件中配置标签来加载我们的mapper文件。
官网文档给了答案:总共有四种方式()。
前文了解了XML 配置解析器XMLConfigBuilder的parse()方法便是加载配置文件生成一个Configuration对象的入口方法;
上篇了解了通过扫描environments标签如何获取数据源,下面会执行一个mapperElement方法来解析mappers标签,rootevalNode("mappers")返回的是一个value是mappers标签中内容的XNode对象;
进入方法,会有限判断有没有package标签,如何没有则会去获取该子node的三个属性,然后3个if分别处理。
无论是哪种方式最后都会执行MapperBuilderAssistant类中的addMappedStatement方法,之后会将解析的sql信息后封装成的MappedStatement对象放在全局配置类的一个Map属性mappedStatements中。
再贴一下我们的测试demo:
在生成SqlSessionFactory对象后,会调用openSession()。已知在前面执行build方法时把数据源和sql都存储在了全局配置类Configuration中,在该方法中则会从配置类中获取Environment(其中包含数据源信息)、TransactionFactory(事务)、Executor(执行器)来生成一个默认的DefaultSqlSession对象返回。
Executor(执行器)一共分为三种:简单、复用、批量,默认SimpleExecutor。CachingExecutor也实现了Executor接口,严格来说CachingExecutor不是一个真正的实现,它会委托给BaseExecutor去实现。此处不做细讲。
到此我们了解到openSession()方法只是获取到一些信息,生成了一个执行器,还没有开始sql执行流程。
接下来测试demo中继续执行LevelDao dao = sqlSessiongetMapper(LevelDaoclass);
则会调用configurationgetMapper(type, this),继续调用mapperRegistrygetMapper(type, sqlSession),其内部是获取具体Class从MapperRegistry类中的 Map中获取MapperProxyFactory,该MAP中的元素是我们在执行new SqlSessionFactoryBuilder()build(input)方法扫描mapper标签时且是package或class的方式存放进去的;
最后通过SqlSession调用MapperProxyFactory类生成了一个代理对象并返回。
我们调用的List all = daofindAll(); 实际上最后是会调用这个代理对象MapperProxy中的invoke方法
当我们执行到我们自定义的方法时会执行execute方法,这句最终就会执行增删改查了;
再往下一层,就是执行JDBC那一套了,获取链接,执行,得到ResultSet,解析ResultSet映射成JavaBean。
最后总结一下具体流程:
笔者只能说会使用Mybtis,并没有具体研究过源码,站在一个使用者的角度记录解决的问题。
跳过大部分源码,从一个功能点开始入手。
以 Select *** 作为例,研究如何获取经过 Mybatis 中 动态语句 转换后的的 SQL语句 。
我们这里不涉及复杂的过程原理(如:读取配置文件、Mapper代理等( 我也不懂 )),只说明一下具体流程。
发现studentMapper被MapperProxy实现。
好奇的同学肯定会问studentMapper是如何创建MapperProxy实例的呢?
一路跟随瞎点。会发现一个配置类,里面东西很多,目前只看和Mapper有关系。
我们继续下一步
到此关于Mapper的运行过程已经分析完了,下面继续分析SelectOne过程。
selectOne 其实只是 selectList 取第一个元素(这点是没有想到的)。
源码解析,这还是第一次写这类文章,确实这些框架的原理,并没有研究过只是知道一点概念,Mapper动态代理之类的。网上的博客从大方向出发,框架设计、设计模式之类的,对于我这种基础薄弱的人看的云里雾里。我准备从一个一个功能开始初步了解、研究此类框架原理。
参考 >
KeyWords: Mybatis 原理,源码,Mybatis Mapper 接口实现类,代理模式,动态代理,Java动态代理,ProxynewProxyInstance,Mapper 映射,Mapper 实现
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。我们在使用 Mybaits 进行 ,通常只需要定义几个 Mapper 接口,然后在编写一个 xml 文件,我们在配置文件中写好 sql , Mybatis 帮我们完成 Mapper 接口道具体实现的调用。以及将结果映射到 model bean 中。
我们在项目中所编写的众多的 Mapper 类只是一个接口(interface ),根据 Java 的多态性我们知道,可以使用接口接口作为形参,进而在运行时确定具体实现的对象是什么。但是,对于 Mapper 接口,我们并没有编写其实现类!Mybatis是如何找到其实现类,进而完成具体的 CRUD 方法调用的呢?原理何在?
为了弄清楚 Mapper 接口是如何找到实现类的,我们先回忆一下 Mybatis 是怎么使用的,根据实际的例子,进而一点点的去分析。这里的使用指的是Mybatis 单独使用,而不是整合 spring , 因为整合 spring 的话,还需要涉及 Mapper dao 装载到 spring 容器的问题,spring 帮忙创建数据源配置等问题。
通常我们使用 Mybatis 的主要步骤是:
从一段代码看起
上面我们概括了使用 Mybatis 的4个步骤。这4个步骤看起来很简单,但是用代码写出来就很多。我们不妨先记着这4个步骤,再去看代码,会容易点。
在这块代码中,第 1 部分我们使用了 Java 编码的形式来实现 SqlSessionFactory ,也可以使用 xml 。如果使用xml的话,上面的第一部分代码就是这样的:
我们本次的目标是弄清楚 “ Mapper 是如何找到实现类的 ”,我们注意上面代码 3 , 4 的位置:
这里 mapper 可以调用selectBlog(1) 这个方法,说明 mapper 是个对象,因为对象才具有方法行为实现啊。BlogMapper接口是不能实例化的,更没有具体方法实现。我们并没有定义一个类,让它实现BlogMapper接口,而在这里它只是通过调用sessiongetMapper() 所得到的。由此,我们可以推断:肯定是sessiongetMapper() 方法内部产生了BlogMapper的实现类。有什么技术可以根据BlogMapper 接口生成了一个实现类呢?想到这里,对于有动态代理 使用经验的程序员来说,很容易想到,这背后肯定是基于动态代理技术,具体怎么实现的呢?下面我们来根据源码一探究竟。
Mapper 接口的注册
从上面的代码中,我们知道 BlogMapper 接口的实现类是从sessiongetMapper中得来的,大概是基于动态代理技术实现。我们既然能够从SqlSession中得到BlogMapper接口的,那么我们肯定需要先在哪里把它放进去了,然后 SqlSession 才能生成我们想要的代理类啊。上面代码中有这么一行:
跟着这个 addMapper 方法的代码实现是这样的:
我们看到这里 mapper 实际上被添加到 mapperRegissry 中。继续跟进代码:
看到这里我们知道上面所执行的configurationaddMapper(BlogMapperclass); 其实最终被放到了HashMap中,其名为knownMappers ,knowMappers是MapperRegistry 类的一个私有属性,它是一个HashMap 。其Key 为当前Class对象,value 为一个MapperProxyFactory 实例。
这里我们总结一下: 诸如BlogMapper 之类的Mapper接口被添加到了MapperRegistry 中的一个HashMap中。并以 Mapper 接口的 Class 对象作为 Key , 以一个携带Mapper接口作为属性的MapperProxyFactory 实例作为value 。MapperProxyFacory从名字来看,好像是一个工厂,用来创建Mapper Proxy的工厂。我们继续往下看。
Mapper接口的动态代理类的生成
上面我们已经知道,Mapper 接口被到注册到了MapperRegistry中——放在其名为knowMappers 的HashMap属性中,我们在调用Mapper接口的方法的时候,是这样的:
这里,我们跟踪一下sessiongetMapper() 方法的代码实现,这里 SqlSession 是一个接口,他有两个实现类,一个是DefaultSqlSession,另外一个是SqlSessionManager,这里我们用的是DefaultSqlSession 为什么是DefaultSqlSession呢?因为我们在初始化SqlSessionFactory的时候所调用的SqlSessionFactoryBuilder的build()方法里边配置的就是DefaultSqlSession, 所以,我们进入到DefaultSession类中,看看它对sessiongetMapper(BlogMapperclass)是怎么实现的:
如代码所示,这里的 getMapper 调用了 configurationgetMapper , 这一步 *** 作其实最终是调用了MapperRegistry,而此前我们已经知道,MapperRegistry是存放了一个HashMap的,我们继续跟踪进去看看,那么这里的get,肯定是从这个hashMap中取数据。我们来看看代码:
我们调用的sessiongetMapper(BlogMapperclass);最终会到达上面这个方法,这个方法,根据BlogMapper的class对象,以它为key在knowMappers 中找到了对应的value —— MapperProxyFactory(BlogMapper) 对象,然后调用这个对象的newInstance()方法。根据这个名字,我们就能猜到这个方法是创建了一个对象,代码是这样的:
看到这里,就清楚了,最终是通过ProxynewProxyInstance产生了一个BlogMapper的代理对象。Mybatis 为了完成 Mapper 接口的实现,运用了代理模式。具体是使用了JDK动态代理,这个ProxynewProxyInstance方法生成代理类的三个要素是:
代理模式中,代理类(MapperProxy)中才真正的完成了方法调用的逻辑。我们贴出MapperProxy的代码,如下:
我们调用的 Blog blog = mapperselectBlog(1); 实际上最后是会调用这个MapperProxy的invoke方法。这段代码中,if 语句先判断,我们想要调用的方法是否来自Object类,这里的意思就是,如果我们调用toString()方法,那么是不需要做代理增强的,直接还调用原来的methodinvoke()就行了。只有调用selectBlog()之类的方法的时候,才执行增强的调用——即mapperMethodexecute(sqlSession, args);这一句代码逻辑。
而mapperMethodexecute(sqlSession, args);这句最终就会执行增删改查了,代码如下:
再往下一层,就是执行JDBC那一套了,获取链接,执行,得到ResultSet,解析ResultSet映射成JavaBean。
至此,我们已经摸清楚了Blog blog = mapperselectBlog(1); 中,BlogMapper接口调用到得到数据库数据过程中,Mybaitis 是如何为接口生成实现类的,以及在哪里出发了最终的CRUD调用。实际上,如果我们在调用Blog blog = mapperselectBlog(1);之前,把从slqSession中得到的 mapper 对象打印出来就会看到,输出大概是这样的:
动态代理没错吧,Java动态代理实在是太美妙了。
上面我们用层层深入的方式摸清楚了 Mapper接口是如何找到实现类的。我们分析了 Mapper接口是如何注册的,Mapper接口是如何产生动态代理对象的,Maper接口方法最终是如何执行的。总结起来主要就是这几个点:
SqlSessionFactoryBuilder 的实例化分两步。第一步 解析配置文件 得到configuration对象,第二步实例化DefaultSqlSessionFactory记录configuration。
sqlSessionFactoryopenSession()这一步就厉害了,完成了事务管理对象实例化、执行器实例化、执行器代理(拦截器链)。到这一步为止完成了defaultSqlSessionFactory会话创建。
sqlSessiongetMapper()是通过从configuration的mapperRegistry中获取mapperclass的MapperProxyFactory对象,再用MapperProxyFactory对象生产出一个MapperProxy的代理对象。到这里完成了对mapper类代理对象的创建,后续调用mapper类的方法时将会由mapperProxy处理。
userMapperqueryUser()真正做事的时候到了。由上文可知mapper类的方法被调用时将会由mapperProxy代替处理,所以第一站就是mapperProxy的invoke()
由下图可知,缓存的位置位于CachingExecutor中的localCache,
而Executor是在openSession时创建,由此可知一级缓存是sqlSesssion级别的。
cacheKey的组成:ms, parameter, rowBounds, boundSql
由下图可知,二级缓存与MappedStatement有关,在MappedStatement中共享一个Cache对象,而MappedStatement位于configuration,所以二级缓存可以跨sqlSession。
有一个 DefaultSqlSession 之后, 还要找到 Mapperxml 里面定义的 Statement ID,才能执行对应的 SQL 语句。
找到 Statement ID 有两种方式:
由于接口名称跟 Mapperxml 的 namespace 是对应的, 接口的方法跟 statement ID 也都是对应的,所以根据方法名就能找到对应的要执行的 SQL。
DefaultSqlSessiongetMapper() 调用了 ConfigurationgetMapper() 。 ConfigurationgetMapper() 又调用了 MapperRegistrygetMapper()
在解析 mapper 标签和 Mapperxml 的时候,已经把接口类型和类型对应的 MapperProxyFactory 放到了一个 Map 中。 获取 Mapper 代理对象, 实际上是从 Map 中获取对应的工厂类后,调用以下方法创建对象:
最终通过代理模式返回代理对象:
JDK 动态代理代理,在实现了 InvocationHandler 的代理类里面,需要传入一个被 代理对象的实现类。
MyBatis 的动态代理不需要实现类的原因:我们只需要根据接口类型+方法的名称, 就可以找到 Statement ID 了,而唯一要做的一件事情也是这件,所以 不需要实现类 。在 MapperProxy 里面直接执行逻辑(也就是执行 SQL)就可以。
获得 Mapper 对象的过程, 实质 上是获取了一个 MapperProxy 的代理对象。 MapperProxy 中有 sqlSession、mapperInterface、methodCache。
在实际开发项目中,我们查询条件不可能很单一,查询字段可能包括很多字段,比如:查询条件可以有用户信息,商品信息,订单信息等。
这里我们的基础实体类是用户类,我在这个基础上扩展他,之后包装他作为我们的查询条件。
包装类:用于parameterType
UserMapper接口
mapper配置文件编写sql:
上图中可看到在mapperxml配置文件中用 userCustomusername 获取父类中的username私有成员变量,实际上是通过 userCustomgetUsername() 方法获得的
以下文章通过一个简单的例子来看看ognl的用法
另还有一篇文章展示了mybatis中的ognl教程
通过以上的了解,现在把User类中的 getUsername 方法注释掉
再次运行程序,得到以下报错:
接着,我们仍旧保留对 getUsername 方法的注释,但是现在把 username 设置为 public
再次运行测试程序,得到以下正常的结果显示
以上就证明了在mapperxml中, userCustomusername 实际上是通过调用了 userCustomgetUsername() 方法获得了user中的username属性,而不是userCustom直接访问父类User中的private修饰的username成员变量。
以上就是关于MyBatis3源码解析-执行SQL流程全部的内容,包括:MyBatis3源码解析-执行SQL流程、Mybatis源码解析(1) 如何获得SQL语句、Mybatis Mapper接口是如何找到实现类的-源码分析等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)