Mybatis 面试题

1. Mybatis 的特点是什么?

  1. Mybatis是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,加载驱动、创建连接、创建 statement 等繁杂的过程,开发者开发时只需要关注如何编写 SQL 语句,可以严格控制 SQL 执行性能,灵活度高。
  2. 由于 MyBatis 专注于 SQL 本身,灵活度高,所以比较适合对性能的要求很高,或者需求变化较多的项目,如互联网项目。

2. Mybaits 的优缺点?

优点:

  1. 基于 SQL 语句编程,很灵活。
  2. SQL 写在 XML 里,与代码解耦,且便于统一管理。
  3. 支持动态 SQL,SQL 片段可复用。

缺点:

  1. SQL 编写工作量较大,SQL 复杂时,对开发人员的 SQL 功底有一定要求。
  2. SQL 语句依赖于数据库,因此数据库移植性差,不过一般情况下,项目也不会有变更数据库的需求。

3. #{} 和 ${} 的区别是什么?

${}是字符串替换,#{}是预处理;

Mybatis 在处理${}时,就是把${}直接替换成变量的值。而处理#{}时,会对 SQL 语句进行预处理,将 SQL 中的#{} 替换为?号,调用PreparedStatementset方法来赋值;

使用#{}可以有效的防止 SQL 注入,提高系统安全性。(什么是 SQL 注入?举个例子,比如把 SQL 的查询参数故意输入为 or 1 = 1,这样 sql 就能查出全部结果,可能导致查出敏感数据)

4. Mybatis 动态 SQL 的原理?

MyBatis 动态 sql 可以让我们在 xml 文件中以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。其执行原理为,使用 OGNL 表达式从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
MyBatis 提供了 9 种动态 sql 标签:

  • <if>
  • <where> (trim, set)
  • <choose> (when,otherwise)
  • <foreach>
  • <bind>

5. Mybatis 的缓存是什么?

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

首先,MyBatis 的缓存目的是在多次执行相同 SQL 的情况下,访问缓存而不是直接访问数据库,提高效率的同时减少了数据库的压力。

Mybatis 默认开启一级缓存。一级缓存是 SqlSession 级别的缓存,缓存的数据只在 SqlSession 内有效。而 SqlSession 又是什么呢?对数据库增删改查等一系列操作都是在 SqlSession 中完成,可以理解为在一个线程内对数据库的一系列操作。在同一个 SqlSession 中,执行相同的 SQL 查询时;第一次会去查询数据库,并写在缓存中,第二次会直接从缓存中取。SqlSession 在执行了 commit 之后会清空一级缓存。

Mybatis 二级缓存是默认不开启的。二级缓存作用于同一个 namespace 下的所有操作语句,也就是 Mapper 级别的,多个 SqlSession 使用同一个 Mapper 的 sql 就能够使用二级缓存。即二级缓存被多个 SqlSession 共享,是一个全局的变量。

如果增删改操作频繁的话,二级缓存形同虚设,每次都会被清空缓存。

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

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

6.1. 编写插件

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

6.2. 四种接口的说明

  1. Executor:拦截执行器的方法。
  2. ParameterHandler:拦截参数的处理。
  3. ResultSetHandler:拦截结果集的处理。
  4. StatementHandler:拦截 SQL 语法构建的处理。

6.3. 拦截规则

@Intercepts注解需要一个Signature(拦截点)参数数组。通过Signature来指定拦截哪个对象里面的哪个方法。@Intercepts注解定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
    /**
     * 定义拦截点
     * 只有符合拦截点的条件才会进入到拦截器
     */
    Signature[] value();
}

Signature来指定咱们需要拦截那个类对象的哪个方法。定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
  /**
   * 定义拦截的类 Executor、ParameterHandler、StatementHandler、ResultSetHandler当中的一个
   */
  Class<?> type();

  /**
   * 在定义拦截类的基础之上,在定义拦截的方法
   */
  String method();

  /**
   * 在定义拦截方法的基础之上在定义拦截的方法对应的参数,
   * JAVA里面方法可能重载,故注意参数的类型和顺序
   */
  Class<?>[] args();
}

标识拦截注解@Intercepts规则使用,简单实例如下:

@Intercepts({//注意看这个大花括号,也就这说这里可以定义多个@Signature对多个地方拦截,都用这个拦截器
        @Signature(
                type = ResultSetHandler.class,
                method = "handleResultSets", 
                args = {Statement.class}),
        @Signature(type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})

说明:

  • @Intercepts:标识该类是一个拦截器;
  • @Signature:指明自定义拦截器需要拦截哪一个类型,哪一个方法;
    • type:上述四种类型中的一种;
    • method:对应接口中的哪类方法(因为可能存在重载方法);
    • args:对应哪一个方法的入参;

这里我们打开接口org.apache.ibatis.executor.Executor的源码,找到需要拦截的方法query(),这样就知道为什么args是那几个参数了。

<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

method中对应四种的类型的方法:

可作用接口 可作用方法 拦截器用途
Executor update(),query(),flushStatements(),commit(),rollback(),getTransaction(),close(),isClosed() 拦截执行器中的方法
ParameterHandler getParameterObject(),setParameters() 拦截对参数的处理
ResultSetHandler handleResultSets(),handleOutputParameters() 拦截对结果集的处理
StatementHandler prepare(),parameterize(),batch(),update(),query() 拦截 SQL 构建的处理
posted @ 2023-06-06 17:11  xfcoding  阅读(26)  评论(0编辑  收藏  举报