1. 说一说 Mybatis 里面的缓存机制
Mybatis 里面设计的二级缓存是用来提升数据的检索效率,避免每次数据的访问都需要去查询数据库。
一级缓存,是SqlSession 级别的缓存,也叫本地缓存,因为每个用户在执行查询的时候都需要使用SqlSession 来执行,
为了避免每次都去查数据库,Mybatis 把查询出来的数据保存到SqlSession 的本地缓存中,后续的SQL 如果命中缓存,就可以直接从本地缓存读取了。
如果想要实现跨 SqlSession 级别的缓存?那么一级缓存就无法实现了,因此在 Mybatis 里面引入了二级缓存,就是当多个用户
在查询数据的时候,只有有任何一个 SqlSession 拿到了数据就会放入到二级缓存里面,其他的SqlSession 就可以从二级缓存加载数据。
- 一级缓存的具体实现原理是:
在SqlSession 里面持有一个Executor,每个 Executor 中有一个LocalCache 对象。当用户发起查询的时候,Mybatis 会根据执行语句在Local Cache 里面查询,如果没命中,再去查询数据库并写入到 LocalCache,否则直接返回。
所以,以及缓存的生命周期是 SqlSessiion,而且在多个 Sqlsession 或者分布式环境下,可能会导致数据库写操作出现脏数据。 - 二级缓存的具体实现原理是:
使用CachingExecutor 装饰了 Executor,所以在进入一级缓存的查询流程之前,会先通过CachingExecutor 进行二级缓存的查询。
开启二级缓存以后,会被多个 SqlSession 共享,所以它是一个全局缓存。因此它的查询流程是先查二级缓存,再查一级缓存,最后再查数据库。另外,MyBatis 的二级缓存相对于一级缓存来说,实现了 SqlSession 之间缓存数据的共享,同时缓存粒度也能够到 namespace 级别,并且还可以通过 Cache 接口实现类不同的组合,对 Cache 的可控性也更强。
二级缓存的设计思想非常常见,比如 Nacos、Eureka 都用到了
2. Mybatis 中#{}和${}的区别是什么?
首先,Mybatis 提供到的#号占位符和$号占位符,都是实现动态SQL 的一种方式,通过这两种方式把参数传递到 XML 之后,
在执行操作之前,Mybatis 会对这两种占位符进行动态解析。
- #号占位符,等同于jdbc 里面的?号占位符。
它相当于向PreparedStatement 中的预处理语句中设置参数,
而PreparedStatement 中的sql 语句是预编译的,SQL 语句中使用了占位符,规定了 sql 语句的结构。
并且在设置参数的时候,如果有特殊字符,会自动进行转义。所以#号占位符可以防止SQL 注入。
- 而使用$的方式传参,相当于直接把参数拼接到了原始的 SQL 里面,Mybatis不会对它进行特殊处理。
所以$
和#
最大的区别在于,前者是动态参数,后者是占位符, 动态参数无法防止 SQL注入的问题,所以在实际应用中,应该尽可能的使用#号占位符。
另外,$符号的动态传参,可以适合应用在一些动态 SQL 场景中,比如动态传递表名、动态设置排序字段等。
3. Mybatis 是如何进行分页的
数据进行分页是最基础的功能,一般可以把分页分成两类:
- 逻辑分页,先查询出所有的数据缓存到内存,再根据业务相关需求,从内存数据中筛选出合适的数据进行分页。
- 物理分页 ,直接利用数据库支持的分页语法来实现,比如Mysql 里面提供了分页关键词Limit
Mybatis 提供了四种分页方式:
- 在Mybatis Mapper 配置文件里面直接写分页 SQL,这种方式比较灵活,实现也简单。
- RowBounds 实现逻辑分页,也就是一次性加载所有符合查询条件的目标数据,根据分页参数值在内存中实现分页。
当然,在数据量比较大的情况下,JDBC 驱动本身会做一些优化,也就是不会把所有结果存储在ResultSet 里面,
而是只加载一部分数据,再根据需求去数据库里面加载。
这种方式不适合数据量较大的场景,而且有可能会频繁访问数据库造成比较大的压力。 - Interceptor 拦截器实现,通过拦截需要分页的 select 语句,然后在这个sql 语句里面动态拼接分页关键字,从而实现分页查询。
Interceptor 是Mybatis 提供的一种针对不同生命周期的拦截器,比如:- 拦截执行器方法
- 拦截参数的处理
- 拦截结果集的处理
- 拦截SQL 语法构建的处理
这种方式的好处,就是可以提供统一的处理机制,不需要我们再单独去维护分页相关的功能。
- 插件(PageHelper)及(MyBaits-Plus、tkmybatis)框架实现这些插件本质上也是使用Mybatis 的拦截器来实现的。
只是他们帮我们实现了扩展和封装,节省了分页扩展封装的工作量,在实际开发中,只需要拿来即用即可。
总结: 认为有三种方式来实现分页:
- 第一种,直接在 Select 语句上增加数据库提供的分页关键字,然后在应用程序里面传递当前页,以及每页展示条数即可。
- 第二种,使用Mybatis 提供的RowBounds 对象,实现内存级别分页。
- 第三种,基于Mybatis 里面的Interceptor 拦截器,在select 语句执行之前动态拼接分页关键字。