MyBatis面试题必问: Mybatis一、二级缓存及其优缺点

一、Mybatis基础知识

1. Mybatis概要

1.1 Mybatis理解

如果没有MyBatis的支持,大家是怎么实现通过程序控制数据库的?首先我们需要为程序引入MySQL连接依赖mysql-connector.jar,加载数据库JDBC驱动,接着创建数据库连接对象Connection、SQL语句执行器Statement,再把SQL语句发送到MySQL执行,最后关闭SQL语句执行器和数据库连接对象。

整个过程是比较繁琐的,这是通过JDBC操作MySQL必走的过程。可实际开发可给不了你那么多时间,如果大家非要用JDBC去写大量的冗余代码也可以,能抗住催你开发进度的压力就行。

这是JDBC操作的过程。

public class JDBCController {
    private static final String db_url = "jdbc:mysql://localhost:3306/db_user";
    private static final String user = "root";
    private static final String password = "root";
    
    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;
        String sql = "select * from user order by id desc";
        try {
            connection = DriverManager.getConnection(db_url, user, password);
            statement = connection.createStatement();
            int result = statement.executeUpdate(sql);
            System.out.println(result);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

MyBatis能帮助我们什么?早在2002年,MyBatis的前身iBatis诞生,并于2010年改名为MyBatis。该框架引入了SQL映射作为持久层开发的一种方法,也就是说我们不需要把SQL耦合在代码里,只需要把SQL语句单独写在XML配置文件中。

以下是MyBatis编写SQL的写法。SQL的编写已经和程序运行分离开,消除了大量JDBC冗余代码,同时MyBatis还能和Spring框架集成。整个SQL编写的流程变得更加灵活也更加规范化

@Mapper
public interface UserMapper extends BatchMapper<UserDO> {
    List<UserDO> selectAllUser();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.JavaGetOffer.UserDO">

    <select id="selectAllUser" resultType="org.JavaGetOffer.UserDO">
        select * from user order by id desc
    </select>
    
</mapper>

1.2 SqlSession是什么

从我们偷偷访问某个小网站开始,到我们不耐烦地关闭浏览器或者退出登录时,我们作为用户和网站的一次会话就结束了。MyBaits框架要访问数据库同样要与数据库建立通信桥梁,而SqlSession对象表示的就是MyBaits框架与数据库建立的会话

我们可以利用SqlSession来操作数据库,如下代码。

    @Test
    public void testMybatis() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<UserDO> userList = userMapper.listAllUser();
        System.out.println(JSON.toJSONString(userList));
    }

2. Mybatis缓存

2.1 Mybatis缓存分类

软件系统合理使用缓存有一个好处。有了缓存,在原始数据没有更新的情况下,我们不需要重新再去获取一遍数据,这也减少了数据库IO,达到提升数据库性能的目的。

MyBatis同样提供了两个级别的缓存,一级缓存是基于上文提到的SqlSession实现,二级缓存是基于Mapper实现。

一级缓存作用在同一个SqlSession对象中,当SqlSession对象失效则一级缓存也跟着失效。我们梳理下一级缓存的生命周期。首先第一次查询时会把查询结果写入SqlSession缓存,如果第二次查询时原始数据没有改变则会读取缓存,但如果是修改、删除、添加语句的执行,那SqlSession缓存会被全部清空掉,这也是为了防止脏读的出现。

一级缓存缓存底层使用的是一个简单的Map数据结构来存储缓存,其中key为SQL + 参数、val为查询结果集。一级缓存的生命周期如下。

二级缓存的作用域是同一个命名空间namespace的Mapper对象,也就是说同一个Mapper下的多个SqlSession是可以共用二级缓存的。二级缓存的缓存写入、清空流程和一级缓存相似,但二级缓存的生命周期是和应用程序的生命周期一致的。为什么?因为Mybatis框架与Spring IOC集成的Mapper对象是单例对象。

另外大家还需要注意下,Mybatis的一级缓存是默认开启的且不能关闭,而二级缓存则需要我们手动开启,我们需要在配置文件中配置cacheEnabled参数。

<configuration>
  <settings>
    <setting name="cacheEnabled" value="true"/>
  </settings>

2.2 Mybatis缓存局限性

缓存是好,就是问题有点多,目前大厂大都禁止了Mybatis缓存的使用。

南哥总结了下,主要有以下原因。

(1)适用场景少

Mybatis二级缓存更适用于读多写少的业务场景,但是对于细粒度的缓存支持并不友好。举个用烂了的商城例子,每个商品信息的更新是非常频繁的的,而让用户每次都看到的是最新的商品信息又非常重要。

在同一个namespace的Mapper中一般会包含多个商品信息的二级缓存,只要有某一个商品信息更新了,则所有商品缓存都会全部失效。那其实在这个业务场景中,二级缓存的存在已经没有多大必要了,还反而增加了系统复杂性。

(2)数据不一致性问题

如果多个不同namespace的Mapper都共同操作同一个数据库表的情况下,第一个Mapper更新了数据库表会清空它本身的二级缓存,但其他namespace的Mapper是没有感知的,仍然缓存的是旧数据,数据不一致的问题就出现了。

(3)不适用于分布式系统

现在还用单机部署的业务已经不多了,大家都紧跟潮流搭了个分布式、高可用的系统。在分布式系统中,如果每个节点都使用自己的本地缓存,假如现在节点A更新了缓存,但节点B、节点C是不会进行同步更新的,同样产生了数据不一致的问题。

3. Mybatis分页插件

Mybatis分页的原理其实很简单,没有想象的那么复杂。我们只需要拦截SQL查询语句,再把SQL语句作为子查询,外面包裹一层SELECT * FROM后再加上LIMIT的分页约束语句。

如下SQL示例,确实挺简单的。

SELECT * FROM user
SELECT u.* FROM (SELECT * FROM user) u LIMIT M, N

二、Mybatis常见面试题

1. 什么是 MyBatis?它与 Hibernate 有什么区别?

回答: MyBatis 是一个持久层框架,它通过 XML 或注解的方式将 SQL 语句与 Java 对象进行映射。与 Hibernate 不同,MyBatis 不完全采用 ORM(对象关系映射)思想,而是更加灵活地直接编写 SQL。MyBatis 适合处理复杂 SQL 场景,而 Hibernate 更适合简单的 CRUD 操作和全自动的对象映射。

2. MyBatis 的核心组件有哪些?

回答: MyBatis 的核心组件包括 SqlSessionFactorySqlSessionMapper 接口和 ExecutorSqlSessionFactory 用于创建 SqlSession 对象,SqlSession 是 MyBatis 执行 SQL 操作的接口,Mapper 接口用于定义 SQL 映射关系,Executor 负责执行 SQL 语句并返回结果。

3. MyBatis 是如何实现 SQL 映射的?

回答: MyBatis 通过 XML 文件或注解将 SQL 语句与 Java 对象的属性进行映射。XML 映射文件中,<select><insert><update><delete> 等标签用于定义 SQL 语句,并通过 resultTyperesultMap 将查询结果映射到 Java 对象中。注解方式则通过 @Select@Insert 等注解直接在接口方法上定义 SQL。

4. MyBatis 的 #$ 符号有什么区别?

回答: # 符号用于安全的参数替换,它会将参数值作为预编译语句的一部分,防止 SQL 注入攻击;而 $ 符号会直接将参数值拼接到 SQL 语句中,不进行任何处理,因此容易引发 SQL 注入风险。例如:

SELECT * FROM user WHERE id = #{id} -- 安全 SELECT * FROM user WHERE id = ${id} -- 不安全

5. MyBatis 如何处理多表关联查询?

回答: MyBatis 通过 resultMapassociationcollection 标签实现多表关联查询。association 用于一对一关系,collection 用于一对多关系。例如:

<resultMap id="UserResultMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <association property="address" javaType="Address" column="address_id" select="selectAddress"/>
</resultMap>

6. 如何在 MyBatis 中实现分页?

回答: MyBatis 提供了多种分页实现方式,常用的有手动分页、数据库分页(如 LIMIT 关键字)和使用分页插件(如 PageHelper)。例如,使用 LIMIT 关键字实现分页:

SELECT * FROM user LIMIT #{offset}, #{limit}

7. 什么是 MyBatis 中的动态 SQL?如何使用?

回答: 动态 SQL 是指根据条件动态生成 SQL 语句,避免硬编码多个 SQL 语句。MyBatis 提供了 <if><choose><when><otherwise><trim> 等标签,用于在 XML 配置文件中编写动态 SQL。例如:

<select id="findUserByConditions" resultType="User">
    SELECT * FROM user WHERE 1=1
    <if test="username != null">
        AND username = #{username}
    </if>
    <if test="age != null">
        AND age = #{age}
    </if>
</select>

8. MyBatis 中的二级缓存是如何工作的?

回答: MyBatis 提供一级缓存(SqlSession 级别)和二级缓存(Mapper 级别)。二级缓存是跨 SqlSession 的,所有 SqlSession 对象共享这部分缓存。二级缓存需要手动配置,可以通过在 Mapper XML 文件中使用 <cache> 标签来开启。示例:

<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

9. MyBatis 中如何实现批量操作?

回答: MyBatis 可以通过 foreach 标签和 batch 模式实现批量操作。foreach 标签用于在 XML 文件中遍历集合,batch 模式通过设置 ExecutorType.BATCH 来执行批量更新操作。例如:

<insert id="insertUsers"> 
    INSERT INTO user (username, age) VALUES 
    <foreach collection="list" item="user" separator=","> 
        (#{user.username}, #{user.age}) 
    </foreach> 
</insert>

10. 如何在 MyBatis 中使用注解方式实现映射?

回答: MyBatis 支持通过注解方式定义 SQL 映射。常用注解包括 @Select@Insert@Update@Delete 以及 @Results@Result 等。例如:

@Select("SELECT * FROM user WHERE id = #{id}") @Results({ @Result(property = "id", column = "id"), @Result(property = "username", column = "username") }) User findById(int id);

11. MyBatis 中的 Mapper 接口和 XML 映射文件如何关联?

回答: Mapper 接口和 XML 映射文件通过相同的接口名称和 XML 文件名进行关联。MyBatis 会根据接口的全限定名去查找对应的 XML 文件,并将 XML 文件中的 SQL 映射到接口方法。例如,如果有 UserMapper 接口,那么对应的 XML 文件通常命名为 UserMapper.xml

12. MyBatis 中的延迟加载是如何实现的?

回答: MyBatis 的延迟加载通过设置 lazyLoadingEnabledaggressiveLazyLoading 来实现。当 lazyLoadingEnabledtrue 时,关联对象会在被访问时才加载。通过在 <settings> 中配置:

<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>

13. 什么是 MyBatis 的 resultMap?它的作用是什么?

回答: resultMap 是 MyBatis 用于定义结果集映射的高级配置。它允许更复杂的映射规则,例如字段与属性名称不一致、一对一和一对多的关联映射。resultMap 使得 SQL 查询结果能够灵活地映射到 Java 对象上。

14. 如何在 MyBatis 中处理复杂的查询结果?

回答: 处理复杂查询结果时,可以使用 resultMap 配合 associationcollection 标签来映射复杂的对象结构。对于多表关联查询,association 用于一对一映射,collection 用于一对多映射,discriminator 用于继承映射。

15. MyBatis 支持哪些数据库?

回答: MyBatis 支持几乎所有主流关系型数据库,包括但不限于 MySQL、PostgreSQL、Oracle、SQL Server、DB2、SQLite 等。MyBatis 依赖于 JDBC 驱动程序,因此只要有对应的 JDBC 驱动,MyBatis 就能与之兼容。

16. MyBatis 的 SqlSession 是线程安全的吗?

回答: SqlSession 不是线程安全的,它应该在每个线程中单独创建并使用。一般推荐使用 try-with-resources 或手动关闭 SqlSession 来确保资源的释放。在 Web 应用中,可以使用事务管理器来管理 SqlSession 的生命周期。

17. 如何在 MyBatis 中实现乐观锁?

回答: MyBatis 可以通过在 SQL 中使用版本号字段实现乐观锁。通常在表中添加一个 version 字段,每次更新时检查并更新该字段。例如:

UPDATE user SET username = #{username}, version = version + 1 WHERE id = #{id} AND version = #{version}

18. 如何在 MyBatis 中进行事务管理?

回答: MyBatis 本身不处理事务管理,但可以通过 Spring 框架或手动管理事务。在 Spring 中,可以通过 @Transactional 注解自动管理事务。MyBatis 也可以使用 SqlSessioncommit()rollback() 方法手动管理事务。

19. 如何在 MyBatis 中实现读写分离?

回答: MyBatis 可以通过配置多个数据源来实现读写分离。使用 Spring 的 AbstractRoutingDataSource 来动态切换数据源,配置读写分离的规则。MyBatis 通过 SqlSessionFactory 的配置来选择不同的数据源。

20. MyBatis 的 Mapper 接口如何与 Spring 集成?

回答: MyBatis 可以通过 @Mapper 注解或 MapperScannerConfigurer 来与 Spring 集成。在 Spring Boot 项目中,只需在启动类中添加 @MapperScan 注解,MyBatis 会自动扫描并注册 Mapper 接口。

21. 如何处理 MyBatis 的 N+1 查询问题?

回答: MyBatis 的 N+1 查询问题通常出现在一对多查询中,解决方法是通过 join 查询或在 select 标签中使用 fetchType=EAGER 来预先加载数据,从而避免多次查询。

22. MyBatis 中如何处理枚举类型?

回答: MyBatis 可以通过自定义 TypeHandler 来处理枚举类型,将数据库中的数值映射为 Java 枚举。还可以在 XML 文件中通过 typeHandler 属性指定 TypeHandler

23. 如何在 MyBatis 中配置全局属性?

回答: MyBatis 支持在全局配置文件 mybatis-config.xml 中配置全局属性,如 cacheEnabledlazyLoadingEnabledmapUnderscoreToCamelCase 等。配置示例如下:

<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> </settings>

24. 如何在 MyBatis 中使用存储过程?

回答: MyBatis 支持通过 XML 映射文件调用存储过程。使用 call 关键字调用存储过程,并通过 @Param 注解传递参数。例如:

<select id="callProcedure" statementType="CALLABLE"> {call my_procedure(#{param1, mode=IN}, #{param2, mode=OUT})} </select>

25. MyBatis 中如何进行 SQL 性能优化?

回答: MyBatis 的 SQL 性能优化可以通过以下方式实现:

  • 使用索引和优化 SQL 查询语句;
  • 使用 MyBatis 的缓存机制;
  • 避免 N+1 查询问题;
  • 通过 SQL 日志分析和优化慢查询。

让我们一起学习,一起进步!期待在评论区与你们见面。

祝学习愉快!

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值