Mybatis入门到源码分析

Valentine 转载请标明出处。

Mybatis介绍以及使用

MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings.MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results.MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records. (摘抄自官网)
MyBatis 是一个持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。

对比JDBC和MyBatis

对比JDBC和MyBatis

对比原生jdbc和使用mybatis,使用mybatis帮我们映射POJO对应数据库的表字段,生成执行的sql语句StatementHandler,设置传参ParamterHandler,设置返回结果ResultSetHandler。Mybatis允许自定义plugin来实现对这四个类的拦截,类似AOP,xxxPlugin implements Interceptor。
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)

使用Mybatis

1、编程式
2、集成式managed 集成到spring
3、工作当中的使用方式,分析业务,定义表结构,generator生成需要的类

generator使用步骤
1.pom.xml 配置generator插件

<plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.3.3</version>
    <configuration>
        <configurationFile>${project.basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
        <overwrite>true</overwrite>
    </configuration>
</plugin>

2.配置 generatorConfig.xml
3.执行 mvn mybatis-generator:generate
4.生成Bean和Example

4、作用域SCOPE 生命周期 ——————scope

SqlSessionFactoryBuilder ——————method

SqlSessionFactiory ——————application

SqlSesssion ——————request/method

Mapper method ——————method

5、Mapper的xml与annotation形式

兼容形式:互补,但是两个同时存在会报错
Mapper.xml
Pros:跟接口分离、统一管理
Cons:过多的xml文件

Annotation
Pros:接口能看到sql可读性高,不需要再去找xml文件,方便
Cons:复杂的联合查询不好维护,代码可读性差

6、Config文件部分解读 http://www.mybatis.org/mybatis-3/configuration.html
  1. Environment


    配置数据库环境
  2. TypeHandler (java和表字段类型的转换实现)

a) 定义com.gupao.dal.typehandlers.TestTypeHandle
b) 注册com.gupao.dal.config.MybatisConfig#localSessionFactoryBean



c) 注册到使用字段上



7、Plugins

拦截范围
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
a) 定义 com.gupao.dal.plugins.TestPlugin



b) 注册 com.gupao.dal.config.MybatisConfig#localSessionFactoryBean



c) 使用

配置文件解读

mapper文件解读

1.namespace
关联到接口方法,区分类似package的作用
2.resultMap/resultType
resultType
Pros:多表关联字段是清楚知道的,性能调优直观 Cros:创建很多实体


resultMap Pros:不需要写join语句 Cros:N+1问题,性能调优不直观

  1. select insert update delete CRUD


动态SQL http://www.mybatis.org/mybatis-3/dynamic-sql.html
缓存

a) 一级缓存 b) 二级缓存
1、为什么要一级缓存?
好处:减少数据库压力
2、怎么验证一级缓存?



3、一级缓存有没有问题?
public void query(){
SqlSession sqlSession;
sqlSession.selectOne();
期间被update下面的数据就是脏数据
sqlSession.selectOne();//命中缓存,内存
}
为什么这样设计?
因为你会这样写代码的概率近乎为0。

Best practice

1.分页
a)逻辑分页 org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForSimpleResultMap 内存里分页
b) 物理分页 i. select … limit 0,10;
分页插件 https://github.com/pagehelper/Mybatis-PageHelper

批量操作Batch

for循环一个一个插入 性能低
foreach拼sql(性能最高,推荐使用) 性能高 有sql长度限制,定好List大小,因为数据库有最大传输packet的限制,就像发送TCP包一样,一个包有最大的限制,show variables like '%packet%';,查出max_allowed_packet本地默认最大传输包4M,slave_max_allowed_packet 从库最大传输包 1G;还有一个就是它会基于网络的buffer,show variables like '%net_buffer%';默认是16K。


mybatis的整个架构


Mybatis的logging模块(这里就挑选了一个比较有意思的模块分析一下)


mybatis的log,把我们一次请求的各个部分都拆分了,ConnectionLogger、PreparedStatementLogger、ResultSetLogger、StatementLogger,各部分有各部分的日志输出,Executor通过动态代理来执行sql的日志输出,如果是isDebugEnabled就这样打日志。


Mybatis实现一个查询的时序图

问题

1、 MyBatis在Spring集成下没有mapper的xml文件会不会报错,为什么?

如果有@Annotation,没有xml不会报错,如果两个都没有就会报错,因为spring解释的时候就把它catch住了。
查看@Select注解,反推org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#loadXmlResource类,里面查看


2、Mapper在spring管理下其实是单例,为什么可以是一个单例?

因为Mapper的主要作用是寻找sql

3、手写Plugin,多个interceptor到底谁先执行?顺序由谁决定的?

sqlSessionFactoryBean.setPlugins(new Interceptor[]{pageInterceptor()});这里setPlugins传入的是数组,查看源码可知道,
org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement是for循环configuration.addInterceptor(plugin);添加插件的,所以是按照添加顺序执行的。

4、怎么验证一级缓存的存在?

使用sqlSessionTemplate.getSqlSessionFactory().openSession(),获取sqlSession来操作

 public void select() {
        SqlSession sqlSession =  sqlSessionTemplate.getSqlSessionFactory().openSession();
        long start = System.currentTimeMillis();
        sqlSession.selectOne("selectSOne");
        System.out.println("cost "+ (System.currentTimeMillis() - start));
        start = System.currentTimeMillis();
        sqlSession.selectOne("selectSOne");
        System.out.println("cost "+ (System.currentTimeMillis() - start));
    }
5、为什么Mybatis的动态代理和正宗的动态代理不一样?

使用动态代理就要实现InvocationHandler接口,ProxyDemoProxy要实现InvocationHandler接口,还要持有ProxyImpl的实例,被代理的是IProxyInterface。
但是mybatis的动态代理不是正宗的动态代理,
被代理的是xxxMapper,动态代理是MapperProxy实现了InvocationHandler接口,MapperProxy是持有mapperInterface的,但是mapperInterface的实现去哪了?
Mybatis的动态代理
org.apache.ibatis.binding.MapperProxy@136aa0c 代理
com.gupaoedu.mybatis.mapper.TestMapper 被代理
正宗的动态代理
com.gupaoedu.mybatis.proxymy.ProxyInterfaceImpl
com.gupaoedu.mybatis.proxymy.ProxyInterfaceImpl@16d8735
至少正宗的长的还是差不多的,那究竟为什么mybatis的不一样呢?MapperProxy为什么可以不需要impl呢?
因为Mapper只需要找到对应的sql就可以了,所以可以不需要impl,而且MapperProxy的MapperInterface是从Configuration传进来的。


6、MyBatis为什么还使用switch...case的语句,还符合开闭原则吗?

对拓展开放,对修改关闭,为什么mybatis里面会存在很多像如下那样的代码,因为这些代码是不变的,增删改查就那几个操作。用策略模式去拓展的情况下,原有测试过的代码是不需要变化的,只需要增加新的代码就能完成功能了,这就是开闭原则。

org.apache.ibatis.binding.MapperMethod.execute() {
switch (command.getType()) {
  case INSERT:...
  case UPDATE:...
  case DELETE:...
  case SELECT:...
  case FLUSH:...
  default:
    throw new BindingException("Unknown execution method for: " + command.getName());
}
}

在接口层面定义动作,在抽象类层面定义共性的方法。基于模板模式,符合开闭原则。为什么CachingExecutor不是抽象类,因为它不会变的,如果它是会变的,就把CachingExecutor变成AbstractCachingExecutor

delegate使用到装饰器模式,何谓装饰器模式?同源下的就是装饰器模式,因为他们都是实现了Executor

7、Mybatis是怎么实现错误日志输出的?

mybatis就使用到了ErrorContext,因为它是ThreadLocal的,而每个sqlSession也是线程级别的,如有某个sql执行异常,即直接通过threadLocal输出,这样做更加方便开发人员排错。
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();

8、MapperProxy的invoke方法里面为什么有第50行这样的代码呢?

if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} 这样一行代码的判断呢?
因为Object有toString()等方法,mapper也有,所以如果mapper运用toString()等方法的时候,就会走这个逻辑。(没有就会报错)

9、 TestMapper 作者为什么要设计这样的形式来做?为什么不是一个class而是一个interface?

1.因为它最终的目的是找到xxxMapper.xml对应的id的sql的;
2.如果有人没有用过mybatis的话,他们可能往xxximpl里面填充jdbc的代码。
3.本来就是做一个代理,而且是一个阉割版的代理,阉割版的代理的话,这个方法也不是太合适。

10、org.apache.ibatis.executor.BaseExecutor#queryFromDatabase 322行这行代码的意义

用来做延迟加载的,如果get出来的value不等于空且不等于这个占位符,嵌套查询的时候的延迟加载就可以加载

org.apache.ibatis.executor.BaseExecutor.DeferredLoad#canLoad
public boolean canLoad() {
  return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
}
org.apache.ibatis.executor.BaseExecutor#deferLoad 延迟加载
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getNestedQueryMappingValue 嵌套查询
org.apache.ibatis.executor.CachingExecutor#deferLoad 延迟加载
11、MyBatis的plugin实现机制

只有初始化这四个类(Executor、ParameterHandler、ResultSetHandler、StatementHandler)的时候才会需要去plugin,每一次构造这四个类的时候都需要plugin。
最终有用到Executor的query方法和ParameterHandler的getParameterObject方法,都会被拦截,加上你写的代码,用作更加完整的日志输出。



org.apache.ibatis.session.Configuration#
newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)



org.apache.ibatis.plugin.InterceptorChain#pluginAll

org.apache.ibatis.plugin.Plugin#wrap

被代理的是Exector但是塞进去的是Plugin,那Plugin必然实现InvocationHandle,最终返回代理对象CachingExecutor注意这里是代理再次被代理,CachingExecutor持有SimpleExecutor委派SimpleExecutor做事。



12、lazy loading 是怎么做到的?
org.apache.ibatis.executor.resultset.DefaultResultSetHandler
#createResultObject(org.apache.ibatis.executor.resultset.ResultSetWrapper,org.apache.ibatis.mapping.ResultMap,
org.apache.ibatis.executor.loader.ResultLoaderMap,java.lang.String)

// 是嵌套查询而且是lazy的情况下,才会懒加载



配置的时候这样配置,传了一个cglib的代理工厂
factory.getConfiguration().setLazyLoadingEnabled(true);
factory.getConfiguration().setAggressiveLazyLoading(false);
factory.getConfiguration().setProxyFactory(new CglibProxyFactory());


13、spring集成Mybatis的原理:

通过 progamming(编程式)形式往managed(集成式)的形式去迁移, programming ---> managed
spring使用SqlSessionFactoryBean帮我们构造SqlSessionFactory,当你每次使用的时候,它都会帮你去做sqlSessionTemplate,然后这个sqlSessionTemplate会帮你去集成,拿到SqlSession

14、Spring怎么集成Mybatis?

两种方式:xml、annotation
annotation形式:





MapperScannerRegistrar 实现 ImportBeanDefinitionRegistrar
spring官方的解释定义一个ImportBeanDefinitionRegistrar的实现类,然后在有@Configuration注解的配置类上使用@Import导入,这样被@MapperScan扫到的Mapper接口就会加载到spring的IOC容器中去。
MapperScannerRegistrar 实现 ImportBeanDefinitionRegistrar,xxxRegistrar符合COC(convention over configuration 约定优于配置)原则。
判断条件就是接口而且是独立的,就会把它扫进BeanDefinitions
org.mybatis.spring.mapper.ClassPathMapperScanner#isCandidateComponent



XML形式:
MapperScannerConfigurer 实现 BeanDefinitionRegistryPostProcessor
也是将bean动态注册到Spring中BeanDefinitionRegistryPostProcessor中定义的postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法 可以让我们实现自定义的注册bean定义的逻辑。
15、Spring集成SqlSession的方式?

mapper 在mybatis里面是方法级别的,在Spring里面是容器级别的。
集成容器的时候还是mapperProxy,变化的是spring里面用的是SqlSessionTemplate,而原生mybatis使用的是DefaultSqlSessiond,不过最终SqlSessionTemplate也是调用DefaultSqlSession。
SqlSessionTemplate的sqlSessionProxy是我们自己拿到的最纯正的SqlSession
代理是用org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor,帮我们做了sqlSession.commit(true)



org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke;



org.mybatis.spring.SqlSessionUtils#getSqlSession(...)

其实在spring里面也是一样的,每次都调用了session = sessionFactory.openSession(executorType),它最后的调用,还是用了DefaultSqlSession,SqlSessionTemplate帮我们做了类似与拦截、AOP、事务等等。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容