2024.3.2-玄子Share-Mybatis 八股文面试题(共计:48 道 9000 字)
前言:
- 本文部分面试题来源于网络仅供学习使用,请支持原作
- 部分面试题有修改润色,部分面试题由我(玄子)自写
- 面试题根据难度以及重要性分为【常规】【重点】【必会】
- 如有疑问联系
- 最终更新时间:2024.03.25
文章目录
- 2024.3.2-玄子Share-Mybatis 八股文面试题(共计:48 道 9000 字)
- MyBatis 介绍
- 【常规】什么是 MyBatis
- 【常规】MyBatis 的优点
- 【常规】MyBatis 的缺点
- 【常规】MyBatis 的适用场景
- MyBatis 基础
- 【重点】什么是 ORM
- 【重点】MyBatis 的实现逻辑
- 【必会】Mybatis 中 #{} 和 ${} 的区别
- 【常规】Mybatis 比 IBatis 的改进
- 【常规】MyBatis 和 IBatis 的区别
- 【常规】MyBatis 与 Hibernate 的区别
- 【常规】Mybatis 是半自动 ORM 映射工具吗?它与全自动的区别在哪里
- 【常规】MyBatis 中模糊查询 LIKE 语句该怎么写
- 【重点】MyBatis 如何获取自动生成的(主)键值
- 【常规】Mapper 中如何传递多个参数
- 【必会】MyBatis 如何防止 Sql 注入
- 【常规】Mybatis 一级缓存与二级缓存
- 【常规】MyBatis 缓存的查询顺序
- 【常规】MyBatis 缓存的实现逻辑
- 【重点】Mybatis 一级缓存与二级缓存失效的情况有哪些
- 【常规】Mybatis 如何指定使用哪一种 Executor 执行器
- 【常规】Mybatis中如何执行批处理
- 【常规】Mybatis 如何完成 MySQL 的批量操作
- 【常规】Mybatis 是否支持延迟加载?如果支持它的实现原理是什么
- MyBatis 映射
- 【重点】当实体类中的属性名和表中的字段名不一样如何将查询的结果封装到指定 POJO
- 【常规】MyBatis 的 Mapper 接口调用时有哪些要求
- 【重点】接口绑定有几种实现方式?分别是怎么实现的
- 【常规】Mybatis 是如何将 Sql 执行结果封装为目标对象并返回的?都有哪些映射形式
- 【重点】Xml 映射文件中除了常见的 CRUD 标签之外还有哪些标签
- 【常规】Mybatis 动态 Sql 是做什么的?都有哪些动态 Sql
- 【常规】MyBatis 里面的动态 Sql 是怎么设定的?用什么语法
- 【常规】Mybatis 动态 Sql 的执行原理
- 【常规】Mybatis 是否可以映射 Enum 枚举类
- 【常规】Mybatis 的 Xml 映射文件和 Mybatis 内部数据结构之间的映射关系
- 【重点】MyBatis 实现一对一有几种方式?具体怎么操作的
- 【重点】Mybatis 实现一对多有几种方式?具体是怎么操作的
- 【常规】Mybatis 不同 Xml 映射文件中 CRUD 标签的 ID 是否可以重复
- 【重点】通常每一个 Dao 接口都会写一个 Xml 映射文件与之对应 Dao 接口的工作原理是什么?接口的方法或参数不同时方法能重载吗
- 【常规】Mybatis 映射文件中如果 A 标签通过 Include 引用了 B 标签的内容, B 标签能否定义在 A 标签的后面,还是说必须定义在 A 标签的前面
- MyBatis 其他
- 【常规】Mybatis 的插件运行原理?以及如何编写一个插件
- 【常规】Mybatis 实现分页的方式有哪些
- 【常规】Mybatis 是如何进行分页的?分页插件的原理是什么
- 【常规】Mybatis 都有哪些开源的分页插件
MyBatis 介绍
【常规】什么是 MyBatis
- MyBatis 是一款优秀的持久层框架,它支持自定义 Sql 、存储过程以及高级映射
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录
【常规】MyBatis 的优点
- MyBatis 是易学的持久层框架,小巧并且简单易学
- MyBatis 相当灵活,不会对应用程序或者数据库的现有设计强加任何影响
- MyBatis 把 Sql 语句从 Java 源程序中独立出来,放在单独的 XML 文件中编写,给程序的维护带来了很大便利
- 很好的与各种数据库兼容,因为 MyBatis 使用 JDBC 来连接数据库,所以只要JDBC支持的数据库 MyBatis 都支持
- MyBatis 封装了底层 JDBC API 的调用细节,并能自动将结果集转换成 Java Bean 对象,大大简化了Java 数据库编程的重复工作
- MyBatis 需要程序员自己去编写 Sql 语句,程序员可以结合数据库自身的特点灵活控制 Sql 语句
- 能够实现比 Hibernate 等全自动 ORM 框架更高的查询效率,能够完成复杂查询
- 提供 XML 标签,支持编写动态的 Sql ,满足不同的业务需求
- 提供映射标签,支持对象与数据库的 ORM 字段关系映射
- 能够与 Spring 很好的集成
【常规】MyBatis 的缺点
- Sql 语句的编写工作量较大,对开发人员编写 Sql 的能力有一定的要求
- Sql 语句依赖于数据库,导致数据库不具有好的移植性,不可以随便更换数据库
【常规】MyBatis 的适用场景
- MyBatis 专注于 Sql 自身,是一个足够灵活的 DAO 层解决方案。对性能的要求很高,或者需求变化较多的项目
- 例如 Web 项目,那么 MyBatis 是不二的选择
MyBatis 基础
【重点】什么是 ORM
ORM(Object/Relational Mapping)即对象关系映射,是一种数据持久化技术
它在对象模型和关系型数据库直接建立起对应关系,并且提供一种机制,通过 JavaBean 对象去操作数据库表的数据
MyBatis 通过简单的 XML 或者注解的方式进行配置和原始映射,将实体类和 Sql 语句之间建立映射关系,是一种半自动的 ORM 实现,之所以说是半自动,因为我们要自己写 Sql
###【常规】什么是数据持久化
数据持久化是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称
例如,文件的存储、数据的读取等都是数据持久化操作,数据模型可以是任何数据结构或对象的模型、XML、二进制流等
当我们编写应用程序操作数据库,对表数据进行增删改查的操作的时候就是数据持久化的操作
【重点】MyBatis 的实现逻辑
- 在 MyBatis 的初始化过程中,会生成一个 Configuration 全局配置对象,里面包含了所有初始化过程中生成对象
- 根据 Configuration 创建一个 SqlSessionFactory 对象,用于创建 SqlSession 会话
- 通过 SqlSession 可以获取到 Mapper 接口对应的动态代理对象,去执行数据库的相关操作
- 动态代理对象执行数据库的操作,由 SqlSession 执行相应的方法,在他的内部调用 Executor 执行器去执行数据库的相关操作
- 在 Executor 执行器中,会进行相应的处理,将数据库执行结果返回
【必会】Mybatis 中 #{} 和 ${} 的区别
两者在 MyBatis 中都可以作为 Sql 的参数占位符,在处理方式上不同
#{}
是预编译处理,${}
是字符串替换,使用#{}
可以有效的防止 Sql 注入,提高系统安全性#{}
在解析 Sql 的时候会将其替换成 ? 占位符,然后通过 JDBC 的 PreparedStatement 对象添加参数值,这里会进行预编译处理,可以有效地防止 Sql 注入,提高系统的安全性${}
在 MyBatis 中带有该占位符的 Sql 片段会被解析成动态 Sql 语句,根据入参直接替换掉这个值,然后执行数据库相关操作,存在 Sql 注入 的安全性问题
【常规】Mybatis 比 IBatis 的改进
- 有接口绑定,包括注解绑定 Sql 和 xml 绑定 Sql
- 动态 Sql 由原来的节点配置变成 OGNL 表达式
- 在一对一与一对多的时候引进了 association
- 在一对多的时候引入了collection 节点,不过都是在 resultMap 里面配置
###【常规】MyBatis 与 IBatis 核心处理类
- IBatis 的核心处理类:SqlMapClient
- MyBatis 的核心处理类:SqlSession
【常规】MyBatis 和 IBatis 的区别
- 在 Sql 里面变量命名有原来的
#变量#
变成了#{变量}
,$变量$
变成了${变量}
- 原来在 Sql 节点里面的 class 都换名字叫 type
- 原来的
queryForObject
,queryForList
变成了selectOne
,selectList
- 原来的别名设置在
映射文件里面
放在了核心配置文件里面
【常规】MyBatis 与 Hibernate 的区别
Mybatis 和 Hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要程序员自己编写Sql 语句,不过 mybatis 可以通过 XML 或注解方式灵活配置要运行的 Sql 语句,并将 Java 对象和 Sql 语句映射生成最终执行的 Sql ,最后将 Sql 执行的结果再映射生成 Java 对象
Mybatis 学习门槛低,简单易学,程序员直接编写原生态 Sql ,可严格控制 Sql 执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是 mybatis 无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套 Sql 映射文件,工作量大
Hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate 开发可以节省很多代码,提高效率。但是 Hibernate 的缺点是学习门槛高,要精通门槛更高,而且怎么设计 O/R 映射,在性能和对象模型之间如何权衡,以及怎样用好 Hibernate 需要具有很强的经验和能力才行
总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好
【常规】Mybatis 是半自动 ORM 映射工具吗?它与全自动的区别在哪里
Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
而 Mybatis 在查询关联对象或关联集合对象时,需要手动编写 Sql 来完成,所以,称之为半自动ORM 映射工具
【常规】MyBatis 中模糊查询 LIKE 语句该怎么写
- 在 java 中拼接通配符,通过
#{}
赋值 - 在 Sql 语句中拼接通配符,不安全,会引起Sql 注入
【重点】MyBatis 如何获取自动生成的(主)键值
MySql 有两种方式获取自动生成的键值
- 在
<insert />
标签中添加useGeneratedKeys="true"
与keyProperty="true"
属性
<insert id="insertname" usegeneratedkeys="true" keyproperty="id">
insert into names (name) values (#{name})
</insert>
- 在
<insert />
标签内添加<selectKey />
标签
【常规】Mapper 中如何传递多个参数
- 直接在方法中传递参数,xml 文件用
#{0}
,#{1}
来获取 - 使用
@param
注解,这样可以直接在 xml 文件中通过#{name}
来获取
【必会】MyBatis 如何防止 Sql 注入
Sql 注入大家都不陌生,是一种常见的攻击方式。攻击者在界面的表单信息或 URL 上输入一些奇怪的 Sql 片段(“or ‘1’= ‘1’”)有可能入侵参数检验不足的应用程序。所以,在我们的应用中需要做一些工作,来防备这样的攻击方式。在一些安全性要求很高的应用中(比如银行软件),经常使用将 Sql 语句全部替换为存储过程这样的方式,来防止 Sql 注入,这当然是一种很安全的方式,但我们平时开发中,可能不需要这种死板的方式
然后观看下面的两段代码
<select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap">
select id, username, password
from user
where username = #{username} and
password = #{password}
</select>
<!-- -->
<select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap">
select id, username, password,
from user
where username = ${username} and
password = ${password}
</select>
#
将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号where username=#{username}
- 如果传入的值是
11
则解析成 Sql 时的值为where username="11"
- 如果传入的值是
drop table user
则解析成 Sql 时的值为where username="drop table user"
$
将传入的数据直接显示生成在 Sql 中where username=${username}
- 如果传入的值是
11
则解析成 Sql 时的值为where username=11
- 如果传入的值是
drop table user
则解析成 Sql 时的值为where username=;drop table user
#
方式能够很大程度防止Sql 注入,$
方式无法防止 Sql 注入$
方式一般用于传入数据库对象,例如传入表名- 一般能用
#
的就别用$
,若不得不使用"${xxx}"
这样的参数,要手工地做好过滤工作,来防止 Sql 注入攻击
MyBatis 框架作为一款半自动化的持久层框架,其 Sql 语句都要我们自己手动编写,这个时候当然需要防止 Sql 注入,其实,MyBatis的 Sql 是一个具有输入+输出
的功能,类似于函数的结构,参考上面的两个例子,其中,parameterType 表示了输入的参数类型,resultType 表示了输出的参数类型。
回应上文,如果我们想防止 Sql 注入,理所当然地要在输入参数上下功夫。上面代码中使用 # 的即输入参数在 Sql 中拼接的部分,传入参数后,打印出执行的 Sql 语句,会看到 Sql 是这样的
select id, username, password from user where username=? and password=?
不管输入什么参数,打印出的 Sql 都是这样的。这是因为 MyBatis 启用了预编译功能,在 Sql 执行前,会先将上面的 Sql 发送给数据库进行编译
执行时,直接使用编译好的 Sql 替换占位符?
就可以了。因为 Sql 注入只能对编译过程起作用,所以这样的方式就很好地避免了 Sql 注入的问题
【常规】Mybatis 一级缓存与二级缓存
MyBatis 的缓存分为一级缓存和二级缓存
-
一级缓存
- 是 SqlSession 级别的,通过同一个 SqlSession 查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问,一级缓存是默认开启的
- 是基于 PerpetualCache 的 HashMap 本地缓存,它的声明周期是和 SqlSession 一致的,有多个 SqlSession 或者分布式的环境中数据库操作,可能会出现脏数据
- 当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空
-
二级缓存
-
是 SqlSessionFactory 级别,通过同一个 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存,此后若再次执行相同的查询语句,结果就会从缓存中获取
-
也是基于 PerpetualCache 的 HashMap 本地缓存,不同在于其存储作用域为 Mapper 级别的,如果多个 SqlSession 之间需要共享缓存,则需要使用到二级缓存
-
并且二级缓存可自定义存储源,如 Ehcache
-
默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态)可在它的映射文件中配置
<cache/>
-
对于缓存数据更新机制,当某一个作用域(一级缓存 Session / 二级缓存 Namespaces)进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear
【常规】MyBatis 缓存的查询顺序
MyBatis 缓存的查找顺序为:二级缓存 > 一级缓存 > 数据库
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- 注意 SqlSession 关闭之后,一级缓存中的数据才会写入二级缓存
【常规】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-ref />
来使用同一个 Cache 对象,不过这样太局限了,且缓存的数据仅仅是保存在了本地内存中,对于当前高并发的环境下是无法满足要求的,所以我们通常不使用
MyBatis 的缓存
【重点】Mybatis 一级缓存与二级缓存失效的情况有哪些
一级缓存是 SqlSession 级别的,通过同一个 SqlSession 查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问,一级缓存是默认开启的
-
一级缓存失效的情况
-
使用另一个 SqlSession
-
使用同一个 SqlSession 但是查询条件不同
-
使用同一个 SqlSession 但是两次查询中间执行了任何一次增删改操作
-
使用同一个 SqlSession 但是两次查询中间手动清空了缓存,手动清空缓存的方法是调用 SqlSession 的 clearCache() 方法
-
二级缓存是 SqlSessionFactory 级别,通过同一个 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存,此后若再次执行相同的查询语句,结果就会从缓存中获取
- 二级缓存失效的情况
- 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
###【重点】Mybatis 有哪些 Executor 执行器?它们之间的区别是什么
Mybatis 有四种基本的 Executor 执行器,SimpleExecutor、ReuseExecutor、BatchExecutor,CachingExecutor
-
SimpleExecutor(默认):每执行一次数据库的操作
- 就创建一个 Statement 对象,用完立刻关闭 Statement 对象
-
ReuseExecutor:执行数据库的操作
-
以 Sql 作为 key 查找缓存的 Statement 对象,存在就使用,不存在就创建
-
用完后,不关闭 Statement 对象,而是放置于缓存 Map<String, Statement> 内,供下一次使用,就是重复使用 Statement 对象
-
-
BatchExecutor:执行数据库更新操作(没有查询操作,因为 JDBC 批处理不支持查询操作)
-
将所有 Sql 都添加到批处理中。通过 addBatch 方法,等待统一执行使用 executeBatch 方法
-
它缓存了多个 Statement 对象,每个 Statement 对象都是调用 addBatch 方法完毕后,等待一次执行 executeBatch 批处理
-
实际上,整个过程与 JDBC 批处理是相同
-
-
CachingExecutor:在上述的三个执行器之上,增加二级缓存的功能
【常规】Mybatis 如何指定使用哪一种 Executor 执行器
在 Mybatis 配置文件中,可以指定默认的 ExecutorType 执行器类型,也可以手动给 DefaultSqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数
【常规】Mybatis中如何执行批处理
使用 BatchExecutor 完成批处理
BatchExecutor:执行数据库更新操作(没有查询操作,因为 JDBC 批处理不支持查询操作)
-
将所有 Sql 都添加到批处理中。通过 addBatch 方法,等待统一执行使用 executeBatch 方法
-
它缓存了多个 Statement 对象,每个 Statement 对象都是调用 addBatch 方法完毕后,等待一次执行 executeBatch 批处理
-
实际上,整个过程与 JDBC 批处理是相同
【常规】Mybatis 如何完成 MySQL 的批量操作
MyBatis 完成 MySQL 的批量操作主要是通过<foreach>
标签来拼装相应的 Sql 语句
<insert id="insertBatch" >
insert into employee(last_name,email,gender,d_id) values
<foreach collection="emps" item="emp" separator=",">
(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
</foreach>
</insert>
【常规】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,支持延迟加载的原理都是一样的
MyBatis 映射
【重点】当实体类中的属性名和表中的字段名不一样如何将查询的结果封装到指定 POJO
- 通过在查询的 Sql 语句中定义字段名的别名,让字段名的别名和实体类的属性名一致
- 通过
<resultMap>
来映射字段名和实体类属性名的一一对应的关系 - 还有一种特殊情况。大多数场景下,数据库字段名和实体类中的属性名差,主要是前者为
下划线风格
后者为驼峰风格
- 在这种情况下,可以开启 MyBatis 的
mapUnderscoreToCamelCase
配置,实现自动的下划线转驼峰的功能
- 在这种情况下,可以开启 MyBatis 的
###【常规】MyBatis 中 ResultType 与 ResultMap 的区别
ResultMap 与 ResultType 的区别为:对象不同、描述不同、类型适用不同
-
对象不同
-
resultmap:resultMap如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
-
resultType:resultType使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
-
-
描述不同
- resultmap:resultMap对于一对一表连接的处理方式通常为在主表的pojo中添加嵌套另一个表的pojo,然后在mapper.xml中采用association节点元素进行对另一个表的连接处理。
- resulTtype:resultType无法查询结果映射到pojo对象的pojo属性中,根据对结构集查询遍历的需要选择使用resultType还是resultMap。适用于单表查询。
-
类型适用不同
- resultmap:mybatis中在查询进行select映射的时候,返回类型可以用resultType,也可以用resultMap。
- resulttype:resultType是直接表示返回类型的,而resultMap则是对外部ResultMap的引用,但是resultType跟resultMap不能同时存在。
【常规】MyBatis 的 Mapper 接口调用时有哪些要求
- Mapper 接口方法名和 mapper.xml 中定义的每个 Sql 的 id 相同
- Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 Sql 的 parameterType 类型相同
- Mapper 接口方法的输出参数类型和mapper.xml 中定义的每 个Sql 的 resultType 类型相同
- Mapper.xml 文件中的 namespace 即是 mapper 接口的类路径
###【重点】什么是 MyBatis 的接口绑定?有什么好处
- 接口绑定就是在 MyBatis 中任意定义接口,然后把接口里面的方法和 Sql 语句绑定
- 我们直接调用接口方法就可以,这样比起原来了 SqlSession 提供的方法我们可以有更加灵活的选择和设置
【重点】接口绑定有几种实现方式?分别是怎么实现的
接口绑定有两种实现方式
- 注解绑定:就是在接口的方法上面加上
@Select、@Update
等注解,里面使用字符串的形式包含 Sql 语句来绑定 - xml 绑定: 在这种情况下,要指定 xml 映射文件里面的 namespace 为接口的全路径名,使用
select、update
等标签及id
属性等来绑定
###【常规】什么情况下用注解绑定?什么情况下用 Xml 绑定
-
当 Sql 语句比较简单时候,用注解绑定
-
当 Sql 语句比较复杂时候,用 xml 绑定
-
一般用 xml 绑定的比较多
【常规】Mybatis 是如何将 Sql 执行结果封装为目标对象并返回的?都有哪些映射形式
第一种是使用<resultMap>
标签,逐一定义列名和对象属性名之间的映射关系
第二种是使用 Sql 列的别名功能,将列别名书写为对象属性名,比如T_NAME AS NAME
,对象属性名一般是 name 小写,但是列名不区分大小写,Mybatis 会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe
,Mybatis 一样可以正常工作
有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的
【重点】Xml 映射文件中除了常见的 CRUD 标签之外还有哪些标签
- 动态 Sql 的 9 个标签
<if />、<choose />、<when />、<otherwise />、<trim />、<where />、<set />、<foreach />、<bind />
- 还有很多其他的标签
<resultMap>、<parameterMap>、<Sql >、<include>、<selectKey>
- 其中
<Sql >
为 Sql 片段标签,通过<include>
标签引入 Sql 片段 <selectKey>
为不支持自增的主键生成策略标签
- 其中
【常规】Mybatis 动态 Sql 是做什么的?都有哪些动态 Sql
- Mybatis 动态 Sql 可以让我们在 xml 映射文件内,以标签的形式编写动态 Sql ,完成逻辑判断和动态拼接 Sql 的功能
- MyBatis 提供了以下 9 种动态 Sql 标签
<if />、<choose />、<when />、<otherwise />、<trim />、<where />、<set />、<foreach />、<bind />
【常规】MyBatis 里面的动态 Sql 是怎么设定的?用什么语法
MyBatis 里面的动态 Sql 一般是通过 if 节点来实现,通过 OGNL 语法来实现,但是如果要写的完整,必须配合 where、trim 节点
- where 节点是判断包含节点有内容就插入where 否则不插入
- trim 节点是用来判断如果动态语句是以 and 或 or 开始,那么会自动把这个 and 或者 or 取掉
【常规】Mybatis 动态 Sql 的执行原理
- 首先在解析 xml 配置文件的时候,会有一个
SqlSource SqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass)
的操作 - createSqlSource 底层使用了 XMLScriptBuilder 来对 xml 中的标签进行解析
- XMLScriptBuilder 调用了 parseScriptNode() 的方法
- parseScriptNode() 的方法中有一个 parseDynamicTags() 方法,会对 nodeHandlers 里的标签根据不同的 handler 来处理不同的标签
- 然后把 DynamicContext 结果放回 SqlSource 中
- DynamicSqlSource 获取 BoundSql
- Executor 在执行的时候,调用 DynamicSqlSource 的解析方法,并返回解析好的 BoundSql 和已经排好序,需要替换的参数
简单来说:使用 OGNL 从 Sql 参数对象中计算表达式的值,根据表达式的值动态拼接 Sql 以此来完成动态 Sql 的功能
【常规】Mybatis 是否可以映射 Enum 枚举类
Mybatis 可以映射枚举类,不单可以映射枚举类,Mybatis 可以映射任何对象到表的一列上
映射方式为自定义一个 TypeHandler 实现 TypeHandler 的 setParameter() 和 getResult() 接口方法
-
TypeHandler 有两个作用
-
一是完成从 javaType 至 jdbcType 的转换
-
二是完成 jdbcType 至 javaType 的转换
-
体现为 setParameter() 和 getResult() 两个方法
-
分别代表设置 Sql 问号占位符参数和获取列查询结果
-
【常规】Mybatis 的 Xml 映射文件和 Mybatis 内部数据结构之间的映射关系
Mybatis 将所有 xml 配置信息都封装到 All-In-One 重量级对象 Configuration 内部
-
在 Xml 映射文件中
-
<parameterMap>
标签会被解析为 ParameterMap 对象,其每个子元素会被解析为 ParameterMapping 对象 -
<resultMap>
标签会被解析为 ResultMap 对象,其每个子元素会被解析为 ResultMapping 对象 -
<select>、<insert>、<update>、<delete>
标签均会被解析为MappedStatement
对象,标签内的 Sql 会被解析为 BoundSql 对象
-
【重点】MyBatis 实现一对一有几种方式?具体怎么操作的
有联合查询和嵌套查询
-
联合查询是几个表联合查询,只查询一次,通过在 resultMap 里面配置 association 节点配置一对一的类就可以完成
-
嵌套查询是先查一个表,根据这个表里面的结果的外键 id,去再另外一个表里面查询数据,也是通过 association 配置,但另外一个表的查询通过 select 属性配置
【重点】Mybatis 实现一对多有几种方式?具体是怎么操作的
有联合查询和嵌套查询
- 联合查询是几个表联合查询,只查询一次,通过在 resultMap 里面配置 collection 节点配置一对一的类就可以完成
- 嵌套查询是先查一个表,根据这个表里面的结果的外键 id ,去再另外一个表里面查询数据,也是通过 collection 配置,但另外一个表的查询通过select属性配置
【常规】Mybatis 不同 Xml 映射文件中 CRUD 标签的 ID 是否可以重复
不同的 Xml 映射文件
-
如果配置了 namespace,那么 id 可以重复
-
如果没有配置 namespace,那么 id 不能重复
-
毕竟 namespace 不是必须的,只是最佳实践而已
原因就是 namespace+id 是作为 Map<String, MappedStatement> 的 key 使用的
- 如果没有 namespace 就剩下 id 那么 id 重复会导致数据互相覆盖
- 有了 namespace,id 自然就可以重复,namespace 不同,namespace+id 自然也就不同
【重点】通常每一个 Dao 接口都会写一个 Xml 映射文件与之对应 Dao 接口的工作原理是什么?接口的方法或参数不同时方法能重载吗
Dao 接口,就是人们常说的 Mapper 接口
- 接口的全限名,就是映射文件中的 namespace 的值
- 接口的方法名,就是映射文件中 MappedStatement 的 id 值
- 接口方法内的参数,就是传递给 Sql 的参数
Mapper 接口是没有实现类的,当调用接口方法时接口全限名+方法名
拼接字符串作为 key 值,可唯一定位一个 MappedStatement
- 举例
com.mybatis3.mappers.StudentDao.findStudentById
- 可以唯一找到 namespace 为
com.mybatis3.mappers.StudentDao
下面id = findStudentById
的 MappedStatement - Mybatis 中,每一个
<select>、<insert>、<update>、<delete>
标签,都会被解析为一个 MappedStatement 对象
Dao 接口里的方法,是不能重载的,因为是接口全限名+方法名
的保存和寻找策略
- Dao 接口的工作原理是 JDK 动态代理 Mybatis 运行时会使用 JDK 动态代理为 Dao 接口生成代理 proxy 对象
- 代理对象 proxy 会拦截接口方法,转而执行 MappedStatement 所代表的 Sql,然后将 Sql 执行结果返回
【常规】Mybatis 映射文件中如果 A 标签通过 Include 引用了 B 标签的内容, B 标签能否定义在 A 标签的后面,还是说必须定义在 A 标签的前面
虽然 Mybatis 解析 xml 映射文件是按照顺序解析的,但是,被引用的B 标签依然可以定义在任何地方,Mybatis 都可以正确识别
原理是 Mybatis 解析 A 标签,发现 A 标签引用了 B 标签,但是 B 标签尚未解析到,尚不存在
- 此时 Mybatis 会将 A 标签标记为未解析状态,然后继续解析余下的标签,包含 B 标签
- 待所有标签解析完毕 Mybatis 会重新解析那些被标记为未解析的标签
- 此时再解析 A 标签时 B 标签已经存在 A 标签也就可以正常解析完成了
MyBatis 其他
【常规】Mybatis 的插件运行原理?以及如何编写一个插件
Mybatis 仅可以编写针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这4种接口的插件
- Mybatis 通过动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法
- 具体就是 InvocationHandler 的 invoke() 方法,当然只会拦截那些你指定需要拦截的方法
实现 Mybatis 的 Interceptor 接口并复写 intercept() 方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件
【常规】Mybatis 实现分页的方式有哪些
-
物理分页
- 借助 Sql 语句进行分页
- 通过拦截器给 Sql 语句末尾加 imt 语句来查询
-
逻辑分页
- 借助数组进行分页
- 借助 RowBounds 分页插件实现
【常规】Mybatis 是如何进行分页的?分页插件的原理是什么
Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页
可以在 Sql 内直接书写带有物理分页的参数来完成物理分页功能
也可以使用分页插件来完成物理分页
- 分页插件的原理就是使用 MyBatis 提供的插件接口,实现自定义插件
- 在插件的拦截方法内,拦截待执行的 Sql 然后根据设置的dialect(方言)和设置的分页参数,重写 Sql
- 生成带有分页语句的 Sql 执行重写后的 Sql 从而实现分页
select * from student
# 拦截 Sql 后重写为
select t.* from (select * from student)t limit 0,10
【常规】Mybatis 都有哪些开源的分页插件
Mybatis 提供了一些开源的分页插件,例如 PageHelper、Mybatis-Plus 等
- 这些插件通常基于拦截器和反射机制实现,具体包括以下步骤
- 创建分页对象(Page)并设置分页参数
- 在查询方法上添加注解或配置文件中添加相应的配置信息,指定要使用的分页插件
- 在拦截器中获取查询 Sql ,并根据分页参数生成新的 Sql 语句
- 使用反射机制将新的 Sql 语句替换原有的 Sql 语句,并设置分页参数
- 调用 Executor 进行查询操作,并将分页结果封装到分页对象中返回给调用方
总体来说 Mybatis 的分页功能和分页插件都是基于拦截器和反射机制实现的
- 通过在查询方法上添加注解或配置信息
- 系统能够自动识别要使用的分页插件
- 并根据分页参数生成新的 Sql 语句
- 从而实现分页功能
2024.3.2-玄子Share-Mybatis 八股文面试题(共计:48 道 9000 字) 2024.03.25