Mybatis

JDBC操作数据库步骤

注册驱动
创建连接
创建statement
执行SQL
关闭连接

几种较为原始的jdbc封装工具

apache DbUtils
springJdbc => jdbcTemplate
————
简陋,功能少,还不足以称为框架。
解决了:数据源、方法封装、结果集映射;
没解决:SQL硬编码,映射不灵活,不支持缓存

ORM框架

object <=> Relation 对象和关系型数据库的映射,操作对象来实现操作数据库。

hibernate

优点:
自动生成SQL
2级缓存
缺点:
1. 不能操作部分字段
2. 无法自定义SQL,SQL优化困难
3. 不支持动态SQL

mybatis(前身ibatis)

SQL和代码分离
动态SQL
重复SQL提取
2级缓存

参考

业务简单:可以选择hibernate或jpa
SQL灵活或复杂:mybatis
性能要求高:jdbc

mybatis详解

4个核心对象:

SqlSessionFactoryBuilder => 方法局部(一旦创建了 SqlSessionFactory,就不再需要它了)
SqlSessionFactory => 单例,应用级别
SqlSession => 每一次request
Mapper => 方法级别

java类型和jdbc类型的映射:TypeHandlerRegistry

自定义TypeHandler:继承BaseTypeHandler抽象类
使用:
insert/update:javaType => jdbcType

select:jdbcType => javaType

eg:JsonTypeHandler原理


主要就是BaseTypeHandler里的两个方法set/get实现javaType和jdbcType的转换。

ObjectFactory

把数据库记录映射成java对象,需要创建java对象,使用ObjectFactory的create方法(反射),默认是DefaultObjectFactory

8个核心标签

<insert>
<delete>
<update>
<select>
<sql>
<resultMap>
<cache>
<cache-ref>

调用存储过程

动态SQL标签

官网:https://mybatis.org/mybatis-3/zh/dynamic-sql.html

where

set

choose标签

choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满足时,则执行 otherwise 中的sql

SELECT
<include refid="Base_Column_list"></include>
FROM md_control_table_column u
<where>
      <choose>
           <when test="isUpdate !=null ">
              AND u.is_update = #{isUpdate, jdbcType=INTEGER}
          </when>
          <when test="isDelete != null">
              AND u.is_delete = #{isDelete, jdbcType=INTEGER}
          </when>
          <otherwise>
          </otherwise>
      </choose>
      <if test="tableColumnId != null">
          AND table_column_id = #{tableColumnId}
      </if>
</where>

强大的trim

<where>等价于:


<set>等价于:


bind标签

${}和#{}区别

Statement和PrepareStatement区别

  • Statemet
    单次执行开销比PrepareStatement小,但是每条SQL都是从0开始编译和执行;
  select * from user where name = '张三'
  select * from user where name = '李四'
  • PrepareStatement
    预编译的statement,先把SQL发给数据库预处理,也有人叫它jdbc存储过程;一次预编译,后续都会复用;支持batch操作

批量插入

mybatis sql的执行,最终依赖的是Executor执行器(默认SIMPLE)


  • 方式1:java代码for循环insert
  • 方式2:insert into 表名 (id, name, address) values (1, '张三', '北京'), (2, '李四', '上海')...
    注:mysqlmax_allowed_packet默认4M,超长报错。
  • 方式3:借助sqlSession的ExecutorType实现(底层封装jdbc prepareStatement的addBatch)

原生jdbc addBatch:

注:
线性增加批量插入的数据条数,执行时间并非呈线性增长,而是3倍的比例增长。在此做一个大胆的推理,数量如果很大,时间将以指数级增长。所以数据量很大时,应该分多批插入。

嵌套查询

<collection>或<association>标签

  1. 一次性查询
  1. 懒加载查询(默认javassist代理的方式实现)


懒加载查询有N+1的问题:
"1"是说1次主查询,"N"是说N次子查询(主查询查到N条记录)。可以配置成懒加载,即用到某个方法时才调用。

物理翻页和逻辑翻页

  • 物理翻页
    使用数据库语法。mysql-limit,oracle-rowNum,sql server-top
  • 逻辑翻页
    一次性查询所有数据到内存,在内存中根据RowBounds对象的属性完成分页。
    RowBounds使用:只需要在mapper方法形参中加入即可,mapper.xml文件无需改动。
源码片段
代码使用

mybatis流程图

mybatis架构分层

对外暴露使用:接口层(前台)
处理数据库操作:核心层(大厨)
支持工作:基础层(采购,保洁)

mybatis缓存

一级缓存

默认打开。
会话(sqlSession)级别的缓存,缓存对象位置:BaseExecutor的localCache中。
一级缓存是会话隔离的,不会有脏数据。
sqlSession未close前,相同的查询(statement和paramter一样)会触发一级缓存。

示意图
BaseExecutor
测试代码

mybatis和spring整合后,如果有事务,则sqlSession在事务提交后关闭;如果没有事务,则每一次CRUD后都会立即关闭。上图所示:第二次查询会触发一级缓存;如果去除事务注解,则不会触发一级缓存,即查询两次。
注1:spring事务的代理无需通过接口
注2:调试发现目前发票项目是SimpleExecutor,而不是ReuseExecutor

二级缓存(比较鸡肋)

默认不开启。
先走二级缓存,再走一级缓存。
mapper级别缓存,在namespace范围内可以跨会话使用,生命周期同ProxyMapper。
通过装饰者模式,CachingExecutor持有Executor的引用,并使用TransactionalCacheManager tcm对象在namespace范围内存储数据。

原理图
CachingExecutor
Executor继承关系树

抽象类BaseExecutor和子类SimpleExecutor、ReuseExecutor、BatchExecutor是模板设计模式。doQuery、doUpdate就是抽象方法。

SimpleExecutor、ReuseExecutor、BatchExecutor区别:

开启步骤步骤:

全局配置(默认开启) + mapper配置

全局配置
默认全局配置-打开
mapper配置

注:如果<select useCache="false">则会针对该statement关闭二级缓存。<select>标签useCache默认是true

由于insert/update/delete标签flushCache默认是true,所以增删改操作会清空本地和二级缓存。

二级缓存的key由多方面组成:

mappedStatementId => com.xxx.BlobMapper.selectById;
分页条件;
SQL;
参数信息;
注:一级缓存和二级缓存的数据结构一样。

集成redis做第三方缓存

源码分析(300多个类)

分析流程:SqlSessionFactoryBuilder -> SqlSessionFactory -> SqlSession
sqlSession.getMapper() => configuration.getMapper() => mapperProxyFactory.newInstance() => mapperProxy => Proxy.newProxyInstance()
调用mapper接口的方法(eg: getById)时,就是执行mapperProxy对象的invoke()方法

configuration.getMapper()
MapperRegistry中有MapperProxyFactory的Map

SqlSessionFactoryBuilder.build(Reader reader, String environment, Properties properties)

SqlSessionFactoryBuilder加载配置文件,通过XmlConfigBuilder解析配置文件,创建Configuration对象,并把properties、settings、typeAlias、typeHandler、plugins、objectFactory、mappedStatement(增删改查标签)等射入。

SqlSessionFactoryBuilder.build()
parseConfiguration
默认全局配置
XmlBuilder
XmlScriptBuilder

factory.openSession()

创建一个Executor射入SqlSession;
Executor的创建依赖Environment和TransactionFactory;
根据是否开启二级缓存,是否有插件,对Executor进行装饰;

image.png

sqlSession.getMapper(BlobMapper.class)

获得 Mapper 对象的过程,实质上是获取了一个 MapperProxy 的代理对象
mapper对象所有的方法调用,实际都是InvocationHandler的invoke()方法调用

早期,可以通过session直接根据statementId执行SQL,但是硬编码,且编译期不报错,难以维护。所以后来演变出session.getMapper(BlobMapper.class)的方式。
Executor执行器根据configuration中找到的statement,组装好的parameter,rowBounds和resultHandler进行查询。

mybatis代理和jdk动态代理区别

image.png

插件

不改变源代码,通过拦截的方式,改变四大核心对象的行为,比如处理参数,处理 SQL,处理结果。
思考:
不改变源代码,怎么修改原对象的行为,比如,前边改一改,后边改一改。 => 代理
多个插件形成链路,层层拦截,存入InterceptorChain(里边是个List) => 责任链模式
代理对象可以不断被代理。

可以被代理的"四大天王"

  1. Executor => 最顶层的对象。openSession()时创建
  2. StatementHandler =>executor.doQuery()时创建,创建它的过程中会顺便创建下边两个Handler
  3. ParameterHandler
  4. ResultSetHandler

注1:mybatis中并不是所有对象都能被拦截的
注2:只有Executor是openSession()时创建,其他三个都是执行SQL时创建。

开发插件步骤

  1. 自定义拦截器实现Interceptor接口
  • 类上添加注解@Intercepts并指定拦截对象、方法、形参
  • 重写intercept(方法增强)、plugin(生成代理对象)、setProperties(注入配置文件中的插件属性)方法。
  1. 全局配置文件中注册插件(注意顺序)
image.png

分析GitHub的PageHelper

原理

存入分页参数:
PageHelper.startPage(pn, 10); => 将分页参数存入全局变量ThreadLocal对象中
取出分页参数:
List<Employee> emps = employeeService.getAll();
在查询时,如果存在分页,则进行分页。

与spring整合

为啥要和spring整合

事务

源码分析

创建SqlSessionFactory

mybatis-spring包中有个核心类:SqlSessionFactoryBean,用于创建mybatis的SqlSessionFactory,该类实现了InitializingBean接口,在SqlSessionFactoryBean的属性设置完成后会调用afterPropertiesSet()方法,在该方法内部完成全局/mapper配置文件解析,创建Configuration对象。

创建SqlSession

mybatis默认的DefaultSqlSession线程不安全,交由mybatis-spring中的SqlSessionTemplate(单例)。
SqlSessionTemplate中封装了DefaultSqlSession里边的方法,且有一个SqlSessionProxy代理对象(jdk动态代理),在jdbc方法调用时,实际就是调用代理对象的invoke(),而每次事务都创建一个新的DefaultSqlSession对象,由ThreadLocal保证线程安全。
mybatis原生包中有线程安全的SqlSessionManager,它同时实现了 SqlSessionFactory、SqlSession 接口,通过ThreadLocal 容器维护 SqlSession

如何拿到SqlSessionTemplate

继承SqlSessionDaoSupport,其成员变量就是SqlSessionTemplate。Hibernate也是如此,继承HibernateDaoSupport

mapper扫描注册

server层@Autowired一下UserMapper,就可以直接调用接口方法了,原理就是@MapperScan扫描@Repository注解,执行doScan()方法过程中,把mapper接口的原始类型替换成了MapperFactoryBean,而实现了FactoryBean接口的类会执行getObject()方法,内部拿到SqlSessionTemplate并调用getMapper()方法,得到了代理对象MapperProxy。
注:SqlSessionTemplate就是SqlSession

手写Mybatis

原理图

注:Configuration中持有MapperRegistry,MapperRegistry中有MapperProxyFactory的map集合,MapperProxyFactory生成MapperProxy。

总结

  1. 逻辑分页RowBounds
  2. Executor继承体系
  3. 缓存:
    默认开启一级缓存。BaseExecutor的成员变量localCache
    默认关闭二级缓存。CachingExecutor的成员变量tcm:TransactionCacheManager。增删改会清空二级缓存。
  4. openSession方法:
    先创建Executor,然后把Executor注入Session形参。
    Executor的创建依赖TransactionFactory和Configuration,内部层层装饰:缓存,插件
  5. 事务:
    mybatis有两种事务配置:JDBC和managed(交给JBOSS,WegLogic等容器)
    和spring整合后,spring会覆盖mybatis的事务。
  6. 流程
    a. 创建session(内含Executor)
    b. session开启事务,执行SQL(实际Executor执行)
    c. session.getMapper(UserMapper.class)得到mapperProxy,调用其findByXx()方法,实际调用代理对象的invoke(),内部区分增删改查,拼装SQL并映射结果集。
  7. xml builder


  8. 插件
    层层代理,针对参数、SQL、结果集做拦截处理。InterceptorChain(里边是个List) 。
    4大核心对象:Executor,StatementHandler ,ParameterHandler,ResultSetHandler。
    只有Executor是openSession()时创建,其他三个都是执行SQL时创建
  9. spring-mybatis
    核心配置SqlSessionFactoryBean实现InitializingBean接口,在属性设置完之后会执行afterPropertiesSet()方法,该方法内部会构建SqlSessionFactory(内部解析配置)
  10. 创建sqlSession
    mybatis默认的DefaultSqlSession线程不安全,交由mybatis-spring中的SqlSessionTemplate(单例)。
    SqlSessionTemplate中封装了DefaultSqlSession里边的方法,且有一个SqlSessionProxy代理对象(jdk动态代理),在jdbc方法调用时,实际就是调用代理对象的invoke(),而每次事务都创建一个新的DefaultSqlSession对象,由ThreadLocal保证线程安全。
    mybatis原生包中有线程安全的SqlSessionManager,它同时实现了 SqlSessionFactory、SqlSession 接口,通过ThreadLocal 容器维护 SqlSession
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 228,606评论 6 533
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,582评论 3 418
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 176,540评论 0 376
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,028评论 1 314
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,801评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,223评论 1 324
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,294评论 3 442
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,442评论 0 289
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,976评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,800评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,996评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,543评论 5 360
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,233评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,662评论 0 26
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,926评论 1 286
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,702评论 3 392
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,991评论 2 374

推荐阅读更多精彩内容