参考JavaGuide面试突击里面的mybatis部分
目录
2、Dao接口的工作原理是什么?Dao 接口里的方法,参数不同时能重载吗?
3、Mybatis是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式?
4、Mybatis 能执行⼀对⼀、⼀对多的关联查询吗?都有哪些实现方式,以及它们之间的区别。
6、Mybatis 都有哪些 Executor 执行器?它们之间的区别是什么?
7、Mybatis 映射文件中,如果 A 标签通过 include 引用了 B 标签的内容,请问,B 标签能否定义在 A 标签的后面,还是说必须定义在 A 标签的前面?
1、mybatis原理 / 核心流程
- mybatis应用程序通过SqlSessionFactoryBuilder从mybatis-config.xml配置文件中构建出SqlSessionFactory。
- 然后,SqlSessionFactory的实例直接开启一个SqlSession。
- 再通过SqlSession实例获得Mapper对象并运行Mapper映射的SQL语句,完成对数据库的CRUD和事务提交,之后关闭SqlSession。
2、Dao接口的工作原理是什么?Dao 接口里的方法,参数不同时能重载吗?
①接口的全限名,就是映射文件中的 namespace的值。接口的方法名,就是映射文件中 MappedStatement 的 id 值。在 Mybatis中,每⼀个 <select> 、 <insert> 、 <update> 、 <delete> 标签,都会被解析为⼀个 MappedStatement 对象。当调用接口方法时,接⼝全限名+⽅法名拼接字符串作为 key 值,可唯⼀定位⼀个 MappedStatement。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.mmall.dao.OrderMapper" >
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
delete from mmall_order
where id = #{id,jdbcType=INTEGER}
</delete>
②不能重载的,因为是全限名+方法名的保存和寻找策略。
Dao 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK 动态代理为 Dao 接口生成代理 proxy 对象,代理对象 proxy 会拦截接口方法,转而执行 MappedStatement 所代表的 sql,然后将 sql 执行结果返回。
Mybatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?
不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置namespace,那么 id 不能重复;毕竟 namespace 不是必须的,只是最佳实践而已。原因就是 namespace+id 是作为 Map<String, MappedStatement> 的 key 使用的,如果没有namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。有了 namespace,⾃然 id 就可以重复,namespace 不同,namespace+id ⾃然也就不同。
3、Mybatis是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式?
- 第⼀种是使用 <resultMap> 标签,逐⼀定义列名和对象属性名之间的映射关系。
- 第⼆种是使用 sql 列的别名功能,将列别名书写为对象属性名,比如 T_NAME AS NAME,对象属性名⼀般是 name,小写,但是列名不区分大小写,Mybatis 会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成 T_NAME AS NaMe,Mybatis ⼀样可以正常工作。有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使用反射给对象的属性逐⼀赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
如果resultType直接写一个pojo类,不按第一种第二种那样设置。就必须开启mybatis设置里的驼峰命名规则,否则匹配不上。
4、Mybatis 能执行⼀对⼀、⼀对多的关联查询吗?都有哪些实现方式,以及它们之间的区别。
一对一:比如订单表关联用户表,查出哪个订单的信息及对应用户。
//sql
<!-- 查询订单,同时包含用户数据 -->
<select id="queryOrderUser" resultType="orderUser">
SELECT
o.id,
o.user_id
userId,
o.number,
o.createtime,
o.note,
u.username,
u.address
FROM
`order` o
LEFT JOIN `user` u ON o.user_id = u.id
</select>
这种情况下我们需要定义出来一个pojo作为返回的resulttype。
一对多:比如用户表关联订单表,查出哪个用户的信息及对应用户的一些订单。
xml:
<resultMap type="user" id="userOrderResultMap">
<id property="id" column="id" />
<result property="username" column="username" />
<result property="birthday" column="birthday" />
<result property="sex" column="sex" />
<result property="address" column="address" />
<!-- 配置一对多的关系 -->
<collection property="orders" javaType="list" ofType="order">
<!-- 配置主键,是关联Order的唯一标识 -->
<id property="id" column="oid" />
<result property="number" column="number" />
<result property="createtime" column="createtime" />
<result property="note" column="note" />
</collection>
</resultMap>
<!-- 一对多关联,查询订单同时查询该用户下的订单 -->
<select id="queryUserOrder" resultMap="userOrderResultMap">
SELECT
u.id,
u.username,
u.birthday,
u.sex,
u.address,
o.id oid,
o.number,
o.createtime,
o.note
FROM
`user` u
LEFT JOIN `order` o ON u.id = o.user_id
</select>
这种我们也要定义一个pojo来接收返回对象,是一个包含用户相关字段以及一个List<Order>字段的类。
同时用resultMap配置好对应字段的关系,把一对多的订单信息配置在collection里。
5、mybatis延迟加载怎么做的?原理是什么?
如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。 所以延迟加载即先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
原理:
Mybatis 仅支持 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() 方法的调用。这就是延迟加载的基本原理。
6、Mybatis 都有哪些 Executor 执行器?它们之间的区别是什么?
Mybatis 有三种基本的 Executor 执行器, SimpleExecutor 、 ReuseExecutor 、 BatchExecutor 。可以在配置文件中指定。
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 ⽣命周期范围内。
7、Mybatis 映射文件中,如果 A 标签通过 include 引用了 B 标签的内容,请问,B 标签能否定义在 A 标签的后面,还是说必须定义在 A 标签的前面?
虽然 Mybatis 解析 Xml 映射⽂件是按照顺序解析的,但是,被引⽤的 B 标签依然可以定义在任何地⽅,Mybatis 都可以正确识别。
原理是,Mybatis 解析 A 标签,发现 A 标签引用了 B 标签,但是 B 标签尚未解析到,尚不存在,此时,Mybatis 会将 A 标签标记为未解析状态,然后继续解析余下的标签,包含 B 标签,待所有标签解析完毕,Mybatis 会重新解析那些被标记为未解析的标签,此时再解析 A 标签时,B标签已经存在,A 标签也就可以正常解析完成了。
8、mybatis的一级缓存和二级缓存
- 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
- 二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
mybatis中的二级缓存是mapper级别的缓存,值得注意的是,不同的mapper都有一个二级缓存,也就是说,不同的mapper之间的二级缓存是互不影响的。
缓存的执行原理和前面提到的一级缓存是差不多的,二级缓存与一级缓存区别在于二级缓存的范围更大,多个sqlSession可以共享一个mapper中的二级缓存区域。mybatis是如何区分不同mapper的二级缓存区域呢?它是按照不同mapper有不同的namespace来区分的,也就是说,如果两个mapper的namespace相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。