MyBatis 面试题(附答案解析)

MyBatis的实现逻辑

  1. 在 MyBatis 的初始化过程中,会生成一个 Configuration 全局配置对象,里面包含了所有初始化过程中生成对象
  2. 根据 Configuration 创建一个 SqlSessionFactory 对象,用于创建 SqlSession “会话”
  3. 通过 SqlSession 可以获取到 Mapper 接口对应的动态代理对象,去执行数据库的相关操作
  4. 动态代理对象执行数据库的操作,由 SqlSession 执行相应的方法,在他的内部调用 Executor 执行器去执行数据库的相关操作
  5. 在 Executor 执行器中,会进行相应的处理,将数据库执行结果返回

MyBatis的缓存实现逻辑

MyBatis 提供了一级缓存和二级缓存

在 MyBatis 地开启一个 SqlSession 会话时,都会创建一个 Executor 执行器对象

  • 一级缓存在 Executor 执行器(SimpleExecutor)中有一个 Cache 对象中,默认就是一个 HashMap 存储缓存数据,执行数据库查询操作前,如果在一级缓存中有对应的缓存数据,则直接返回,不会去访问数据库默认的缓存区域为SESSION,表示开启一级缓存,可以设置为STATEMENT,执行完查询后会清空一级缓存,所有的数据库更新操作也会清空一级缓存缺陷:在多个 SqlSession 会话时,可能导致数据的不一致性,某一个 SqlSession 更新了数据而其他 SqlSession 无法获取到更新后的数据,出现数据不一致性,这种情况是不允许出现了,所以我们通常选择“关闭”一级缓存
  • 二级缓存在 Executor 执行器(CachingExecutor)中有一个 TransactionalCacheManager 对象中,可以在一定程度上解决的一级缓存中多个 SqlSession 会话可能会导致数据不一致的问题,就是将一个 XML 映射文件中定义的缓存对象放在全局对象中,对于同一个 Mapper 接口都是使用这个 Cache 对象,不管哪个 SqlSession 都是使用该 Cache 对象执行数据库查询操作前,如果在二级缓存中有对应的缓存数据,则直接返回,没有的话则去一级缓存中获取,如果有对应的缓存数据,则直接返回,不会去访问数据库默认全局开启,需要在每个 XML 映射文件中定义缺陷:对于不同的 XML 映射文件,如果某个的 XML 映射文件修改了相应的数据,其他的 XML 映射文件获取到的缓存数据就可能不是最新的,也出现了脏读的问题,当然你可以所有的 XML 映射文件都通过来使用同一个 Cache 对象,不过这样太局限了,且缓存的数据仅仅是保存在了本地内存中,对于当前高并发的环境下是无法满足要求的,所以我们通常不使用MyBatis的缓存

所以对于 MyBatis 中的缓存,我认为是存在一定的缺陷,无法在正式的生产环境使用,如果需要使用缓存,可以参考我的另一篇文档《JetCache源码分析》

#{} 和 ${} 的区别是什么?

两者在 MyBatis 中都可以作为 SQL 的参数占位符,在处理方式上不同

  • #{}:在解析 SQL 的时候会将其替换成 ? 占位符,然后通过 JDBC 的 PreparedStatement 对象添加参数值,这里会进行预编译处理,可以有效地防止 SQL 注入,提高系统的安全性
  • ${}:在 MyBatis 中带有该占位符的 SQL 片段会被解析成动态 SQL 语句,根据入参直接替换掉这个值,然后执行数据库相关操作,存在 SQL注入 的安全性问题

MyBatis中自定义标签的执行原理

MyBatis 提供了以下几种动态 SQL 的标签:

在 MyBatis 的初始化过程中的解析 SQL 过程中,会将定义的一个 SQL 解析成一个个的 SqlNode 对象,当需要执行数据库查询前,需要根据入参对这些 SqlNode 对象进行解析,使用OGNL表达式计算出结果,然后根据结果拼接对应的 SQL 片段,以此完成动态 SQL 的功能

如何使用可以参考MyBatis官方文档

简述Mapper接口的工作原理

在 MyBatis 的初始化过程中,每个一个 XML 映射文件中的