MyBatis框架面试题总结(面试必备)

写在前面:鸽了两天的面试题终于出来了,mybatis的面试题也不少,考研名师唐迟曾说过:学习的进步不再于你学了多少,而在于你做了多少的总结。
最后再次感谢ThinkWon大神的总结。
原文链接:https://blog.csdn.net/ThinkWon/article/details/101292950

文章目录

1. MyBatis简介

1.1 MyBatis是什么?

MyBatis是一款优秀的持久层框架,使用Java编写,它封装了JDBC的很多细节,使开发者能够只关注于SQL语句本身,无需关注注册驱动,创建连接等繁琐功能,它使用了ORM的思想实现了结果集的封装。

1.2 ORM是什么

Object Relational Mapping对象关系映射,是一种解决关系型数据库数据和Java对象(POJO)的映射关系。简单的说就是实体类对象和数据库数据的各种对应关系,如对象的属性字段对应数据库表的字段名称。

1.3 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?

因为MyBatis查询关联对象或关联集合时,需要手动来编写SQL,所以时半自动化ORM映射工具。
而Hibernate框架是属于全自动化的ORM映射关系,查询关联对象或关联集合时不需要手写SQL,可以根据对象模型直接获取,所以是属于全自动的。

1.4 传统JDBC开发存在的问题

  1. 频繁创建数据库连接,释放容易造成系统资源的浪费,影响性能,可以用连接池来解决这一问题,但是JDBC又需要自己来实现连接池。
  2. SQL语句定义,参数设置,结果集处理存在硬编码,实际开发中,可能SQL语句的变化比较大,一旦修改,就要修改Java代码,系统便要重新编译,重新发布,不好维护。
  3. 使用preparedStatement在占有符的传参中存在硬编码,因为SQL语句的where条件不一定,可能多也可能少,修改SQL还需要重新修改Java代码,不易维护。
  4. 结果集中存在重复代码,处理麻烦,如果可以映射成Java对象会比较方便。

1.5 JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?

针对以上的传统JDBC开发存在的问题,MyBatis开发出了解决方案。

  1. 在mybtais的主配置文件中配置数据库连接池,使用连接池管理数据库连接。
  2. 将SQL语句配置在接口的配置文件中,与实际的Java接口代码分离。
  3. MyBatis使用ORM思想自动将Java对象映射到SQL本身语句
  4. MyBatis自动将SQL语句执行结果映射到Java对象。

1.6 Mybatis优缺点

<mark>优点</mark>

1. 基于SQL语句的编程,相当的灵活,不会对应用程序或者数据库现有设计造成任何影响,SQL语句编写在XML中,解除SQL与程序代码的耦合,提供XML标签,便于统一管理,支持编写动态SQL,支持可重用。
2. 与传统的JDBC相比,消除了大量的冗余代码,不需要手动开关数据库连接。
3. 很好的可以与各种数据库兼容MySQL,Oracle..
4. 提供映射标签,支持对象与数据库的ORM字段关系映射,提供对象关系映射标签,支持对象关系组件的维护。
5. 能够和spring很好的集成。

<mark>缺点</mark>

1.SQL语句编写工作量很大,尤其在与字段多,管理表多的情况下,需要开发人员具备一定的SQL功底。
2.SQL语句依赖于数据库,导致数据库移植性差,不能随意换数据库。

1.7 MyBatis框架适用场景

  1. 对性能要求很高的项目,或者说需求变化较多的项目,如互联网电商项目。
  2. MyBatis专注于SQL语句本身,是一个足够灵活的Dao层解决方案。

Hibernate 和 MyBatis 的区别

<mark>相同点</mark>
都是对JDBC的封装,都是持久层框架,都用于dao层的开发
<mark>不同点</mark>

  • 映射关系
1.MyBatis是一个半自动化的ORM映射框架,配置Java对象和SQL语句的执行结果对应关系,多表关联配置简单。
2.Hibernate 是一个全自动化的ORM映射框架,配置Java对象与数据库表的对应关系,多表关联配置复杂
  • SQL优化和移植
1.Hibernate 提供数据库的无关性,即可以切换任意的数据库,即移植性好,但是消耗性能,如果项目需要支持多种数据库,代码开发量少,但是SQL的优化却很复杂。
2. MyBatis需要手动编写SQL,支持动态SQL,处理表生成表名等操作,代码量相对大一点,不支持数据库无关性,即移植性差,但是SQL优化容易。
  • 开发难易程度和学习成本
1. Hibernate 属于重量级的框架,学习成本高,适用于需求相对稳定的中小型项目,如:办公系统。
2. MyBatis 属于轻量级框架,学习成本低,适用于需求变化频繁的大型互联网项目,如互联网大型电商系统。

小结

  • MyBatis 是一个小巧,轻便,高效,简单,直接的半自动化持久层框架。
  • Hibernate 是一个强大,方便,高效,复杂,间接的全自动化持久层框架。

2. MyBatis的解析和运行原理

2.1 MyBatis编程步骤是什么样的?

  1. 加载配置文件创建SqlSessionFactory
  2. 通过SqlSessionFactory创建SqlSession
  3. 通过SqlSessioni执行数据库操作(创建dao层代理对象)
  4. 释放资源

2.2 请说说MyBatis的工作原理

工作原理图如下:

  1. 读取MyBatis主配置文件:里面包含了数据库的基本配置信息,如数据库连接信息。
  2. 加载接口映射文件,从MyBatis主配置文件中加载,一个主配置文件可以配置多个接口映射文件,每个文件对应一张表。
  3. 构建会话工厂SqlSessionFactory:通过配置信息构建SqlSessionFactory会话工厂。
  4. 构建会话对象SqlSession:里面包含了执行SQL的所有方法。
  5. Executor 执行器:MyBatis底层定义了一个Executor 接口操作数据库,它将根据SqlSession传递的参数动态生成需要执行的SQL语句,同时负责查询缓存。
  6. MappedStatement 对象:在Executor 接口执行的方法参数中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id,参数等。
  7. 输入参数映射:输入参数可以是List,Map集合类型,也可以是基本类型或POJO类型,输入参数映射类似于JDBC的preparedStatement设置参数的过程。
  8. 输出参数映射:输出参数可以实List,Map集合类型,也可以是基本类型或POJO类型,输出参数类似于JDBC对结果集的解析过程。

2.3 MyBatis的功能架构是怎样的


Mybatis的功能架构分为三层:

  • API接口层:提供外部使用的接口API,开发人员通过这些API来操作数据库,接口层一般接收调用请求后,就要来调用数据处理层来完成具体的数据处理。
  • 数据处理层:负责具体的SQL查找,SQL解析,SQL执行和执行结果的映射处理等。它的主要目的是根据调用的请求完成一次数据库的操作。
  • 框架基础支撑层:负责最基础的功能支撑,包括连接管理,事务管理,配置加载和缓存处理,这些都是公用的东西,我们把它抽取出来作为最基础的组件,为上层数据处理层提供最基础的支撑。

2.4 MyBatis的框架架构设计是怎么样的


从上图可以发现,MyBatis的初始化从加载主配置文件开始,生成Configuration类

  1. 加载配置:可以从代码的注解或xml中加载,将SQL的配置信息加载成一个个MappedStatement对象,(包括了传参映射配置,执行SQL,结果集映射配置)存储到内存中。
  2. SQL解析:当API接口层接收到调用请求后,会接收到传入的SQL的id和传入对象,(可以是Map或JavaBean对象),MyBatis会根据SQL的id找到相应的MapperStatement,然后根据传入的参数对MapperStatement进行解析,解析后得到最终要执行的SQL语句和参数。
  3. SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
  4. 结果映射:将数据库的查询结果根据映射配置进行转换,可以转换成Map,JavaBean或基本数据类型,并最终返回结果。

2.5 为什么需要预编译

预编译定义:
SQL预编译是指数据库驱动在发送SQL语句和参数之前对SQL语句进行编译,这样在DBMS执行SQL时进不需要进行编译了。
为什么要预编译?
JDBC通过preparedStatement对象来抽取预编译的SQL语句,使用预编译可以优化SQL的执行。预编译之后的SQL语句可以直接执行,DBMS不需要再次编译。提高了执行SQL的性能。同时预编译对象可以重复使用,例如:把一个SQL预编译后产生的preparedStatement存储起来,下一次如果再次使用同一个SQL,可以直接使用这个缓存的PreparedStatement对象。再则就是防止SQL注入的问题,MyBatis默认情况下,将所有的SQL语句,进行预编译。

深入理解:https://blog.csdn.net/JAYU_37/article/details/105444736

2.6 Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?

延迟加载的基本原理:
使用CGLIB创建目标的代理对象,当调用方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器发现a.getB()是null值,就会单独事先保存好的关联B对象的sql,把B查询上来,a.setB(b),之后b就有值了,接着完成a.getB().getName()方法的调用,这就是延迟加载的基本原理。

MyBatis是仅支持association关联对象和collection关联集合对象的延迟加载,association指的是一对一,collection指的是一对多的查询。开启MyBaits延迟加载需要在主配置文件中设置标签< lazyloadingEnabled = true | false >

3. 映射器

3.1 #{}和${}的区别

  1. ${}传入的值直接出现在SQL中,所以一般使用外部要加上 ‘’,而#{}会自动加上‘’
  2. #{}可以防止SQL注入问题,而${}不能,使用#{}传参更加安全
  3. #{}的变量替换是在DBMS内,${}的变量替换是在DBMS外部
  4. #{}传入的值会经过预编译处理生成 ? 占位符,调用preparedStatement的set方法为其赋值
  5. 一般能使用#{}的地方尽量不要使用${}

参考:https://www.cnblogs.com/mww-NOTCOPY/p/11777187.html

3.2 模糊查询like语句该怎么写

写法一: ‘ %${param}%’ :可能会引起SQL注入问题,不推荐使用。
写法二:”%”#{parm}”%”
注意:这种写法极易出错,因为%后面的引号必须是”,因为#{param}解析完之后会给自身套上一个‘’,例如:

SELECT * FROM USER WHERE NAME LIKE "%"'li'"%";   #可以查询到,查询的是li
SELECT * FROM USER WHERE NAME LIKE "%''li''%";   #不可以查询到任何结果,查询的是''li''

写法三:concat(’%’,#{param},’%’);使用concat函数,推荐使用。
写法四:使用bind标签

<select id="listUserLikeUsername" resultType="com.jourwon.pojo.User">
  <bind name="pattern" value="'%' + username + '%'" />
  select id,sex,age,username,password from person where username LIKE #{ pattern}
</select>

3.3 在mapper中如何传递多个参数

  1. 顺序传参法
public User selectUser(String name, int deptId);
<select id="selectUser" resultMap="UserResultMap">
	select * from user
	where 
	user_name= #{ 0} and dept_id=#{ 1};
</select>

#{}里面的数字代表传参的顺序,但是此方法并不建议使用,原因是sql语句表达不直观,一旦出错很难发现错误。

  1. @Param注解传参法
public User selectUser(@Param("userName")String name, @Param("deptId")int deptId);
<select id="selectUser" resultMap="UserResultMap">
	select * from user
	where 
	user_name= #{ username} and dept_id=#{ deptId};
</select>

#{}里面的名称是注解@Param()对应value名称,使用于参数不多的情况下,此方法推荐使用。
3. Map传参法

public User selectUser(Map<String,Object>params);
<select id="selectUser" resultMap="UserResultMap" parameterType="java.util.Map">
	select * from user
	where 
	user_name= #{ username} and dept_id=#{ deptId};
</select>

#{}里面的名称对应的是Map的key值,此方法使用与传递多个参数的情况下。
4. JavaBean传参法

public User selectUser(User user);
<select id="selectUser" resultMap="UserResultMap" parameterType="com.jourwon.pojo.User">
	select * from user
	where 
	user_name= #{ username} and dept_id=#{ deptId};
</select>

#{}里面的名称传的是User类对应的属性名称扩展不易,代码可读性强,业务逻辑处理方便,推荐使用。

3.4 Mybatis如何执行批量操作

使用foreach标签
foreach主要用于构建in条件中,它可以在SQL语句中迭代一个集合,foreach的标签属性有item,index,collection,open,separator,close。

  • item:表示从集合中随便去的一个别名,但最好能见名知意。
  • indx:用于指定一个名字,用于表示迭代过程中,每次迭代到的位置,并不常用。
  • open:什么时候开始,一般使用“(”
  • close:什么时候结束,常用“)”
  • separator:每次迭代时,中间以什么符号进行分割,常用“,”

注意:使用foreach最关键也是最容易出错的地方应该就是在collection属性了,该属性必须是指定的,但是在不用情况下,该属性的值是不一样的。主要有三种情况:

  1. 如果传入的单参数类型是一个List,collection的属性值为list
  2. 如果传入的单参数类型是一个array数组,collection的属性值为array
  3. 不管传入单参还是多参,都可以将其封装成Map,参数可以是集合或者对象类型,collection属性值就是这些参数对应的key

具体用法:

<!-- int add(@Param("emps") List<Employee> emps)
MySQL下的批量保存,可以使用foreach遍历,mysql支持values(),(),...-->
<insert  id="add" paramType="java.util.List">
	insert into emp(ename,gender,email,did)values
	<foreach collection="emps" item="emp" seperator=",">
	(#{ emp.ename},#{ emp.gender},#{ emp.email},#{ emp.dept.id})
	</foreach>
</insert>

3.5 如何获取生成的主键

对于主键自增长的MySQL

<insert id="insertUser" useGeneratedKeys="true" keyProperty="userId" >
    insert into user( 
    user_name, user_password, create_time) 
    values(#{ userName}, #{ userPassword} , #{ createTime, jdbcType= TIMESTAMP})
</insert>

可以发现上述的parameterType 不写,因为idea的mybatis插件可以自动识别出来传入的参数类型,如果要想访问主键,那么parameterType的类型应当是Map或Java实体,获取主键值可以通过key或者getXXX方法

3.6 当实体类中的属性名和表中的字段名不一样 ,Mybatis是如何将sql执行结果封装为目标对象并返回的?

假设实体类的属性名:

private Integer uid;
private String uname;
private String ugrader;

数据库表user字段名:

int(10) id;
varchar(32) name;
varchar(32) grader;

方案一:
取别名,例如

<select id="getUser" paramterType="int" resultType="com.liuzeyu.User">
	select id uid,name uname,ugrader where id = #{ uid}
</select>

方案二:
resultMap标签

<resultMap id="userMap" type="com.liuzeyu.User">
	<id property = "uid" column="id"/>
	<result property = "uname" column = "name"/>
	<result property = "ugrader" column="grander"/>
</resultMap>

<select id="getUser" paramterType="int" resultMap="userMap">
	select id uid,name uname,ugrader where id = #{ uid}
</select>

3.7 Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?

不同的接口映射文件中如果配置了namespace则id可以重复,否则不能。
原因:

原因就是namespace+id是作为Map<String, MappedStatement>的key使用的,
如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。
有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。

3.8 什么是MyBatis的接口绑定?有哪些实现方式?

所谓的接口绑定,就是在mybatis中定义任意接口,然后把接口的方法和SQL语句绑定,我们直接调用接口方法就可以了,比前的使用sqlSession提供的方法更加的灵活。
方法1:使用注解绑定,就是在接口上使用@Select,@Delete,@Update…里面编写SQL语句
方法二:使用xml来绑定,在这种情况下,xml的接口映射配置文件的namespace属性必须配置接口的全限定类名。
当SQL语句比较简单时使用注解绑定,反之使用xml绑定

3.9 使用MyBatis的mapper接口调用时有哪些要求?

  1. Mapper接口方法名必须和mapperx.xml每个sql的id一致
  2. Mapper接口方法的参数类型必须和mapper.xml中每个sql的parameterType类型一致,可以取别名,只有一个参数时parameterType属性可以不写
  3. Mapper接口方法的返回参数类型必须和mapper.xml中每个sql的resultType类型一致
  4. mapper.xml的namespace必须是Mapper接口的全限定类名

3.10 Mapper 编写有哪几种方式?

总共有三种方案。

  1. 使用mapper扫描器
    1. mapper.xml文件编写:
      mapper.xml中的namespace必须添加mapper接口的全限定类名
      mapper接口方法名必须和mapper.xml定义的执行sql语句id名称一致
      如果mapper.xml和mapper接口的名称保持一致则不用在mybatis主配置文件进行配置mapper.xml的具***置,如下

      mappers>
          <mapper resource="mapper.xml 文件的地址" />
          <mapper resource="mapper.xml 文件的地址" />
      </mappers>
      
    2. 定义mapper接口
      注意mapper.xml的文件名必须和mapper接口名必须保持一致并且放在同一目录下。

    3. 配置mapper扫描

      <bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer">
      	<property name="basePackage" value="mapper接口权限地类名"/>
      	<property name="SqlSessionFactoryBeanName" value="sessionFactory"/>
      </bean>
      
    4. 使用扫描器后可以从spring容器中获取mapper的实现类对象。
      使用的是resultType

3.11 Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?

还有很多标签,例如:

<resultMap>:结果集映射
<parameterMap>:参数映射
<sql>:sql片段标签
<include>:引入sql片段标签
<selectKey>:为不自增的主键添加策略标签
.....

3.12 Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?

A标签引用了B标签,虽然B标签在A标签后面,但是还是可以被正确引用。
原因:

因为配置文件加载到A标签后,发现了引入的B标签内容还未被解析到,此时A标签就置为未解析状态
继续解析以下的标签,包含B标签,知道解析完B标签,待所有的标签都解析完之后,MyBatis就将
哪些未被解析的内容再次解析,所以A标签也可以被正常解析。

4. 高级查询

4.1 MyBatis实现一对一,一对多有几种方式,怎么操作的?

使用联合查询和嵌套查询

  • 联合查询:几张表联合查询,只查询一次,通过resultMap的association和collection节点配置一对一,一对多查询。
  • 嵌套查询:先查一个表,根据这个表里面的结果的外键id,去另外一个表里面查询,也是通过配置association和collection,但另外一张表的查询需要用到select标签。

5. 动态SQL

5.1 Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?

MyBatis动态SQL可以让我们在XML配置文件中,以标签形式编写动态SQL,完成逻辑判断和SQL拼接,MyBatis提供了9中动态sql标签,分别是

trim | where | set | foreach | if | choose | when | otherwise | bind

其执行原理是,使用OGNL从SQL参数对象中计算表达式的值,根据表达式的值动态拼接SQL,以此来完成动态SQL的功能。

6. 缓存

6.1 简述以下Mybatis的一级、二级缓存

一级缓存:一级缓存SqlSession对象范围的缓存,当我们执行查询之后,查询结果会同时存入SqlSession为我们提供的一块区域中,该区域的结构是Map,当我们再次查询到同样的数据时mybatis会先从sqlSession中获取。当调用SqlSession增删改,commit(),close()时就会清空一级缓存。
二级缓存:二级缓存的机制和一级相同,它是指Mybatis的SqlSessionFactory对象缓存,在同一个SqlSessionFactory创建的SqlSession对象内共享缓存。默认不开启二级缓存,若要开启,存储的类必须实现序列化Serializable接口,作用是用来保存对象状态,并且在映射配置文件中开启< cache/>

注意:缓存默认更新机制,当某一作用域(SqlSession/SqlSessionFactory)进行了G/U/D(增删改)后,默认作用域下的select中的缓存将被清空。

写在最后:
3.10处遇到一个问题:如果mapper.xml和mapper接口的名称保持一致则不用在mybatis主配置文件进行配置mapper.xml的具***置,但是在我项目中如果不配置于数据库交互则会报错?所以3.10的说法是否正确有待考察…

<mappers>
    <mapper resource="mapper.xml 文件的地址" />
    <mapper resource="mapper.xml 文件的地址" />
</mappers>