前言知识汇总
上篇文章中我们已经详细介绍了Mybatis的存储类对象。我们上篇提到了:
Mapper.xml当中的SQL标签都被解析成了一个一个的MappedStatement对象。那么我们当中的SQL是基于什么形式进行封装的呢?
我们要知道,Java当中一切皆对象。MappedStatement当中SQL被封装成了MappedStateMent当中的SqlSource对象。
我们通过sqlSource.getBoundSql()来获取一个BoundSql对象,BoundSQL当中的对象就是对于SQL语句的真实封装。
Cofiguration也好,MappedStatent也好存储的是我们配置文件或者是在注解当中书写的配置信息。它们是一个存储对象。
我们还要有操作类型的对象。
一:操作类型对象
1:Excutor
Excutor是执行器的意思,什么是执行器,执行器就是完成各种操作的对象。它是Mybatis当中处理功能的核心。
增删改和查都在Excutor当中都提供了相应的方法。
我们在不了解Mybatis的时候,我们认为Mybatis的当中我们使用的是SqlSession,但是实际上。真正执行各种操作的是Excutor。
SqlSession仅仅是一个门面,它会调用Excutor完成对应的操作。
我们说Excutor是完成Mybatis中操作对象的核心。他是一个接口,我们从接口实现当中可以看到具体的功能点的
我们基于alt+7可以查看我们所有的方法
提供了两类最为核心的方法update && query,注意我们在编程当中的增删改,统一都叫update,而query完成的是查询的操作。
增删改查不就对应了操作数据库的几种方式嘛,另外还有跟事务相关的方法和以及跟缓存相关的方法,这些就是Mybatis当中的所有的操作了。在我们还没有关注Mybatis源码的时候,我们一直以为是由SqlSession来完成的,但是现在显而易见,完成这些操作的应该是Excutor
A:Excutor功能
1:增删改查
2:事务操作(提交、回滚)
3:缓存操作
B:Excutor为什么是接口
未来开发设计之后,我们拿到一个全新的功能之后,我们不知道怎么设计。我们这里有一个规范,但凡是操作相关的类型,我们都需要设计成接口。就比如我们的SqlSession也是一个接口。
设计成接口也是为了提供一种规范,我们在这种规范下可以有不同的实现,也就是不同的实现类类适配不同的业务场景
C:Excutor三个实现类
BaseExcutor仅仅是一个适配器,仅仅提供了一些基本的功能。真正的核心的实现类最为核心的三个实现类是:
值得注意的是最常用的执行器往往是最简单的这个:SimpleExecutor,完成常规的操作,这个是最核心的Excutor,这要是Mybatis当中内置推荐的执行器。如果我们不更改配置的话,默认使用的就是Configuration当中的简单的Excutor
Configuration三个功能:封装Mybatis-Config.xml,存储MappedStatement,创建其他的核心执行的对象包括Excutor,所以配置肯定也在这个类里边。
ReuseExcutor这是一个复用Excutor,他复用的是Statement。我们说任何对数据库的操作底层都得用JDBC,JDBC中的三大件东西Connection是连接数据库的ResultSet是获取查询结果集的,真正和数据库交互的是Statement。复用Statement在什么条件下会发生呢?
Statement与什么想关呢?Statement是与SQL语句相关的。如果我们的SQL在任何条件下都不会改变的话,我们这样的话就可以一直使用这个Statement对象,减少了对象的创建,保证了性能。
这个SQL语句的话可以是增删改查任何的语句,但是不能有任何的改变,任何的改变的话,就不适用了。
BatchExcutor是一个批处理的执行器,我们知道在JDBC当中就有批处理的操作,如果我们想再JDBC当中仍然有批处理的操作。使用这个BatchExcutor即可。也就是一个Connection处理一坨SQL语句,我们就可以考虑使用这个执行器了。
JDBC当中的批处理是啥意思?
我们最简单的JDBC是获取一个Connection之后,我们直接获取一个Statement对象,然后拿着这个对象和数据库沟通,然后取到结果集,释放连接。但是这样会有一个问题,需要频繁的有连接的创建和关闭。或者是连接池的获取和归还。
我们想到了批处理,也就是在一个连接之上,与数据库进行多语SQL交互,这样的话我们就省略了大量的Connetion的创建和销毁,这就是批处理,大大提高了性能。
站在Mybatis当中如果我们想要使用批处理的话,我们就可以使用BatchExcutor
为什么说IO,连接都是非常真贵的资源?
我们知道,我们搞Java开发我们底层面临的是OS操作系统的资源,在它之上我们构建了一个Java虚拟机的东西。我们写Java程序是运行在虚拟机层面上的东西,然后我们想要在虚拟机当中去建立网络连接、或者是io、或者是线程的时候,这个时候虚拟机就办不到了,需要去沟通操作系统,因为只有操作系统可以进行读取文件,可以去访问互联网,只有操作系统可以操作CPU操作线程、进程。所以,我们知道这些操作被后都有操作系统,都会有这些native的方法的操作,而这些东西都是耗时和占用资源的,所以我们认为这些东西,必须要管,必须要复用,必须要池化。
Excutor来处理和数据库的操作,那么这个是怎么和JDBC建立连接的呢,这就和后面的对象有关系了。
2:StatementHandler
这个是真正与数据库建立联系的对象。他是怎么和数据库建立联系的呢?很简单,他直接与JDBC当中的Statement建立联系。Excutor调用StatementHandler,而StatementHandler当中封装了Statement,这样Excutor就可以真正的与数据进行交互了。
A:作用
StatementHandler是一个接口,他的作用就是分担了Excutor中的增删改查。实现真正的JDBC操作。
是Mybatis封装了JDBC Statement,真正MyBatis进行数据库访问的核心。
这里有一个疑问,为什么不能直接把Excutor省略掉,直接使用StatementHandler这里为什么又包了一层呢?
Excutor当中有三件事:增删改查、事务、缓存。把集中的第一部分操作交给了StatementHandler。这里为什么要使用包装,一个类型包装另外一个类型,是为了符合功能单一原则。
完成对数据库的操作,包括了增删改查,还有批处理,还有获取SQL,还有对于SQL参数的处理,所以他的主要工作就是对于数据库的操作。
B:StatementHandler实现类
这里有很浓重的适配器设计模式的痕迹,会有一个BaseStatementHandler草草的实现一些基础功能。关键的类还是三个:
PreparedStatementHandler、SimpleStatementHandler、CallableStatementHandler这里和JDBC中的Statement是严格对应的。
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { String sql = boundSql.getSql(); statement.execute(sql); return resultSetHandler.<E>handleResultSets(statement); }
3:ParameterHandler
这个的作用就是将Mybatis的参数转换为JDBC的参数,
@Param —> #{} — > ?
后边会有详解…
4:ResultSetHandler
A:作用
这个的作用就是将JDBC当中的查询结果集ResultSet进行了封装。
为什么这里处理结果集对象的入参是Statement?
这是很简单的,在JDBC的操作中,我们必须得有Statement才能获取ResultSet。
在Idea当中如何快速看他的实现类:鼠标放到类名上:Ctrl+Alt+B即可,鼠标翻到方法上,直接Ctrl+Alt+B也可直接到方法。
后边会有详解…
5:TypeHandler
A:作用
他的作用就是类型的梳理,当前我们使用Java程序操作数据库,这里边管的就是数据库类型和Java类型转换的过程。
类型一定是和参数和返回值息息相关的。所以,这个类在两个Handler的下边。
后边会有详解…
二:Mybatis核心对象是如何和SqlSession建立联系的?
我们知道SqlSession会调用Excutor进而调用StatementHandler…来完成具体的操作,那么这个怎么证明呢?他们的联系是怎么进行建立的呢?
上篇文章中我们提到, Mybatis基础开发都会进行这样的操作:
Mybatis开发一开始最基础的构建一定会处理上红框中的代码。将这些进行封装,这些操作最终的目的就是为了获取SqlSession
我们还说过,Mybatis有两种操作数据的方式:直接获取Dao实现类的方式和直接调用SqlSession当中方法的实行去进行调用。第二种的方法是最为底层的方式。第一种方式本质上是第二种开发的封装,基于代理设计模式。最终的本质Mybatis处理S进行开发的方式一定是第二种。我们直接研究第二种代码的调用关系就清楚了,最底层的代码一定是第二种方式,第二种方式才是实实在在Mybatis的方式,第一种的变成方式底层也是由第二种来完成的,我们如何证明呢:
先讲一个看源码时十分有用的操作:
sqlSession.insert(......) -- 想看insert 方法的源码,Ctrl+B,进来发现是接口,Ctrl+Alt+B查看对应的方法的实现。 总结:看方法调用源码是Ctrl+B,看实现类源码是:Ctrl+Alt+B 以上操作前提都得是光标在对应的类、方法名上。 如果想回去怎么办? 回去的快捷键:Ctrl+Alt+左右健 我这发现了一个变量,我想看他的变量定义怎么办?Ctrl+B
有了以上的宝贝知识,我们在看源码的时候,就不会乱崩了。
Mybatis源码当中的核心对象是在SqlSession调用对应功能的时候建立联系。假设我问要做insert操作:
197行去拿了MappedStatement对象,其实就是拿了Mapper.xml里边的某一个insert标签,而我们知道标签中有SQL,MappedStatement当中有BoundSQL对象。
198行,拿着我们的Statemen交给了Excutor的update方法去执行insert操作。默认情况下这里的excutor一定是SimpleExcutor
看到这里应该所有的操作都清晰了。
总结一下:
三:代理的方式是怎么建立联系的?
第一种方式是代理设计模式,那么第一种是怎么通过代理设计模式就把上边这种UserDao.insert(…)这种方式变成了上述的这种编写方式的呢?
这是我们现在要研究明白的核心,我们只要把这块再研究明白了,实际上Mybatis底层运行的核心,我们就都玩明白了。
这里是一种典型的动态代理设计模式,我们如何一眼看出来这里是代理设计模式,代理设计模式是基于接口在为原始类做一个新的实现,或者干脆就搞出来一个新的实现类。我们搞Mybatis的时候基本上没有用过UserDaoImpl自己写,都是生成的他的对象。
这里如何创建一个代理对象,我们如何写呢,我们不实用Mybatis为我们提供的。我们只需要实用我们之前提到的动态代理的开发即可。
类加载器我们借一个就行,接口就是我们定义的接口,InvocationHandler是我们的额外功能的存放的地方。基于这种方式一定可以实现我们的动态代理。