本文为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 emps;
    //...构造器、get、set方法等
}

方法1:collection(联合查询)

DeptMapper接口

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

DeptMapper.xml


        
        

        
            
            
            
            
            
        
    

    

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

(1)查询部门信息

DeptMapper接口

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

DeptMapper.xml


    
        
        
        
        
    

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

EmpMapper

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

EmpMapper.xml



    

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();  
      }
        
        
        
        
        
      insert into t_user values(null,'张三','123',23,'女')  
        
      

      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查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取

二级缓存开启的条件

cacheEnabled="true"

使二级缓存失效的情况: 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

没有提交sqlsession时,数据会保存在一级缓存中,提交后,会保存在二级缓存中。

14.3、MyBatis缓存查询的顺序

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

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

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

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

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


    

15.2 逐一设置resultMap映射关系

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


    
        
        
        
        
        
    

    

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

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

SQLMapper接口:

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

SQLMapper.xml:




    

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

  • Dao 接口即 Mapper 接口。接口的全限名,就是映射文件中的 namespace 的值;接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的参数,就是传递给 sql 的参数。
  • Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MapperStatement。在 Mybatis 中,每一个 select * from t_user where username = #{username} and password = #{password}

    多个参数封装成map

  • 若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号

    ParameterMapper接口:

      public interface  ParameterMapper  { 
        /**
     * 验证登录
     */
        User checkLoginByMap(Map map);
    }

    对应在ParameterMapper.xml中配置。

     
        

    23. Mybatis 动态 sql 有什么用?执行原理?有哪些动态 sql?

本质是一系列的标签

Mybatis 动态 sql 可以在 Xml 映射文件内,以标签的形式编写动态 sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接 sql 的功能。

Mybatis 提供了9种动态sql标签:trim | where | set | foreach | if | choose | when | otherwise | bind。

  • Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题

if(常用)

if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行

  • 在where后面添加一个恒成立条件 1=1
  • 这个恒成立条件并不会影响查询的结果
  • 这个 1=1 可以用来拼接 and 语句,例如:当empName为null时

    • 如果不加上恒成立条件,则SQL语句为 select * from t_emp where and age = ? and sex = ? and email = ? ,此时 where 会与 and 连用,SQL语句会报错
    • 如果加上一个恒成立条件,则SQL语句为 select * from t_emp where 1= 1 and age = ? and sex = ? and email = ? ,此时不报错

应用场景:多条件查询

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

,加上动态 sql 的9个标签,其中 为 sql 片段标签,通过 标签引入 sql 片段, 为不支持自增的主键生成策略标签。

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

不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复;

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

但是,在以前的 Mybatis 版本的 namespace 是可选的,不过新版本的 namespace 已经是必须的了。

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

Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而 Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。

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

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

28. 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,支持延迟加载的原理都是一样的。

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

接口绑定,就是在 MyBatis 中任意定义接口,然后把接口里面的方法和 SQL 语句绑定,我们直接调用接口方法就可以,这样比起原来了 SqlSession 提供的方法我们可以有更加灵活的选择和设置。

接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上 @Select、@Update 等注解,里面包含 Sql 语句来绑定;另外一种就是通过 xml 里面写 SQL 来绑定,在这种情况下,要指定 xml 映射文件里面的 namespace 必须为接口的全路径名。当 Sql 语句比较简单时候,用注解绑定,当 SQL 语句比较复杂时候,用 xml 绑定,一般用 xml 绑定的比较多。

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

Mapper 接口方法名和 mapper.xml 中定义的每个 sql 的 id 相同

Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同

Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同

Mapper.xml 文件中的 namespace 即是 mapper 接口的类路径

29. 简述 Mybatis 的插件运行原理,以及如何编写一个插件?

Mybatis 仅可以编写针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这4种接口的插件,Mybatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke() 方法,当然,只会拦截那些你指定需要拦截的方法。

编写插件:实现 Mybatis 的 Interceptor 接口并复写 intercept() 方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。

30. 多对一映射处理

如:查询员工信息以及员工所对应的部门信息

需要查询一对多、多对一的关系,需要在“一”的pojo中加入List<多>属性,在“多”的pojo中加入“一”。

也就是说,在Dept类中,要加入 private List emps ;;在Emp类中,要加入 private Dept dept; 。然后给他们各自添加get、set方法,重写构造器和toString()

方法1:使用association处理映射关系

  • association:处理多对一的映射关系
  • property:需要处理多对一的映射关系的属性名
  • javaType:该属性的类型

    
    
    
    
    
    
    
    
        
        
    
    
    
    
    
        select * from t_emp where eid = #{eid} 

    2. 查询部门信息

    //DeptMapper里的方法
    /**
     * 通过分步查询,员工及所对应的部门信息
     * 分步查询第二步:通过did查询员工对应的部门信息
     */
    Dept getEmpAndDeptByStepTwo(@Param("did") Integer did);
    
    
        
        
    
    
    

    31、延迟加载

    • 分步查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息:

         lazyLoadingEnabled
      aggressiveLazyLoading
    • 此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过association和collection中的fetchType属性设置当前的分步查询是否使用延迟加载,fetchType=“lazy(延迟加载)|eager(立即加载)”

    mybatis-config.xml

    
        
        
    
    @Test
    public void getEmpAndDeptByStepOne() { 
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        Emp emp = mapper.getEmpAndDeptByStepOne(1);
        System.out.println(emp.getEmpName());
    }
    • 关闭延迟加载,两条SQL语句都运行了

    本文为Mybatis面经,其中难点问题做了详细解释_第1张图片

    • 开启延迟加载,只运行获取emp的SQL语句

    本文为Mybatis面经,其中难点问题做了详细解释_第2张图片

    本文为Mybatis面经,其中难点问题做了详细解释_第3张图片

    通过fetchType参数,可以手动控制延迟加载或立即加载,否则根据全局配置的属性决定是延迟加载还是立即加载。

    本文为Mybatis面经,其中难点问题做了详细解释_第4张图片

    @Test
    public void getEmpAndDeptByStepOne() { 
        SqlSession sqlSession = SqlSessionUtils.getSqlSession();
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        Emp emp = mapper.getEmpAndDeptByStepOne(1);
        System.out.println(emp.getEmpName());
        System.out.println("----------------");
        System.out.println(emp.getDept());
    }

你可能感兴趣的:(本文为Mybatis面经,其中难点问题做了详细解释)