写点什么

三天吃透 mybatis 面试八股文

作者:程序员大彬
  • 2023-03-07
    广东
  • 本文字数:4613 字

    阅读完需:约 15 分钟

本文已经收录到 Github 仓库,该仓库包含计算机基础、Java 基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎 star~


Github 地址:https://github.com/Tyson0314/Java-learning



Mybatis 是什么?

  • MyBatis 框架是一个开源的数据持久层框架。

  • 它的内部封装了通过 JDBC 访问数据库的操作,支持普通的 SQL 查询、存储过程和高级映射,几乎消除了所有的 JDBC 代码和参数的手工设置以及结果集的检索。

  • MyBatis 作为持久层框架,其主要思想是将程序中的大量 SQL 语句剥离出来,配置在配置文件当中,实现 SQL 的灵活配置。

  • 这样做的好处是将 SQL 与程序代码分离,可以在不修改代码的情况下,直接在配置文件当中修改 SQL。

ORM 是什么

ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单 Java 对象(POJO)的映射关系的技术。简单的说,ORM 是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。

Mybatis 和 Hibernate 的区别?

主要有以下几点区别:


  1. Hibernate 的开发难度大于 MyBatis,主要由于 Hibernate 比较复杂,庞大,学习周期比较长。

  2. Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而 Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。

  3. 数据库扩展性的区别。Hibernate 与数据库具体的关联在 XML 中,所以 HQL 对具体是用什么数据库并不是很关心。MyBatis 由于所有 sql 都是依赖数据库书写的,所以扩展性、迁移性比较差。

  4. 缓存机制的区别。Hibernate 的二级缓存配置在 SessionFactory 生成配置文件中进行详细配置,然后再在具体的表对象映射中配置那种缓存。MyBatis 的二级缓存配置都是在每个具体的表对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓冲机制,并且 MyBatis 可以在命名空间中共享相同的缓存配置和实例,通过 Cache-ref 来实现。

  5. 日志系统完善性的区别。Hibernate 日志系统非常健全,涉及广泛,而 Mybatis 则除了基本记录功能外,功能薄弱很多。

  6. sql 的优化上,Mybatis 要比 Hibernate 方便很多。由于 Mybatis 的 sql 都是写在 xml 里,因此优化 sql 比 Hibernate 方便很多。而 Hibernate 的 sql 很多都是自动生成的,无法直接维护 sql;总之写 sql 的灵活度上 Hibernate 不及 Mybatis。

MyBatis 框架的优缺点及其适用的场合

优点


  1. 与 JDBC 相比,减少了 50%以上的代码量。

  2. MyBatis 是易学的持久层框架,小巧并且简单易学。

  3. MyBatis 相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL 写在 XML 文件里,从程序代码中彻底分离,降低耦合度,便于统一的管理和优化,并可重用。

  4. 提供 XML 标签,支持编写动态的 SQL,满足不同的业务需求。

  5. 提供映射标签,支持对象与数据库的 ORM 字段关系映射。


缺点


  1. SQL 语句的编写工作量较大,对开发人员编写 SQL 的能力有一定的要求。

  2. SQL 语句依赖于数据库,导致数据库不具有好的移植性,不可以随便更换数据库。


适用场景


MyBatis 专注于 SQL 自身,是一个足够灵活的 DAO 层解决方案。对性能的要求很高,或者需求变化较多的项目,例如 Web 项目,那么 MyBatis 是不二的选择。

Mybatis 的工作原理

  • 读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。

  • 加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。

  • 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。

  • 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。

  • Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。

  • MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。

  • 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。

  • 输出结果映射:输出结果类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

Mybatis 都有哪些 Executor 执行器?它们之间的区别是什么?

Mybatis 有三种基本的 Executor 执行器,SimpleExecutorReuseExecutorBatchExecutor


SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。


ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map<String, Statement>内,供下一次使用。简言之,就是重复使用 Statement 对象。


BatchExecutor:执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行 executeBatch()批处理。与 JDBC 批处理相同。


作用范围:Executor 的这些特点,都严格限制在 SqlSession 生命周期范围内。

MyBatis 中接口绑定有几种实现方式?

  1. 通过注解绑定,在接口的方法上面加上 @Select@Update 等注解里面包含 Sql 语句来绑定(SQL 语句比较简单的时候,推荐注解绑定)

  2. 通过 xml 里面写 SQL 来绑定, 指定 xml 映射文件里面的 namespace 必须为接口的全路径名(SQL 语句比较复杂的时候,推荐 xml 绑定)

Mybatis 是如何进行分页的?

Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页,先把数据都查出来,然后再做分页。


可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

分页插件的基本原理是什么?

分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql(SQL 拼接 limit),根据 dialect 方言,添加对应的物理分页语句和物理分页参数,用到了技术 JDK 动态代理,用到了责任链设计模式。

简述 Mybatis 的插件运行原理

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

.如何编写一个插件?

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

.Mybatis 是否支持延迟加载?

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()方法的调用。


当然了,不光是 Mybatis,几乎所有的包括 Hibernate,支持延迟加载的原理都是一样的。

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

#{ } 被解析成预编译语句,预编译之后可以直接执行,不需要重新编译 sql。


//sqlMap 中如下的 sql 语句select * from user where name = #{name};//解析成为预编译语句;编译好SQL语句再取值select * from user where name = ?;
复制代码


${ } 仅仅为一个字符串替换,每次执行 sql 之前需要进行编译,存在 sql 注入问题。


select * from user where name = '${name}'//传递的参数为 "ruhua" 时,解析为如下,然后发送数据库服务器进行编译。取值以后再去编译SQL语句。select * from user where name = "ruhua";
复制代码

Mybatis 的预编译

数据库接受到 sql 语句之后,需要词法和语义解析,优化 sql 语句,制定执行计划。这需要花费一些时间。如果一条 sql 语句需要反复执行,每次都进行语法检查和优化,会浪费很多时间。预编译语句就是将 sql 语句中的值用占位符替代,即将sql语句模板化。一次编译、多次运行,省去了解析优化等过程。


mybatis 是通过PreparedStatement和占位符来实现预编译的。


mybatis 底层使用PreparedStatement,默认情况下,将对所有的 sql 进行预编译,将 #{}替换为?,然后将带有占位符?的 sql 模板发送至 mysql 服务器,由服务器对此无参数的 sql 进行编译后,将编译结果缓存,然后直接执行带有真实参数的 sql。


预编译的作用:


  1. 预编译阶段可以优化 sql 的执行。预编译之后的 sql 多数情况下可以直接执行,数据库服务器不需要再次编译,可以提升性能。

  2. 预编译语句对象可以重复利用。把一个 sql 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个 sql,可以直接使用这个缓存的 PreparedState 对象。

  3. 防止 SQL 注入。使用预编译,而其后注入的参数将不会再进行SQL编译。也就是说其后注入进来的参数系统将不会认为它会是一条 SQL 语句,而默认其是一个参数。

一级缓存和二级缓存

缓存:合理使用缓存是优化中最常见的方法之一,将从数据库中查询出来的数据放入缓存中,下次使用时不必从数据库查询,而是直接从缓存中读取,避免频繁操作数据库,减轻数据库的压力,同时提高系统性能。


一级缓存是 SqlSession 级别的缓存:Mybatis 对缓存提供支持,默认情况下只开启一级缓存,一级缓存作用范围为同一个 SqlSession。在 SQL 和参数相同的情况下,我们使用同一个 SqlSession 对象调用同一个 Mapper 方法,往往只会执行一次 SQL。因为在使用 SqlSession 第一次查询后,Mybatis 会将结果放到缓存中,以后再次查询时,如果没有声明需要刷新,并且缓存没超时的情况下,SqlSession 只会取出当前缓存的数据,不会再次发送 SQL 到数据库。若使用不同的 SqlSession,因为不同的 SqlSession 是相互隔离的,不会使用一级缓存。


二级缓存是 mapper 级别的缓存:可以使缓存在各个 SqlSession 之间共享。二级缓存默认不开启,需要在 mybatis-config.xml 开启二级缓存:


<!-- 通知 MyBatis 框架开启二级缓存 --><settings>  <setting name="cacheEnabled" value="true"/></settings>
复制代码


并在相应的 Mapper.xml 文件添加 cache 标签,表示对哪个 mapper 开启缓存:


<cache/>
复制代码


二级缓存要求返回的 POJO 必须是可序列化的,即要求实现 Serializable 接口。


当开启二级缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。




最后给大家分享一个 Github 仓库,上面有大彬整理的 300 多本经典的计算机书籍 PDF,包括 C 语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以 star 一下,下次找书直接在上面搜索,仓库持续更新中~




Github 地址https://github.com/Tyson0314/java-books

用户头像

还未添加个人签名 2023-01-15 加入

非科班转码,拿过几家大厂offer

评论

发布
暂无评论
三天吃透mybatis面试八股文_Java_程序员大彬_InfoQ写作社区