面试官:MyBatis中resultMap的实现原理是什么?彻底懵逼了。。

本文详细解释了MyBatis中resultMap的工作原理,涉及自动映射、TypeHandler的作用,以及如何处理复杂查询、分页查询、性能优化和资源管理,包括连接池、事务和资源清理策略。

在这里插入图片描述

大家好,我是哪吒。

面试的时候,被问到 “MyBatis中resultMap的实现原理是什么?”

我的第一反应就是,resultMap不就是进行自动映射的嘛!还有原理?

MyBatis支持自动映射,可以根据查询结果的列名和Java对象的属性名自动匹配。在使用自动映射时,结果集中的列名会与Java对象的属性名进行匹配,无需在Mapper XML文件中手动配置映射关系,简化了开发。

通过标签配置查询结果集与Java对象之间的映射关系,或者使用指定查询结果映射到的Java对象类型。在insert、update、delete等操作中,也会使用和标签配置参数与SQL语句中的占位符之间的映射关系。

通过TypeHandler实现Java类型和数据库字段类型之间的转换。MyBatis提供了一些内置的TypeHandler,同时也允许用户自定义TypeHandler来处理特定的转换逻辑,确保数据库字段的数据类型正确地映射到Java对象的属性类型。

MyBatis的TypeHandler是一个接口,用于处理Java类型与JDBC类型之间的转换。

当执行SQL语句时,如果语句中包含了参数占位符(如#{param}),MyBatis会使用TypeHandler将Java方法参数的类型转换为JDBC可以理解的SQL类型,以便在数据库操作中使用。

查询数据库后,MyBatis会使用TypeHandler将结果集中的数据从SQL类型转换为Java对象的属性类型,这样就能够将查询结果映射到Java对象上。

TypeHandler主要用于处理单个属性的映射,而resultMap则用于处理整个结果集的映射。

TypeHandler 是用于处理单个Java对象属性与数据库字段之间的映射。它负责将Java对象的属性值转换为JDBC可以理解的类型,以及将查询结果从JDBC类型转换回Java对象的属性类型。这种转换是基于Java类型和JDBC类型之间的映射关系来实现的。

resultMap 是用来定义如何将整个查询结果集映射到Java对象。它提供了更复杂的映射能力,允许开发者自定义如何将数据库列名映射到Java对象的属性上,适用于复杂的数据结构,如关联查询的结果。

再分享几道关于resultMap的问题。

1、当查询结果集包含多个表的联合查询时,如何使用resultMap将这些结果映射到Java对象?

在Mapper XML文件中定义resultMap,将查询结果映射到Java对象上。可以使用元素来处理关联查询的结果,使用元素来处理集合类型的属性。

<resultMap id="userOrderResult" type="com.example.UserOrder">
    <id property="id" column="user_id"/>
    <result property="username" column="username"/>
    <result property="email" column="email"/>
    <collection property="orders" ofType="com.example.Order">
        <id property="orderId" column="order_id"/>
        <result property="productName" column="product_name"/>
        <result property="price" column="price"/>
    </collection>
</resultMap>

在执行查询操作时,引用这个resultMap

<select id="getUserOrders" resultMap="userOrderResult">
    SELECT u.id as user_id, u.username, u.email, o.id as order_id, o.product_name, o.price
    FROM user u
    LEFT JOIN order o ON u.id = o.user_id
    WHERE u.id = #{userId}
</select>

这样,MyBatis就会根据定义的resultMap将查询结果映射到Java对象上,并将这些对象返回给调用者。

2、如何使用MyBatis的resultMap进行分页查询?

在Mapper XML文件中定义resultMap,将查询结果映射到Java对象上。

<resultMap id="userResult" type="com.example.User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="email" column="email"/>
</resultMap>

在执行查询操作时,引用这个resultMap。

<select id="getUsersByPage" resultMap="userResult">
    SELECT * FROM user LIMIT #{offset}, #{pageSize}
</select>

其中,#{offset}表示偏移量,即从第几条记录开始查询;#{pageSize}表示每页显示的记录数。

最后,在调用该查询方法时,传入相应的参数即可实现分页查询。例如:

List<User> users = userMapper.getUsersByPage(0, 10); // 获取前10条记录

3、resultMap是如何提高整体性能的?

(1)可重用性

定义好的可以在多个查询语句中重复使用,提高了代码的复用性和维护性。而且,MyBatis还提供了继承的功能,进一步增加了配置的灵活性,使配置的重复利用更加便捷。

(2)二级缓存

可以帮助 MyBatis 识别并利用二级缓存(如与数据库查询结果的缓存),从而提高系统性能,减少对数据库的频繁访问。

(3)减少字段映射错误

通过明确定义,开发人员可以准确地指定查询结果集中的每列数据应该映射到哪个 Java 对象的属性上。这有助于避免因为字段名字或顺序变化而导致的映射错误,从而减少调试和排查错误的时间,提高开发效率。

4、MyBatis是如何实现PreparedStatement的?

MyBatis通过使用JDBC的PreparedStatement来实现预编译的SQL语句。

  1. 获取数据库连接:MyBatis首先需要获取一个数据库连接对象;
  2. 准备SQL语句:MyBatis会将用户传入的参数(如#{xxx})与SQL语句中的占位符(即"?")进行绑定。这个过程称为预编译,它允许MyBatis将参数安全地插入到SQL语句中,防止了SQL注入攻击。
  3. 创建PreparedStatement:MyBatis在执行SQL语句时,会为每次查询创建一个新的PreparedStatement对象。这个对象是JDBC API的一部分,它表示一种预编译的SQL语句,可以提高执行效率和安全性。
  4. 执行SQL语句:MyBatis使用PreparedStatement对象的executeQuery方法来执行SQL查询,并返回结果集。
  5. 处理结果集:MyBatis将结果集转换为Java对象,这个过程可以通过配置的resultMap来完成,它可以将数据库的列映射到Java对象的属性上。
  6. 资源释放:执行完毕后,MyBatis会负责关闭PreparedStatement和Connection等资源,以释放数据库连接。

5、MyBatis如何关闭数据库连接?

(1)连接池

MyBatis通常与连接池一起使用,连接池负责管理数据库连接的创建、分配和释放。当MyBatis需要执行SQL语句时,它会从连接池中获取一个可用的连接,而不是直接创建新的连接。

(2)事务管理

MyBatis提供了事务管理的功能,它会根据配置的事务策略(如提交或回滚)来处理数据库连接的关闭。如果事务提交成功,连接会被返回到连接池中;如果事务回滚,连接可能会被关闭。

(3)资源清理

MyBatis在执行SQL语句后,会负责关闭PreparedStatement和ResultSet等资源,以释放数据库连接。这可以通过配置的资源清理策略来实现,例如使用try-with-resources语句或显式调用close方法。

(4)配置参数

MyBatis的配置参数也会影响数据库连接的关闭行为。例如,可以设置是否自动提交事务、是否缓存查询结果等,这些都会影响连接的关闭时机和方式。

(5)异常处理

MyBatis在执行过程中可能会遇到各种异常,如SQL错误、网络中断等。在这些情况下,MyBatis会根据异常类型来决定如何处理数据库连接,例如重试、回滚或关闭连接。

(6)插件机制

MyBatis还支持插件机制,允许开发者自定义插件来拦截和处理数据库操作。通过插件,可以实现更复杂的资源管理和异常处理逻辑,例如自定义连接池、监控数据库性能等。

6、MyBatis如何管理连接池的?

MyBatis作为一个ORM框架,它本身并不直接管理数据库连接,而是通过配置数据源来实现。

(1)内置连接池

当在MyBatis配置文件中设置时,MyBatis会使用内置的连接池。这个连接池是在MyBatis内部实现的,它会在启动时初始化一定数量的数据库连接,并在需要时提供给MyBatis使用。

(2)第三方连接池

如果需要使用第三方的数据库连接池,如DBCP、C3P0、Druid或Hikari等,可以在MyBatis的配置中进行相应的设置。这些连接池通常提供更好的性能和更丰富的功能,如连接监控和统计等。

(3)UnpooledDataSource

如果不希望使用连接池,可以将数据源类型设置为UNPOOLED。

这种情况下,MyBatis会为每次查询创建一个新的数据库连接,并在查询结束后关闭该连接。

不推荐使用。

(4)事务管理

MyBatis的事务管理也是连接池管理的一部分。当使用事务时,MyBatis会根据事务的开始和结束来控制连接的获取和释放。如果事务成功提交,连接会被返回到连接池中;如果事务回滚,连接可能会被关闭。

(5)资源清理

MyBatis在执行SQL语句后,会负责关闭PreparedStatement和ResultSet等资源,以释放数据库连接。这可以通过配置的资源清理策略来实现,例如使用try-with-resources语句或显式调用close方法。

7、如何理解MyBatis中的资源清理策略?

(1)更新数据时清除缓存

当执行数据更新操作(如INSERT、UPDATE或DELETE)时,应确保相关的缓存被及时清除或更新,以避免脏读或数据不一致的情况发生。

(2)合理配置缓存大小

为了避免缓存占用过多内存,应根据应用的需求和服务器的性能来合理配置缓存的大小和清除策略。

(3)处理缓存并发问题

在并发环境下,需要特别注意缓存可能引起的问题,如脏读或数据不一致。可以通过配置事务隔离级别或者使用乐观锁等机制来减少并发问题的发生。

(4)关闭自动提交

为了防止每次执行SQL语句后都自动提交事务,导致频繁地打开和关闭数据库连接,可以在MyBatis的配置中关闭自动提交功能。

(5)使用合适的资源清理策略

MyBatis提供了不同的资源清理策略,如基于时间、基于空间或基于计数等。选择合适的策略可以帮助有效地管理资源,避免资源泄露。

8、如何在MyBatis中配置资源清理策略?

(1)数据库连接资源管理

MyBatis本身不提供连接池,但可以与第三方连接池整合,比如常用的 c3p0、Druid 等。通过配置连接池的参数,可以控制连接的获取和释放、最大连接数、空闲连接超时等。

(2)缓存资源管理

MyBatis 支持二级缓存,可以在标签中配置二级缓存的属性,包括缓存刷新间隔、缓存过期时间等。

<!-- 开启二级缓存 -->
<cache eviction="FIFO" flushInterval="60000" size="512"/>

(3)开启自动提交

设置在执行查询语句后自动提交事务,这样可以及时释放资源。

<setting name="autoCommit" value="true"/>

(4)使用SqlSessionFactoryBuilder建造者模式

使用 SqlSessionFactoryBuilder 来创建 SqlSessionFactory,在创建完成后对其进行立即销毁操作,这样可以释放资源并避免资源泄露。

SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader);
// 使用 factory 创建 SqlSession
factory.close();

(5)手动释放资源

在使用完毕后,显式调用相关资源的关闭方法,如关闭 SqlSession、清理缓存等。

SqlSession session = sqlSessionFactory.openSession();
// 执行数据库操作
session.close();

3万字80道Java经典面试题总结(2024修订版)- Java基础篇

2 万字 42 道Java经典面试题总结(2024修订版)- Java集合篇

4 万字 102 道Java经典面试题总结(2024修订版)- 多线程篇

10万字208道Java经典面试题总结(2024修订版)- SSM篇


🏆文章收录于:100天精通Java从入门到就业

全网最细Java零基础手把手入门教程,系列课程包括:Java基础、Java8新特性、Java集合、高并发、性能优化等,适合零基础和进阶提升的同学。

🏆哪吒多年工作总结:Java学习路线总结,搬砖工逆袭Java架构师

华为OD机试 2023B卷题库疯狂收录中,刷题点这里

刷的越多,抽中的概率越大,每一题都有详细的答题思路、详细的代码注释、样例测试,发现新题目,随时更新,全天优快云在线答疑。

### ### 1. 什么是 MyBatis?它的核心特点是什么? MyBatis 是一个基于 Java 的持久层框架,通过简化数据库操作和 SQL 映射,使开发者能够更灵活地控制 SQL 语句的执行过程。MyBatis 的核心特点包括: - **SQL 与业务代码分离**:将 SQL 语句统一放在 XML 配置文件中,便于统一维护[^2]。 - **灵活的映射机制**:支持将数据库结果映射Java 对象、HashMap 或基本数据类型。 - **支持动态 SQL**:提供 `<if>`、`<choose>`、`<foreach>` 等标签,允许构建动态 SQL 语句。 - **轻量级**:没有复杂的依赖关系,易于集成到各种项目中。 ### ### 2. MyBatis 的优点和缺点分别是什么? **优点**: - **灵活性高**:开发者可以完全控制 SQL 语句,适合需要精细化 SQL 调优的场景。 - **易于维护**:SQL 与 Java 代码分离,方便统一管理和修改。 - **性能优越**:由于 SQL 是手动编写的,可以进行针对性优化,避免了 ORM 框架的冗余查询。 - **支持多种映射方式**:包括基于 XML 的映射和注解方式的映射。 **缺点**: - **需要手动编写 SQL**:对于复杂的业务逻辑,编写和维护大量 SQL 会增加开发成本。 - **不提供自动分页功能**:需要借助插件或手动实现分页逻辑。 - **不支持级联操作**:如一对一、一对多等关系需要手动处理映射。 ### ### 3. MyBatis 和 Hibernate 有哪些不同? | 特性 | MyBatis | Hibernate | |------------------|----------------------------------|----------------------------------| | 类型 | 半自动 ORM 框架 | 全自动 ORM 框架 | | SQL 控制 | 需要手动编写 SQL 语句 | 自动生成 SQL 语句 | | 性能 | 更高,适合需要优化 SQL 的场景 | 一般,适合快速开发 | | 映射机制 | 手动配置 SQL 与 Java 对象映射 | 自动映射数据库表与实体类 | | 适用场景 | 复杂查询、性能敏感的系统 | 快速开发、简单业务模型 | ### ### 4. `#{} `和 `${}` 的区别是什么? - `#{}`:使用预编译的方式处理参数,防止 SQL 注入攻击。MyBatis 会将参数值替换为 `?`,然后通过 `PreparedStatement` 设置参数值。例如: ```java @Select("SELECT * FROM users WHERE id = #{id}") User selectUser(int id); ``` - `${}`:直接拼接 SQL 字符串,不会进行预编译,存在 SQL 注入风险。适用于动态表名、排序字段等场景: ```java @Select("SELECT * FROM users ORDER BY ${columnName}") List<User> selectUsersOrderBy(String columnName); ``` ### ### 5. 当实体类中的属性名和表中的字段名不一样,怎么办? 可以通过以下两种方式解决字段名与属性名不一致的问题: 1. **使用别名**:在 SQL 查询中为字段设置别名,使其与实体类属性名一致: ```sql SELECT user_id AS id, user_name AS name FROM users; ``` 2. **配置 ResultMap**:在 XML 映射文件中定义 `<resultMap>`,手动指定字段与属性的映射关系: ```xml <resultMap id="userResultMap" type="User"> <id property="id" column="user_id"/> <result property="name" column="user_name"/> </resultMap> <select id="selectUser" resultMap="userResultMap"> SELECT * FROM users WHERE user_id = #{id} </select> ``` ### ### 6. 模糊查询 like 语句该怎么写? 模糊查询可以通过 `LIKE` 关键字实现,推荐使用 `CONCAT` 函数拼接通配符 `%`,以提高可读性和兼容性: ```xml <select id="searchUsers" resultType="User"> SELECT * FROM users WHERE user_name LIKE CONCAT('%', #{keyword}, '%') </select> ``` ### ### 7. Mapper 接口的工作原理是什么?方法能重载吗? Mapper 接口是 MyBatis 实现接口绑定的一种方式,其工作原理如下: - 接口方法名与 XML 文件中 `<select>`、`<insert>` 等标签的 `id` 属性对应。 - 接口全限定名与 XML 文件的 `namespace` 属性一致。 - MyBatis 使用动态代理生成接口的实现类,通过反射调用 SQL 语句。 关于重载问题,**Mapper 接口中的方法不能重载**,因为 MyBatis 是通过方法名和参数类型来绑定 SQL 语句的,重载会导致无法准确匹配 SQL。 ### ### 8. MyBatis 如何进行分页?分页插件的原理是什么? MyBatis 原生不支持自动分页,通常通过以下方式实现: 1. **手写分页 SQL**:在 SQL 中使用 `LIMIT` 和 `OFFSET` 实现分页: ```sql SELECT * FROM users LIMIT #{offset}, #{limit}; ``` 2. **使用分页插件**:如 PageHelper,它通过拦截 SQL 语句并自动添加分页条件来实现分页功能。其原理是基于 MyBatis 的插件机制,在执行 SQL 之前动态修改 SQL 语句。 ```java PageHelper.startPage(1, 10); List<User> users = userMapper.selectAll(); ``` ### ### 9. MyBatis 如何将 SQL 执行结果封装为目标对象并返回? MyBatis 提供了多种结果映射方式: - **自动映射**:如果字段名与属性名一致,MyBatis 会自动完成映射。 - **手动映射**:通过 `<resultMap>` 明确指定字段与属性的对应关系。 - **Map 映射**:返回类型为 `Map`,键为字段名,值为字段值。 ```xml <select id="selectUser" resultType="map"> SELECT * FROM users WHERE id = #{id} </select> ``` ### ### 10. MyBatis 如何执行批量操作? 批量操作通常通过 `<foreach>` 标签实现,例如批量插入: ```xml <insert id="batchInsert"> INSERT INTO users (name, email) VALUES <foreach collection="users" item="user" separator=","> (#{user.name}, #{user.email}) </foreach> </insert> ``` ### ### 11. 如何获取自动生成的主键值? 可以通过 `useGeneratedKeys` 和 `keyProperty` 属性获取自动生成的主键: ```xml <insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> INSERT INTO users (name, email) VALUES (#{name}, #{email}) </insert> ``` ### ### 12. 在 Mapper 中如何传递多个参数? 可以通过以下方式传递多个参数: 1. **使用 `@Param` 注解**: ```java @Select("SELECT * FROM users WHERE name = #{name} AND email = #{email}") User selectUser(@Param("name") String name, @Param("email") String email); ``` 2. **使用 Map 传递参数**: ```java Map<String, Object> params = new HashMap<>(); params.put("name", "John"); params.put("email", "john@example.com"); User user = userMapper.selectUser(params); ``` ### ### 13. MyBatis 动态 SQL 有什么用?有哪些常见标签? 动态 SQL 用于根据不同的条件构建不同的 SQL 语句,常用标签包括: - `<if>`:条件判断。 - `<choose>`、`<when>`、`<otherwise>`:类似 Java 的 `switch-case`。 - `<foreach>`:循环遍历集合。 - `<set>`:用于更新操作,自动处理逗号分隔。 - `<trim>`:去除 SQL 中的前缀或后缀。 ```xml <select id="searchUsers" resultType="User"> SELECT * FROM users <where> <if test="name != null"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="email != null"> AND email = #{email} </if> </where> </select> ``` ### ### 14. MyBatis 是否支持延迟加载?如果支持,其实现原理是什么? MyBatis 支持延迟加载(Lazy Loading),其实现原理是通过代理对象拦截对关联对象的访问,在真正需要时才执行对应的 SQL 查询。延迟加载可以通过配置 `<association>` 和 `<collection>` 的 `fetchType="lazy"` 来启用。 ```xml <resultMap id="userResultMap" type="User"> <id property="id" column="id"/> <association property="role" column="role_id" fetchType="lazy" select="com.example.mapper.RoleMapper.selectById"/> </resultMap> ``` ### ### 15. MyBatis 的一级缓存和二级缓存有什么区别? | 特性 | 一级缓存 | 二级缓存 | |----------------|------------------------------|----------------------------------| | 作用范围 | 同一个 `SqlSession` 内 | 多个 `SqlSession` 共享 | | 默认开启 | 是 | 否,需手动配置 | | 生命周期 | 与 `SqlSession` 一致 | 与 `SqlSessionFactory` 一致 | | 存储位置 | 内存 | 可配置为内存、Redis 等外部缓存 | ### ### 16. 为什么说 MyBatis 是半自动 ORM 框架?它与全自动 ORM 的区别在哪里? MyBatis 被称为半自动 ORM 框架,因为它需要开发者手动编写 SQL 语句,并通过映射文件或注解将 SQL 与 Java 对象进行绑定。相比之下,全自动 ORM 框架(如 Hibernate)会自动生成 SQL 并管理对象的生命周期。 - **全自动 ORM**:如 Hibernate,开发者几乎不需要编写 SQL,框架会自动处理 CRUD 操作。 - **半自动 ORM**:如 MyBatis,开发者需要编写 SQL,但可以获得更高的灵活性和性能控制能力。 ### ### 17. MyBatis 支持哪些 Executor 执行器?它们之间的区别是什么? MyBatis 提供了三种 Executor: - **SimpleExecutor**:默认执行器,每次执行 SQL 都会新建一个 Statement。 - **ReuseExecutor**:重用 Statement,适用于执行相同 SQL 多次的场景。 - **BatchExecutor**:用于批处理操作,提高批量插入或更新的效率。 可以通过 `sqlSession.getMapper()` 获取不同执行器: ```java SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); ``` ### ### 18. MyBatis 的插件运行原理是什么?如何编写一个插件? MyBatis 插件基于 Java 动态代理机制,通过拦截 `Executor`、`StatementHandler`、`ParameterHandler` 和 `ResultSetHandler` 四大核心接口的方法,实现对 SQL 执行过程的增强。 编写插件的基本步骤如下: 1. 实现 `Interceptor` 接口。 2. 使用 `@Intercepts` 和 `@Signature` 注解指定拦截的目标方法。 3. 在配置文件中注册插件。 ```java @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class MyInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 增强逻辑 return invocation.proceed(); } } ``` ### ### 19. 传统 JDBC 开发存在哪些问题?MyBatis 是如何解决的? 传统 JDBC 开发存在以下问题: - **代码冗余**:需要手动处理连接、事务、异常等。 - **SQL 与业务逻辑耦合**:SQL 直接写在 Java 代码中,难以维护。 - **结果集处理繁琐**:需要手动遍历 `ResultSet` 并映射Java 对象。 MyBatis 通过以下方式解决这些问题: - 封装 JDBC 操作,简化数据库连接和事务管理。 - 将 SQL 与 Java 代码分离,提升可维护性。 - 提供自动映射功能,简化结果集处理。 ### ### 20. 简述 MyBatis 的工作原理MyBatis 的工作原理主要包括以下几个步骤: 1. **加载配置文件**:读取 `mybatis-config.xml` 和映射文件。 2. **构建 `SqlSessionFactory`**:通过配置创建会话工厂。 3. **获取 `SqlSession`**:用于执行 SQL 语句。 4. **执行 SQL**:通过 `Executor` 执行 SQL 并处理参数。 5. **映射结果**:将数据库结果映射Java 对象并返回[^3]。 ```java SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); try { User user = sqlSession.selectOne("com.example.mapper.UserMapper.selectUser", 1); } finally { sqlSession.close(); } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

哪 吒

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

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

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

打赏作者

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

抵扣说明:

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

余额充值