Mybatis常见面试题

1、什么是Mybatis

‌MyBatis是一个开源的‌Java持久层框架,主要用于简化Java应用程序与数据库之间的交互。它支持定制化‌SQL、存储过程以及高级映射,是一个半ORM(对象关系映射)框架。MyBatis内部封装了‌JDBC,使得开发者不需要处理加载驱动、创建连接、创建statement等繁杂的过程,只需要关注SQL语句本身。

2、Mybatis的优缺点

MyBatis 的优缺点如下:

优点:

  • 高度灵活,开发人员可直接编写 SQL 语句以满足特定业务需求,动态 SQL 功能强大,能适应复杂场景。
  • SQL 与 Java 代码分离,使代码结构更清晰,易于理解和维护,且在切换数据库时相对容易。
  • 提供缓存机制,可减少数据库访问次数提升性能,还能针对具体数据库进行 SQL 调优。
  • 易于与其他框架集成,如 Spring、Spring Boot 等,插件扩展机制方便满足特定业务需求。

缺点:

  • 对于简单的 CRUD 操作也需编写 SQL,增加工作量且易出错。
  • 对象关系映射功能相对较弱,需手动处理映射关系,数据库结构变动影响大。
  • 学习曲线较陡,涉及概念多,调试相对复杂。

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

#{}使用预编译的方式来处理SQL语句中的参数,将传入的参数值以安全的方式替换掉占位符。在SQL语句执行前,会先将#{}替换为一个问号占位符,然后使用PreparedStatement进行预编译,最后将实际的参数值设置到预编译语句中。使用#{}可以有效地防止SQL注入等安全问题,同时也可以避免一些数据类型转换的问题。

则是直接将参数值替换到 S Q L 语句中。在 S Q L 语句执行前,会直接将 {}则是直接将参数值替换到SQL语句中。在SQL语句执行前,会直接将 则是直接将参数值替换到SQL语句中。在SQL语句执行前,会直接将{}替换为对应的参数值,这种方式的好处是可以直接拼接字符串,但也带来了一些安全问题。使用${}时需要开发人员自行保证参数的合法性,否则可能会出现SQL注入等安全问题。

例如:

SELECT * FROM user WHERE name = '${name}'

如果$name的值是 “admin’ OR ‘1’='1”,则该SQL语句会查询出所有用户的数据,而不是仅查询用户名为"admin"的用户数据,造成了安全风险。

4、MyBatis的解析和运行原理

在学习 MyBatis 程序之前,需要了解一下 MyBatis 工作原理,以便于理解程序。MyBatis 的工作原理如下图:
在这里插入图片描述

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

5、MyBatis的功能架构是怎样的

在这里插入图片描述
我们把Mybatis的功能架构分为三层:

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

6、为什么需要预编译

  1. 定义:
    SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。

  2. 为什么需要预编译
    JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。预编译阶段可以优化 SQL 的执行。预编译之后的 SQL 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的SQL,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。同时预编译语句对象可以重复利用。把一个 SQL 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个SQL,可以直接使用这个缓存的 PreparedState 对象。Mybatis默认情况下,将对所有的 SQL 进行预编译。

7、Mybatis都有哪些Executor执行器?它们之间的区别是什么?

MyBatis 有三种基本的 Executor 执行器:

  1. SimpleExecutor:简单执行器,每执行一次 SQL 语句就创建一个新的 Statement 对象,用完即关闭。适用于简单的数据库操作场景,但性能相对较低,因为每次执行都要进行数据库连接和 Statement 创建与关闭的操作。
  2. ReuseExecutor:可重用执行器,会缓存 Statement 对象,对于相同的 SQL 语句会重复使用已创建的 Statement 对象,减少了 Statement 的创建和销毁次数,提高了性能。适用于频繁执行相同 SQL 语句的场景。
  3. BatchExecutor:批处理执行器,用于执行批量 SQL 操作。它会将多个 SQL 语句收集起来,一次性发送给数据库执行,减少了数据库的交互次数,提高了批量操作的性能。适用于需要进行大量数据插入、更新等批量操作的场景。

MyBatis 三种执行器区别如下:

SimpleExecutor(简单执行器):每次执行 SQL 都创建新 Statement 并立即关闭,性能低,适用于少量不频繁操作。

ReuseExecutor(可重用执行器):缓存 Statement,相同 SQL 重用,减少创建销毁次数,提高性能,适用于频繁执行相同或相似 SQL 场景。

BatchExecutor(批处理执行器):收集多个 SQL 一次性执行,减少交互次数,提高批量操作性能,适用于大量数据批量操作场景。
MyBatis 中如何指定使用哪一种 Executor 执行器
在 MyBatis 的配置文件中可以通过 defaultExecutorType 属性来指定默认的执行器类型

另外,针对每个 <select>、<insert>、<update>、<delete> 标签,也可以通过 executorType 属性来指定该 SQL 语句使用的执行器类型,如:

<select id="selectUserById" resultType="User" executorType="BATCH">
  SELECT * FROM user WHERE id = #{id}
</select>

8、MyBatis 是如何进行分页的?分页插件的原理是什么?

  1. MyBatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页;

  2. MyBatis可以使用SQL语句中的LIMIT关键字实现简单的分页,但是对于大数据量的分页查询,需要使用更高效的方法。

  3. MyBatis提供了一种基于拦截器的分页插件来优化分页查询。

分页插件原理

分页插件通常是通过拦截 MyBatis 的 SQL 执行过程来实现分页功能的。以常见的分页插件 PageHelper 为例,其原理如下:

  1. 插件注册:

    • 在 MyBatis 的配置文件中注册分页插件,使其能够拦截 MyBatis 的 SQL 执行。
  2. SQL 拦截:

    • 当执行查询 SQL 时,分页插件会拦截这个 SQL,并在 SQL 中添加分页相关的语句。
    • 例如,如果是 MySQL 数据库,插件会在原始 SQL 后面添加 LIMITOFFSET 关键字,根据传入的分页参数来确定每页的记录数和起始位置。
  3. 结果集处理:

    • 分页插件还会对查询结果进行处理,只返回当前页的数据,并提供一些分页相关的信息,如总记录数、总页数等。
  4. 与 MyBatis 集成:

    • 分页插件通过实现 MyBatis 的插件接口,与 MyBatis 无缝集成,对开发人员来说,使用分页插件就像使用 MyBatis 的原生功能一样简单。

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

Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。

它的原理是:
在执行主查询语句时,仅仅查询主表的数据,而不去查询关联表的数据,将关联表的查询延迟到实际需要使用关联对象时再去查询。MyBatis 通过使用代理模式,在查询主表数据时创建代理对象,当实际需要使用关联对象时,再去查询关联表数据并设置到代理对象中,从而实现了延迟加载。

在具体实现时,MyBatis 通过 CGLIB 或 JDK 动态代理来创建代理对象,当代理对象的方法被调用时,会触发代理拦截器的方法。拦截器会检查关联对象是否已经加载,如果未加载,则发送延迟加载的 SQL 语句查询关联表数据,并将查询结果设置到代理对象中。这样,在实际使用关联对象时,就可以避免不必要的关联表查询,从而提高查询性能。

需要注意的是,MyBatis 的延迟加载功能需要配合 lazyLoadingEnabled 属性一起使用,并且需要遵循一些规范和约束,如不能在延迟加载时关闭 SqlSession,不能使用多线程并发访问等。同时,在使用延迟加载时,也需要注意潜在的 N+1 查询问题,需要合理设计 SQL 查询语句,避免不必要的性能损耗。

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

MyBatis 动态 SQL 是为了解决 SQL 语句灵活性不足的问题而提出的一种技术。动态 SQL 可以根据条件拼接 SQL 语句,从而满足不同的查询需求。MyBatis 提供了以下几种动态 SQL 标签:

  1. <if>:条件判断标签,当条件成立时才执行其中的 SQL 语句。
  2. <choose>、<when>、<otherwise>:选择判断标签,根据条件选择不同的 SQL 语句执行。
  3. <trim>、<where>、<set>:SQL 语句修饰标签,用于在 SQL 语句的前后加上修饰字符,如 WHERE、AND、OR 等。
  4. <foreach>:遍历标签,用于遍历一个集合并将集合中的元素添加到 SQL 语句中。
    动态 SQL 的执行原理是 :
    当 MyBatis 执行动态 SQL 语句时,会将 SQL 语句和参数传递给 SQL 解析器进行解析。SQL 解析器会根据 SQL 语句中的动态标签和参数的值,生成一个完整的 SQL 语句。然后,MyBatis 将生成的 SQL 语句和参数传递给 JDBC 驱动程序进行执行。最终,JDBC 驱动程序将执行结果返回给 MyBatis。
    使用动态 SQL 可以使 SQL 语句更灵活、更具可读性和可维护性,也可以提高应用程序的性能和效率。但是,需要注意使用动态 SQL 时要避免 SQL 注入攻击。建议使用 MyBatis 提供的参数绑定功能,将参数值与 SQL 语句分离,从而避免 SQL 注入攻击。

11、MyBatis 能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别

是的,MyBatis 可以执行一对一、一对多的关联查询。

在 MyBatis 中,实现一对一、一对多关联查询的方式有两种:

  1. 嵌套查询(Nested Query):通过在 ResultMap 中定义关联对象的 ResultMap,然后在 SQL 语句中使用嵌套查询(通常使用子查询)来完成关联查询。这种方式适用于数据量较小的场景,但如果数据量很大,可能会影响查询性能。
  2. 嵌套结果(Nested Results):通过在 ResultMap 中使用 Association 和 Collection 标签,定义关联对象的 ResultMap,然后通过查询结果集的方式来完成关联查询。这种方式适用于数据量较大的场景,因为它可以在一次 SQL 查询中完成关联查询,从而提高查询性能。
    两种方式的区别在于执行方式和性能。嵌套查询会执行多条 SQL 语句,每个 SQL 语句返回一个结果集,最终将结果集合并为一个对象;而嵌套结果只执行一条 SQL 语句,返回一个结果集,然后将结果集中的数据映射到对象中。

总的来说,如果数据量较小,建议使用嵌套查询;如果数据量较大,建议使用嵌套结果。但需要注意的是,使用嵌套结果时需要在 SQL 语句中使用 JOIN 操作,所以需要对 SQL 语句的性能进行优化,以避免出现慢查询的情况。同时,在定义 ResultMap 和 SQL 语句时需要注意标签的使用和语句的正确性,以确保关联查询的正确性和性能。

12 、MyBatis 的 xml 映射文件中,不同的 xml 映射文件的id 是否可以重复

不同的 xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复;毕竟 namespace 不是必须的,只是最佳实践而已。

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

13、MyBatis 是否可以映射 Enum 枚举类

是的,MyBatis可以映射Java中的Enum枚举类。MyBatis提供了两种方式来映射Enum枚举类:

1.使用EnumTypeHandler:EnumTypeHandler是MyBatis内置的类型处理器之一,它可以将Java中的Enum枚举类与数据库中的数据进行相互转换。

例如,在Mapper XML文件中,可以这样使用EnumTypeHandler来映射枚举类型:

<resultMap id="userResultMap" type="User">
  <id property="id" column="id" />
  <result property="gender" column="gender" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
</resultMap>

在这个例子中,我们将数据库中的gender列与Java中的User类的gender属性进行映射,并使用EnumTypeHandler类型处理器进行转换。

2.使用注解@EnumValue:如果在枚举类中使用了@EnumValue注解,则MyBatis会自动将枚举值与数据库中的数据进行相互转换。

例如,在定义枚举类时,可以使用@EnumValue注解来标识该枚举值对应的数据库中的值:

public enum Gender {
  MALE("M"),
  FEMALE("F");
 
  private String value;
 
  Gender(String value) {
    this.value = value;
  }
 
  @EnumValue
  public String getValue() {
    return value;
  }
}

在这个例子中,我们将MALE枚举值对应的数据库值设为"M",将FEMALE枚举值对应的数据库值设为"F"。然后,在Mapper XML文件中,就可以直接使用枚举类型进行映射,例如:

<resultMap id="userResultMap" type="User">
  <id property="id" column="id" />
  <result property="gender" column="gender"/>
</resultMap>

14、简述 MyBatis 的 xml 映射文件和 MyBatis 内部数据结构之间的映射关系

MyBatis 将 XML 映射文件中的配置信息映射成 Configuration 对象,该对象是 MyBatis 中重要的数据结构之一,包含了所有的配置信息,包括数据库连接信息、映射文件信息、缓存配置信息等等。Configuration 对象内部包含许多其他对象,包括:

  • MappedStatement:封装了一个 SQL 语句的信息,如 SQL 语句、输入参数、输出结果等。
  • SqlSource:封装了一个 SQL 语句的信息,如 SQL 语句、输入参数等,但不包含输出结果。
  • BoundSql:表示绑定了 SQL 语句和实际参数值的 SQL 语句,包含了 SQL 语句、参数值等信息。
  • ParameterMap:表示参数映射关系的对象,包含了参数的名称、类型等信息。
  • ParameterMapping:表示一个参数映射关系,包含了参数名称、类型等信息。
  • ResultMap:表示结果集映射关系的对象,包含了结果集字段与 Java 对象属性之间的映射关系。
  • ResultMapping:表示一个结果集映射关系,包含了结果集字段与 Java 对象属性之间的映射关系。
    在解析 XML 映射文件时,MyBatis 会将 <select>、<insert>、<update>、<delete> 等标签解析成 MappedStatement 对象,将 <resultMap> 标签解析成 ResultMap 对象,将 <parameterMap> 标签解析成 ParameterMap 对象,将 SQL 语句解析成 SqlSource 对象,将 SQL 语句和实际参数值绑定后的结果解析成 BoundSql 对象,并将这些对象保存在 Configuration 对象中。通过这些对象,MyBatis 可以在执行 SQL 语句时进行参数映射、结果集映射等操作。

15、Mybatis 的一级、二级缓存

  1. 一级缓存是指MyBatis在同一个SqlSession中执行相同SQL时,会把查询到的结果缓存到内存中。当下次查询相同SQL时,会直接从缓存中获取数据,避免了重复查询数据库,提高了查询效率。一级缓存是默认开启的,也不需要进行额外配置。

  2. 二级缓存是指MyBatis在多个SqlSession之间共享缓存。它可以避免多个SqlSession重复查询同一条数据,提高了应用的性能。但是,使用二级缓存需要进行额外的配置,包括在mapper.xml文件中配置标签以及在MyBatis的配置文件中开启二级缓存。需要注意的是,二级缓存仅仅是对查询结果进行缓存,对于insert、update、delete等操作,并不会清空缓存。

需要注意的是,虽然缓存可以提高应用的性能,但是缓存也有可能带来一些问题,如数据不一致等。因此,在使用缓存时需要根据具体情况进行合理的配置。

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

MyBatis 的插件机制可以在某些语句执行前、后拦截并执行自定义的处理逻辑,可以用于日志记录、参数处理、数据加密等功能。其实现原理是基于 Java 动态代理技术实现的。

MyBatis 插件机制涉及以下两个接口:

  • Interceptor:定义了拦截器的基本方法,包括 intercept()、plugin()、setProperties() 等。
  • Invocation:定义了被拦截方法的基本信息和执行方法的 invoke() 方法。
    当创建 SqlSession 对象时,MyBatis 会将插件按顺序进行包装,最终生成一个包含了所有插件功能的代理对象,当执行 SQL 语句时,会先经过代理对象,然后在代理对象中执行插件逻辑。

自定义插件需要实现 Interceptor 接口,并且实现 intercept() 方法来拦截需要增强的方法,然后使用 @Intercepts@Signature 注解对拦截器进行配置。

例如,定义一个打印 SQL 执行时间的插件,实现过程如下:

1.创建自定义插件类实现 Interceptor 接口

@Intercepts(@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}))
public class PrintSqlTimeInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = invocation.proceed();
        long endTime = System.currentTimeMillis();
        long sqlTime = endTime - startTime;
        if (sqlTime > 1000) {
            System.out.println("SQL execution time is too long: " + sqlTime + "ms");
        }
        return result;
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {}
}

2.使用 @Intercepts@Signature 注解对拦截器进行配置。

@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MyPlugin implements Interceptor {
    //...
}

3.在 MyBatis 配置文件中配置插件

<plugins>
    <plugin interceptor="com.example.PrintSqlTimeInterceptor"/>
</plugins>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_30885871

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值