Mybatis面经

1. 什么是 Mybatis?

  • Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高
  • MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
  • 通过 xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回(从执行 sql 到返回 result 的过程)

2. Mybaits 的优点?

  • 基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用
  • 与 JDBC 相比,减少了50%以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接
  • 很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis 都支持)
  • 能够与 Spring 很好的集成
  • 提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射标签,支持对象关系组件维护-

3. MyBatis 框架的缺点?

  • SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写 SQL 语句的功底有一定要求
  • SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

4. MyBatis 框架适用场合?

MyBatis 专注于 SQL 本身,是一个足够灵活的 DAO 层解决方案
对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis 将是不错的选择

5. MyBatis 与 Hibernate 有哪些不同?

  • Mybatis 和 hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要程序员自己编写 Sql 语句
  • Mybatis 直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是 mybatis 无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套 sql 映射文件,工作量大
  • Hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用 hibernate 开发可以节省很多代码,提高效率

6. MyBatis和其它持久化层技术对比

  • JDBC
    • SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
    • 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
    • 代码冗长,开发效率低
  • Hibernate 和 JPA
    • 操作简便,开发效率高
    • 程序中的长难复杂 SQL 需要绕过框架
    • 内部自动生产的 SQL,不容易做特殊优化
    • 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
    • 反射操作太多,导致数据库性能下降
  • MyBatis
    • 轻量级,性能出色
    • SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
    • 开发效率稍逊于HIbernate,但是完全能够接受

7. 谈谈MyBatis和JPA的区别

  • ORM映射不同:

    MyBatis是半自动的ORM框架,提供数据库与结果集的映射;

    JPA(默认采用Hibernate实现)是全自动的ORM框架,提供对象与数据库的映射。

  • 可移植性不同:

    JPA通过它强大的映射结构和HQL语言,大大降低了对象与数据库的耦合性;

    MyBatis由于需要写SQL,因此与数据库的耦合性直接取决于SQL的写法,如果SQL不具备通用性而用了很多数据库的特性SQL的话,移植性就会降低很多,移植时成本很高。

  • SQL优化上的区别:

    由于Mybatis的SQL都是写在XML里,因此优化SQL比Hibernate方便很多。

    而Hibernate的SQL很多都是自动生成的,无法直接维护SQL。虽有HQL,但功能还是不及SQL强大,见到报表等复杂需求时HQL就无能为力,也就是说HQL是有局限的Hhibernate虽然也支持原生SQL,但开发模式上却与ORM不同,需要转换思维,因此使用上不是非常方便。总之写SQL的灵活度上Hibernate不及Mybatis。

8. MyBatis输入输出支持的类型有哪些?

parameterType:

MyBatis支持多种输入输出类型,包括:

简单的类型,如整数、小数、字符串等;

集合类型,如Map等;

自定义的JavaBean。

其中,简单的类型,其数值直接映射到参数上。对于Map或JavaBean则将其属性按照名称映射到参数上。

9. MyBatis里如何实现一对多关联查询?

MyBatis 实现一对多有联合查询嵌套查询。联合查询是几个表联合查询,只查询一次,通过在 resultMap 里面的 collection 节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的结果的外键 id,去再另外一个表里面查询数据,也是通过配置 collection,但另外一个表的查询通过 select 节点配置。

一对多:例如:根据部门id查找部门以及部门中的员工信息

⚠️ 需要查询一对多、多对一的关系,需要在“一”的pojo中加入List<多>属性,在“多”的pojo中加入“一”。
⚠️ 也就是说,在Dept类中,要加入private List emps;;在Emp类中,要加入private Dept dept;。然后给他们各自添加get、set方法,重写构造器和toString()

public class Dept {
    private Integer did;
    private String deptName;
    private List<Emp> emps;
	//...构造器、get、set方法等
}

方法1:collection(联合查询)

DeptMapper接口

public interface DeptMapper {
    /**
     * 获取部门以及部门中所有的员工信息
     */
    Dept getDeptAndEmp(@Param("did") Integer did);
 }

DeptMapper.xml

    <resultMap id="deptAndEmpResultMap" type="Dept">
        <id property="did" column="did">id>
        <result property="deptName" column="dept_name">result>

        <collection property="emps" ofType="Emp">
            <id property="eid" column="eid">id>
            <result property="empName" column="emp_name">result>
            <result property="age" column="age">result>
            <result property="sex" column="sex">result>
            <result property="email" column="email">result>
        collection>
    resultMap>

    <select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
        select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did}
    select>

方法2: 分步查询(嵌套查询)

(1)查询部门信息

DeptMapper接口

public interface DeptMapper {
    /**
     * 分步查询 查询部门及其所有的员工信息
     * 第一步  查询部门信息
     */
    Dept getDeptAndEmoByStepOne(@Param("did") Integer did);
}

DeptMapper.xml


    <resultMap id="deptAndEmoByStepOneMap" type="Dept">
        <id property="did" column="did">id>
        <result property="deptName" column="dept_name">result>
        <collection property="emps"
                    select="com.atguigu.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo"
                    column="did">
        collection>
    resultMap>

    <select id="getDeptAndEmoByStepOne" resultMap="deptAndEmoByStepOneMap">
        select * from t_dept where did = #{did}
    select>

(2)根据部门id查询部门中的所有员工

EmpMapper

public interface EmpMapper {
    /**
     * 分步查询 查询部门及其所有的员工信息
     * 第一步  查询部门信息
     * 第二步  根据查询员工信息
     */
    List<Emp> getDeptAndEmpByStepTwo(@Param("did") Integer did);
}

EmpMapper.xml



    <select id="getDeptAndEmpByStepTwo" resultType="Emp">
        select * from t_emp where did = #{did}
    select>

10. MyBatis获取参数值的两种方式-- #{} 和 ${} 的区别是什么

#{} 是预编译处理,${} 是字符串替换。

Mybatis 在处理 #{} 时,会将 sql 中的 #{} 替换为 ? 号,调用 PreparedStatement 的 set 方法来赋值;Mybatis 在处理 ${} 时,就是把 ${} 替换成变量的值。

使用 #{} 可以有效的防止 SQL 注入,提高系统安全性。

  • ${}的本质就是字符串拼接
  • #{}的本质就是占位符赋值

${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;
但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号(尽量使用这一种)。

  • 使用#设置参数时,MyBatis会创建预编译的SQL语句,然后在执行SQL时MyBatis会为预编译SQL中的占位符(?)赋值。预编译的SQL语句执行效率高,并且可以防止注入攻击。

  • 使用$设置参数时,MyBatis只是创建普通的SQL语句,然后在执行SQL语句时MyBatis将参数直接拼入到SQL里。这种方式在效率、安全性上均不如前者,但是可以解决一些特殊情况下的问题。例如,在一些动态表格(根据不同的条件产生不同的动态列)中,我们要传递SQL的列名,根据某些列进行排序,或者传递列名给SQL都是比较常见的场景,这就无法使用预编译的方式了。

总结:分成两种情况进行处理

  1. 实体类类型的参数(若mapper接口中的方法参数为实体类对象时此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号)
  2. 使用@Param标识参数
```java
public interface ParameterMapper {
    /**
     * 添加用户信息
     */
    int insertUser(User user);
}
public interface ParameterMapper {
    /**
     * 验证登录 (使用@Param)
     */
    User checkLoginByParam(@Param("username") String username, @Param("password") String password);
}

11. 既然 ${}不安全,为什么还需要用它,什么时候会用到它?

它可以解决一些特殊情况下的问题。例如,在一些动态表格(根据不同的条件产生不同的动态列)中,我们要传递SQL的列名,根据某些列进行排序,或者传递列名给SQL都是比较常见的场景,这就无法使用预编译的方式了。

批量删除时:
只能使用${},如果使用#{},则解析后的sql语句为delete from t_user where id in ('1,2,3'),这样是将1,2,3看做是一个整体,只有id为1,2,3的数据会被删除。正确的语句应该是delete from t_user where id in (1,2,3),或者delete from t_user where id in ('1','2','3')

12. MyBatis的xml文件和Mapper接口是怎么绑定的?

是通过xml文件中 根标签的namespace属性进行绑定的,即namespace属性的值需要配置成接口的全限定名称,MyBatis内部就会通过这个值将这个接口与这个xml关联起来。

  • MyBatis中可以面向接口操作数据,要保证两个一致
    • mapper接口的全类名和映射文件的命名空间(namespace)保持一致
    • mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致
 package com.atguigu.mybatis.mapper;  
 
public interface UserMapper {  
	/**  添加用户信息  */  
	int insertUser();  
}
  
DOCTYPE mapper  
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">  
<mapper namespace="com.atguigu.mybatis.mapper.UserMapper">  
	  
	<insert id="insertUser">  
		insert into t_user values(null,'张三','123',23,'女')  
	insert>  
mapper>

13. MyBatis分页和自己写的分页哪个效率高?

自己写的分页效率高。

在MyBatis中,我们可以通过分页插件实现分页,也可以通过分页SQL自己实现分页。其中,分页插件的原理是,拦截查询SQL,在这个SQL基础上自动为其添加limit分页条件。它会大大的提高开发的效率,但是无法对分页语句做出有针对性的优化,比如分页偏移量很大的情况,而这些在自己写的分页SQL里却是可以灵活实现的。

14. 了解MyBatis缓存机制吗?

MyBatis的缓存分为一级缓存和二级缓存。

14.1、MyBatis的一级缓存

  • 一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问

使一级缓存失效的四种情况:

  1. 不同的SqlSession对应不同的一级缓存
  2. 同一个SqlSession但是查询条件不同
  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作
  4. 同一个SqlSession两次查询期间手动清空了缓存

14.2、MyBatis的二级缓存

  • 二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取

二级缓存开启的条件

  1. 在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
  2. 在映射文件中设置标签
  3. 二级缓存必须在SqlSession关闭或提交之后有效
  4. 查询的数据所转换的实体类类型必须实现序列化的接口

⚠️ 使二级缓存失效的情况: 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
没有提交sqlsession时,数据会保存在一级缓存中,提交后,会保存在二级缓存中。

14.3、MyBatis缓存查询的顺序

  • 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
  • 如果二级缓存没有命中,再查询一级缓存
  • 如果一级缓存也没有命中,则查询数据库
  • SqlSession关闭之后,一级缓存中的数据会写入二级缓存

15. 当实体类中的属性名和表中的字段名不一样 ,怎么办 ?

字段名(数据库里的名字例如emp_name)和实体类中的属性名不一致,但是字段名符合数据库的规则(使用_),实体类中的属性 名符合Java的规则(使用驼峰),此时也可通过以下两种方式处理字段名和实体类中的属性的映射关系

  • 可以通过为字段起别名的方式,保证和实体类中的属性名保持一致
  • 通过 来映射字段名和实体类属性名的一一对应的关系。

15.1 用起别名的方式保证字段名与属性名一致

和sql中一样,用字段名 属性名(如emp_name empName)来使二者一致。


    <select id="getAllEmp" resultType="Emp">
        select eid, emp_name empName, age, sex, email from t_emp
    select>

15.2 逐一设置resultMap映射关系

在resultMap中,一一对应地设置属性名->字段名,再在select标签中添加resultMap=“对应resultMap的id”


    <resultMap id="empResultMap" type="Emp">
        <id property="eid" column="eid">id>
        <result property="empName" column="emp_name">result>
        <result property="age" column="age">result>
        <result property="sex" column="sex">result>
        <result property="email" column="email">result>
    resultMap>

    <select id="getAllEmp" resultMap="empResultMap">
        select * from t_emp
    select>

16. 模糊查询 like 语句该怎么写?

select * from t_user where username like "%"#{username}"%"是最常用的

SQLMapper接口:

public interface SQLMapper {
    /**
     * 根据用户名模糊查询用户信息
     */
    List<User> getUserByLike(@Param("username") String username);
}

SQLMapper.xml:




    <select id="getUserByLike" resultType="User">
     
       
        select * from t_user where username like "%"#{username}"%"
    select>

17. 通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应,请问,这个 Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?

  • Dao 接口即 Mapper 接口。接口的全限名,就是映射文件中的 namespace 的值;接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的参数,就是传递给 sql 的参数。

  • Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MapperStatement。在 Mybatis 中,每一个