MyBatis 知识点整理

1. #{} 和 ${}

  1. ${} :Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于静态文本替换,比如${driver}会被静态替换为com.mysql.jdbc.Driver。
  2. #{} :sql 的参数占位符,Mybatis 会将 sql 中的#{}替换为?,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的? 号占位符设置参数值,比如 ps.setInt(0, parameterValue),#{item.name} 的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值,相当于 param.getItem().getName()。

2. 常用标签

  1. 增删改查:select、insert、update、delete
  2. 属性相关:resultMap、parameterMap
  3. 动态 sql :if、foreach、choose、when、otherwise、bind
  4. 格式化输出:where、set、trim
  5. 配置关联关系:collection、association
  6. 定义常量与引用:sql、include
  7. 主键策略:selectKey

3. 动态 sql

动态 sql 可以让我们在 XML 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。原理为使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。

4. 结果映射

  1. 使用 resultMap 标签,逐一定义列名和对象属性名之间的映射关系。
  2. 使用 sql 列的别名功能,将列别名书写为对象属性名,比如 T_NAME AS NAME,对象属性名一般是 name,小写,但是列名不区分大小写,MyBatis 会忽略列名大小写。

有了列名与属性名的映射关系后,MyBatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

4.1 映射 Enum 枚举类

MyBatis 不单可以映射枚举类,还可以映射任何对象到表的一列上。映射方式为自定义一个 TypeHandler,实现 TypeHandler 的 setParameter() 和 getResult() 接口方法。

TypeHandler 有两个作用,一是完成从 javaType 至 jdbcType 的转换,二是完成 jdbcType 至 javaType 的转换,体现为 setParameter() 和 getResult() 两个方法,分别代表设置 sql 问号占位符参数和获取列查询结果。

5. Mapper 接口和 XML 映射文件

Mapper 接口的全限名,就是 XML 映射文件中的 namespace 的值;接口的方法名,就是映射文件中 MappedStatement 的 id 值;接口方法内的参数,就是传递给 sql 的参数。

在 MyBatis 初始化时,每一个 select、insert、update、delete 标签,都会被解析为一个 MappedStatement 对象,存在 Map<String, MappedStatement> 中,namespace + id 作为 key 值可唯一定位一个 MappedStatement,因此接口中的方法不能重载。如果没有 namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同。

Mapper 接口是没有实现类的,在调用接口方法时,使用 JDK 动态代理为 Mapper 接口生成代理 proxy 对象,代理对象 proxy 会拦截接口方法,转而执行 MappedStatement 所代表的 sql,然后将 sql 执行结果返回。

6. 插件

MyBatis 仅可以编写针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种接口的插件,MyBatis 使用 JDK 动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke() 方法,当然,只会拦截那些你指定需要拦截的方法。

实现 MyBatis 的 Interceptor 接口并复写 intercept() 方法,然后再给插件编写注解,指定要拦截哪一个接口的哪些方法即可,最后在配置文件中配置编写的插件。

7. 分页

MyBatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页,可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

分页插件的基本原理是使用 MyBatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。
例如:select _ from student,拦截 sql 后重写为 select t._ from (select \* from student) t limit 0, 10

8. 关联查询

MyBatis 不仅可以执行一对一、一对多的关联查询,还可以执行多对一、多对多的关联查询。多对一查询,其实就是一对一查询,只需要把 selectOne() 修改为 selectList() 即可;多对多查询,其实就是一对多查询,只需要把 selectOne() 修改为 selectList() 即可。

实现方式

  1. 单独发送一个 sql 去查询关联对象,赋给主对象,然后返回主对象。
  2. 使用嵌套查询,嵌套查询的含义为使用 join 查询,一部分列是 A 对象的属性值另外一部分列是关联对象 B 的属性值,好处是只发一个 sql 查询,就可以把主对象和其关联对象查出来。

如果 join 查询出来 100 条记录,但主对象只有 5 个。MyBatis 通过 resultMap 标签内的 id 子标签,指定了唯一确定一条记录的 id 列,根据列值来完成 100 条记录的去重复功能。id 标签可以有多个,代表了联合主键的语意。同样主对象的关联对象,也是根据这个原理去重复的,尽管一般情况下,只有主对象会有重复记录,关联对象一般不会重复。

9. 延迟加载

MyBatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,配置 lazyLoadingEnabled=true|false 是否启用延迟加载。

原理
使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke() 方法发现 a.getB() 是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。

10. Executor

MyBatis 有三种基本的 Executor 执行器,都严格限制在 SqlSession 生命周期范围内。

  1. SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。
  2. ReuseExecutor:重复使用 Statement 对象。执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map<String, Statement>内,供下一次使用。
  3. BatchExecutor:执行 update(JDBC 批处理不支持 select),将所有 sql 都添加到批处理中,等待统一执行,它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch() 完毕后,等待逐一执行 executeBatch()批处理。

JDBC 批处理

  1. Statement:可以向数据库发送不同类型的 sql 语句,但是这些 sql 语句没有进行预编译,执行效率不高且需要列出每一个 sql 语句。
  2. PreparedStatement:只能应用在类型相同参数不同的 sql 语句中,常用于在同一个表中批量插入数据,或批量更新表的数据。

11. 缓存

在应用运行过程中,有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis 提供了缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中级缓存,避免直接对数据库进行查询,提高性能。MyBatis 缓存特性在生产环境中应该进行关闭,单纯作为一个 ORM 框架使用可能更为合适。

11.1 一级缓存

每个 SqlSession 中持有了 Executor,每个 Executor 中有一个 LocalCache。当用户发起查询时,MyBatis 根据当前执行的语句生成MappedStatement,在 LocalCache 中查询。如果缓存命中的话,直接返回结果给用户;如果缓存没有命中的话,查询数据库,结果写入LocalCache,最后返回结果给用户。

配置一级缓存有两个选项,SESSION 或者 STATEMENT,默认是SESSION级别。

  1. SESSION:在一个 MyBatis 会话中执行的所有语句,都会共享这一个缓存。
  2. STATEMENT:缓存只对当前执行的这一个 Statement 有效。

一级缓存的生命周期和 SqlSession 一致,内部只是一个没有容量限定的HashMap。它最大范围是 SqlSession 内部,有多个 SqlSession 或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为 Statement。

一级缓存

11.2 二级缓存

在一级缓存中最大的共享范围就是一个 SqlSession 内部。如果多个 SqlSession 之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询,二级缓存开启后,同一个 namespace 下的所有操作语句,都影响着同一个 Cache,即二级缓存被多个 SqlSession 共享,是一个全局的变量。数据的查询执行的流程就:二级缓存 -> 一级缓存 -> 数据库。

二级缓存相对于一级缓存来说,实现了 SqlSession 之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过 Cache 接口实现类不同的组合,对Cache的可控性也更强。但在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。在分布式环境下,由于默认的 MyBatis Cache 实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis 的 Cache 接口实现,有一定的开发成本,直接使用 Redis 等分布式缓存可能成本更低,安全性也更高。

二级缓存

参考:
MyBatis
使用JDBC进行批处理
聊聊MyBatis缓存机制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值