Java面试题之MyBatis
MyBatis 是一个被广泛应用的持久化框架。一个简单的使用示例如下所示,先创建会话工厂,然后从会话工厂中打开会话,通过 class 类型和配置生成 Mapper 接口的代理实现,最后使用 Mapper 进行持久化操作。
架构图
1.MyBatis的优点
简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件就可以了,易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
提供xml标签,支持编写动态sql。
2.MyBatis框架的缺点:
SQL语句的编写工作量较大,尤其是字段多、关联表多时,更是如此,对开发人员编写SQL语句的功底有一定要求。
SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
3.Mapper接口的工作原理是什么?
Mapper接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回
4.Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
第一种是使用标签,逐一定义数据库列名和对象属性名之间的映射关系。
第二种是使用sql列的别名功能,将列的别名书写为对象属性名。
有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
5.Mybatis 是如何进行分页的?分页插件的原理是什么?
Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页。可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。
6.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,支持延迟加载的原理都是一样的。
7.Mybatis 的一级、二级缓存?
1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就 将清空,默认打开一级缓存。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态),可在它的映射文件中配置;
3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。
8.简述 Mybatis 的插件运行原理,以及如何编写一个插件
Mybatis 仅可以编写针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种接口的插件,Mybatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
编写插件:实现 Mybatis 的 Interceptor 接口并复写 intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
9.#{ } 和 ${ } 的区别是什么?
#{ } 是预编译处理当中的一个占位符。MyBatis在处理 #{ } 的时候,会将SQL中的 #{ } 替换成 ? ,然后,再调用PreparedStatement对象的set方法进行赋值,由此来防止SQL注入的问题。使用预编译的优势在于,一次编译,多次运行,省去了边执行边解析优化的过程。
${ } 是单纯的字符串文本替换。MyBatis在处理 ${ } 的时候,只是简单的把 ${ } 替换为变量的值而已,由此会造成SQL注入,带来不必要的风险。
大部分情况下,我们都是使用 #{ } 来处理业务。但是,针对一些特殊的情况,比如 通过一个“变化的字段”做排序等,也可以使用 ${ } 。
10.MyBatis的接口绑定是什么?有哪些绑定方式?
接口绑定,就是在MyBatis中定义接口,然后把接口里面的方法和SQL语句绑定,我们直接调用接口中的方法就可以操作数据库了。这样比起直接使用SqlSession对象提供的原生态的方法,更加灵活与简单。
接口绑定主要有两种实现方式:(1)第一种是通过注解绑定,也就是说 在接口的方法上面加上 @Select、@Update 等注解,注解里面包含SQL语句来进行绑定。这种方式可以省去SQL的 xml 映射文件,对于简单的SQL来说比较适用,后期维护比较困难,平时在业务中基本不怎么使用。(2)第二种是通过在SQL的xml 映射文件里面写SQL来进行绑定, 在这种情况下,要指定 xml 映射文件里面的 namespace 参数必须为接口的全类名。不管是SQL简单还是复杂,xml 文件的方式 都比较简单高效,也是最常用的。
11.在MyBatis中使用Mapper接口开发时有哪些要求?
一般情况下,在日常开发的时候,会遵循一个mapper.xml映射文件对应于一张表的增删改查。
其中,mapper.xml映射文件中的namespace属性,必须要定义为对应的Mapper接口的全类名,以此来标识一个mapper级别的二级缓存。
- Mapper接口中的方法名要和mapper.xml中定义的每个SQL语句的id属性相同。
- Mapper接口中的方法的输入参数类型要和mapper.xml中定义的每个SQL语句的parameterType的类型相同。
- Mapper接口中的方法的输出参数类型要和mapper.xml中定义的每个SQL语句的resultType的类型相同,或者使用resultMap也行。
12.MyBatis中动态SQL的执行原理是什么?
在MyBatis中,动态SQL的执行原理大致可以分为三个阶段:初始化阶段、代理阶段、数据读写阶段,每个阶段都在做不同的事情。
- 初始化阶段:通过 XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder 来解析XML配置文件中的信息 存储到Configuration类中的。
- 代理阶段:首先从Configuration配置类MapperRegistry对象中获取Mapper接口和对应的代理对象工厂信息,然后再利用代理对象工厂MapperProxyFactory创建实际代理类,最后在MapperProxy类中通过MapperMethod类对象内保存的对应方法的信息,以及对应的SQL语句的信息进行分析,最终确定对应的增强方法的调用。
- 数据读写阶段:通过四种Executor调用四种Handler进行数据查询和数据封装返回。
13.MyBatis的工作原理是什么?
13.1 工作原理图解

13.2 MyBatis的执行流程大概可以分为八个步骤,分别是:
- 首先会读取MyBatis的配置文件【mybatis-config.xml】,配置文件中配置了MyBatis的运行时环境信息,例如数据库连接信息等。
- 然后会加载MyBatis的映射文件【xxxMapper.xml】,映射文件也就是SQL映射文件,SQL映射文件中配置了操作数据库的SQL语句,映射文件需要在【mybatis-config.xml】中加载。【mybatis-config.xml】中可以加载多个不同的映射文件,每个映射文件都对应着数据库中的一张表。
- 接着会通过MyBatis的配置文件构造出一个全局的会话工厂对象【SqlSessionFactory】。
- 再通过会话工厂对象来创建会话对象【SqlSession】,会话对象中包含了执行SQL语句的所有方法。
- 在执行SQL语句时,其底层使用的是一个执行器Executor接口来操作数据库,它将根据会话对象【SqlSession】传递过来的参数动态的生成需要执行的SQL语句,同时会负责查询缓存的维护。
- 在执行器【Executor】接口的执行方法中有一个MappedStatement对象,这个对象是对映射关系的封装,包括了输入参数映射和输出结果映射。
- 在输入参数映射中,输入参数可以是普通的基本数据类型,也可以是POJO类型、或者是Map/List等集合类型。输入参数映射的过程,类似于原生JDBC对preparedStatement对象设置参数的过程。
- 在输出结果映射中,输出结果可以是基本数据类型,也可以是POJO类型、或者是Map/List等集合类型。输出结果映射的过程,类似于原生JDBC对结果集做解析的过程。

浙公网安备 33010602011771号