mybaits
本文最后更新于41 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com

1、什么是 MyBatis?

核心解析

MyBatis 是一款半 ORM(对象关系映射)框架,本质是对 JDBC 的封装,解决 JDBC 开发中 “重复冗余代码” 的痛点。开发时只需关注 SQL 本身,无需手动处理 “加载驱动、创建连接、创建 Statement” 等繁琐步骤,同时支持通过 XML 或注解将 POJO(简单 Java 对象)与数据库记录映射,消除几乎所有 JDBC 冗余代码。

记忆要点

  • 定位:半 ORM 框架,封装 JDBC;
  • 核心价值:简化开发(免写 JDBC 冗余代码)、灵活可控(直接写原生 SQL);
  • 映射方式:XML 或注解,关联 POJO 与数据库。

2、MyBatis 的优缺点?

核心解析

MyBatis 的优势和劣势均源于其 “手动控制 SQL” 的设计理念,具体对比如下:

维度优点(核心:灵活、简化)缺点(核心:依赖 SQL)
SQL 控制基于原生 SQL 编程,灵活度高,不影响现有系统 / 数据库设计;SQL 与代码分离(XML 配置),便于管理SQL 编写工作量大(字段多、关联表多时更明显),对开发人员 SQL 功底有要求
开发效率比 JDBC 减少 50%+ 代码量,无需手动开关连接;支持动态 SQL、ORM 映射
兼容性依赖 JDBC,支持所有 JDBC 兼容的数据库SQL 依赖数据库语法(如 MySQL 的LIMIT、Oracle 的ROWNUM),数据库移植性差
集成能力可与 Spring 无缝集成

记忆要点

  • 优点记 “3 减 2 支持”:减代码、减冗余、减耦合;支持动态 SQL、支持多数据库;
  • 缺点记 “2 依赖”:依赖开发人员 SQL 能力、依赖具体数据库语法。

3、Hibernate 和 MyBatis 的区别?

核心解析

两者都是持久层框架,均封装 JDBC,但设计理念完全不同(“全自动” vs “半自动”),核心区别集中在映射粒度、SQL 控制、移植性三方面:

对比维度MyBatis(半自动 ORM)Hibernate(全自动 ORM)
映射关系映射 “Java 对象与 SQL 执行结果”,多表关联配置简单(需手动写关联 SQL)映射 “Java 对象与数据库表”,多表关联配置复杂(需配置对象间关联关系,如one-to-many
SQL 控制手动写原生 SQL,支持动态 SQL、存储过程,SQL 优化易(直接改 SQL)自动生成 SQL,提供 HQL(跨数据库查询语言),SQL 优化难(需理解 HQL 生成逻辑)
数据库移植性差(SQL 依赖数据库语法)好(HQL 跨数据库,无需改 SQL)
功能封装轻量,仅封装 JDBC 核心流程,无额外复杂特性(如级联较弱)重量级,封装完整(日志、缓存、强级联),但性能消耗略高

记忆要点

  • 相同点:都封装 JDBC,都是持久层框架,用于 DAO 层;
  • 不同点:MyBatis“手动 SQL + 结果映射”,Hibernate“自动 SQL + 表映射”;移植性 H 好,灵活性 My 好。

4、为什么说 MyBatis 是半自动 ORM 映射工具?与全自动的区别?

核心解析

ORM 的 “全自动 / 半自动” 核心判断标准是:查询关联对象时是否需要手动写 SQL

  • 全自动(如 Hibernate):根据对象关系模型直接获取关联对象。例如查询 “用户(User)” 时,若 User 关联 “订单(Order)”,调用user.getOrders()即可自动查询订单,无需手动写 SQL;
  • 半自动(MyBatis):查询关联对象必须手动编写 SQL。例如查询 “用户” 时,若要获取其关联的 “订单”,需在 XML 中写 “关联查询 SQL”(如SELECT u.*, o.* FROM user u LEFT JOIN order o ON u.id = o.user_id),或分两次查询(先查用户,再查订单)。

记忆要点

  • 关键判断:关联对象查询是否需手动写 SQL;
  • 对比:Hibernate“对象关系驱动,自动查关联”;MyBatis“SQL 驱动,手动查关联”。

5、传统 JDBC 开发存在什么问题?

核心解析

传统 JDBC 开发是 “手动操作全流程”,存在 4 个核心痛点,可按 “连接→SQL→参数→结果” 流程记忆:

  1. 连接管理混乱:频繁创建 / 释放数据库连接,浪费系统资源,影响性能(需手动实现连接池);
  2. SQL 硬编码:SQL 语句、参数设置、结果集处理写在 Java 代码中,SQL 变更需改代码、重新编译,维护成本高;
  3. 参数设置繁琐PreparedStatement传参需手动对应 “?” 位置(如ps.setString(1, name)),参数数量 / 顺序变了易出错;
  4. 结果集处理重复:需手动遍历ResultSet,将字段值逐个赋给 POJO,代码重复且繁琐。

记忆要点

  • 按 “连接→SQL→参数→结果” 四步记,每步对应一个痛点:连接浪费、SQL 硬编码、参数易错、结果处理繁。

6、JDBC 的不足,MyBatis 是如何解决的?

核心解析

MyBatis 针对 JDBC 的 4 个痛点,提供了 “配置化 + 自动化” 的解决方案,一一对应:

JDBC 痛点MyBatis 解决方案
连接管理混乱mybatis-config.xml中配置数据库连接池(如 Druid、C3P0),统一管理连接,避免频繁创建 / 释放
SQL 硬编码将 SQL 写在XXXMapper.xml中,与 Java 代码分离,SQL 变更只需改 XML,无需重新编译
参数设置繁琐自动将 Java 对象(POJO/Map)映射到 SQL 的#{}, 无需手动对应 “?” 位置
结果集处理重复自动将 SQL 执行结果映射到 Java 对象(POJO/List/Map),无需手动遍历ResultSet

记忆要点

  • 对应 JDBC 痛点,记 “4 自动 / 配置”:连接池配置、SQLXML 配置、参数自动映射、结果自动映射。

7、MyBatis 编程步骤是什么样的?

核心解析

MyBatis 编程是 “从工厂到会话,再到执行” 的流程,共 6 步,核心是SqlSessionFactory(工厂)→SqlSession(会话)→执行 SQL

  1. 创建SqlSessionFactory(全局唯一,加载配置文件生成);
  2. 通过SqlSessionFactory创建SqlSession(单次数据库交互会话);
  3. 通过SqlSession执行数据库操作(如selectOne()update());
  4. 若执行增删改,调用session.commit()提交事务;
  5. 调用session.close()关闭会话,释放资源;

记忆要点

  • 记 “工厂造会话,会话做操作,操作完提交,最后关会话”;
  • 关键:SqlSessionFactory是单例(全局一个),SqlSession是多例(每次交互一个)。

8、说说 MyBatis 的工作原理?

核心解析

MyBatis 的工作原理是 “加载配置→创建工厂→创建会话→执行 SQL→映射结果” 的全流程,可拆解为 8 个关键步骤:

  1. 加载全局配置:读取mybatis-config.xml(全局配置文件),包含数据库连接信息、环境配置等;
  2. 加载映射文件:读取XXXMapper.xml(SQL 映射文件),包含 SQL 语句、参数 / 结果映射规则,需在全局配置中指定加载路径;
  3. 创建会话工厂:根据全局配置 + 映射文件,构建SqlSessionFactory(单例,负责生成SqlSession);
  4. 创建会话SqlSessionFactory创建SqlSession(会话对象,包含执行 SQL 的所有方法);
  5. 获取执行器SqlSession内部创建Executor(执行器,MyBatis 核心组件,负责 SQL 执行和缓存维护);
  6. 封装 MappedStatementExecutor根据 SQL 的 id,从配置中获取MappedStatement(封装 SQL 的 id、参数类型、结果类型等信息);
  7. 输入参数映射Executor将 Java 参数(POJO/Map)转换为 SQL 的参数(类似 JDBC 的PreparedStatement设参);
  8. 输出结果映射:执行 SQL 后,ExecutorResultSet转换为 Java 对象(POJO/List),返回给SqlSession,最终返回给调用者。

记忆要点

  • 按 “配置→工厂→会话→执行器→SQL 封装→参数映射→结果映射” 流程记;
  • 核心组件:SqlSessionFactory(工厂)、SqlSession(会话)、Executor(执行器)、MappedStatement(SQL 封装)。

9、MyBatis 的功能架构是怎样的?

核心解析

MyBatis 的功能架构按 “分层职责” 划分,共 3 层,自上而下为 “调用→处理→支撑”:

  1. API 接口层:提供外部调用接口(如SqlSessionselectOne()insert()),开发人员通过该层操作数据库,接收到请求后转发给 “数据处理层”;
  2. 数据处理层:核心业务层,负责 SQL 查找、解析、执行和结果映射,具体包括:
    • 查找MappedStatement(根据 SQL id);
    • 解析动态 SQL(如ifforeach);
    • 执行 SQL(通过Executor);
    • 结果映射(ResultSet→Java 对象);
  3. 基础支撑层:提供通用基础功能,支撑上层运行,包括:
    • 连接管理(连接池);
    • 事务管理(提交 / 回滚);
    • 配置加载(全局配置、映射文件);
    • 缓存处理(一级 / 二级缓存)。

记忆要点

  • 三层记 “上接口、中处理、下支撑”:接口层对外,处理层做核心,支撑层做基础。

10、什么是 DBMS?

核心解析

DBMS(Database Management System,数据库管理系统)是操纵和管理数据库的大型软件,是 “用户 / 应用程序” 与 “数据库” 之间的中间件,核心作用是 “统一管理数据库,保证数据安全和完整”。

  • 核心功能:建立数据库、维护数据(增删改查)、权限控制、数据备份 / 恢复;
  • 关键接口:提供 DDL(数据定义语言,如CREATE TABLE)和 DML(数据操作语言,如INSERTSELECT),供用户操作数据库;
  • 典型例子:MySQL、Oracle、SQL Server。

记忆要点

  • 定位:数据库的 “管家”,连接用户与数据库;
  • 核心:提供 DDL/DML,管安全、管维护。

11、为什么需要预编译?

核心解析

SQL 预编译是 “数据库驱动在发送 SQL 和参数前,先将 SQL 编译为可执行的二进制格式”,核心目的是 “优化性能 + 防注入”,具体原因:

  1. 提升性能:DBMS 执行预编译后的 SQL 时,无需重新编译(复杂 SQL 编译耗时久),且预编译后的PreparedStatement可重复利用(缓存起来下次直接用),减少重复编译开销;
  2. 防止 SQL 注入:预编译将 “SQL 语句” 与 “参数” 分离,参数会被当作 “纯值” 处理(而非 SQL 片段),避免恶意参数拼接成非法 SQL(如OR 1=1);
  • 注意:MyBatis 默认对所有 SQL 进行预编译(通过#{}, 对应PreparedStatement)。

记忆要点

  • 预编译记 “2 个好处”:快(少编译)、安全(防注入);
  • MyBatis 默认支持,对应#{}, 而非${}

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

核心解析

Executor是 MyBatis 的核心执行器,负责 SQL 执行和 Statement 管理,共 3 种类型,区别集中在 “Statement 的创建与复用策略”:

执行器类型核心逻辑适用场景
SimpleExecutor每执行一次 SQL(select/update),创建一个 Statement,执行完立即关闭单次、少量 SQL 执行(默认类型)
ReuseExecutor以 SQL 为 key 缓存 Statement,执行相同 SQL 时复用已有 Statement,不关闭重复执行相同 SQL(如循环查同一类数据)
BatchExecutor仅支持 update(不支持 select),将所有 SQL 加入批处理(addBatch ()),统一执行(executeBatch ())批量更新 / 插入(如批量插入 1000 条数据)

记忆要点

  • 记 “3 种复用策略”:Simple(不用)、Reuse(复用同 SQL)、Batch(批量攒 SQL);
  • Batch 仅支持增删改,不支持查。

13、MyBatis 中如何指定使用哪一种 Executor 执行器?

核心解析

指定 Executor 有 2 种方式:全局默认配置手动指定会话级别

  1. 全局默认配置:在mybatis-config.xml的<settings>中配置defaultExecutorType,指定全局默认执行器:xml<settings>
     <!– 可选值:SIMPLE(默认)、REUSE、BATCH –>
     <setting name=”defaultExecutorType” value=”SIMPLE”/>
    </settings>
  2. 手动指定会话级别:创建SqlSession时,通过SqlSessionFactory.openSession(ExecutorType)手动指定,优先级高于全局配置:java运行// 手动指定Batch执行器
    SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);

记忆要点

  • 两种方式:全局配settings,会话级传参数;
  • 优先级:会话级 > 全局级。

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

核心解析

MyBatis仅支持关联对象(一对一 association、一对多 collection)的延迟加载,核心是 “用的时候再查”,而非 “一次查完所有关联数据”。

  • 启用配置:在mybatis-config.xml中设置lazyLoadingEnabled=true(默认 false);
  • 实现原理(CGLIB 动态代理):
    1. 查询主对象时(如查 User),MyBatis 用 CGLIB 生成 User 的代理对象;
    2. 此时关联对象(如 User 的 Orders)为 null,不触发关联查询;
    3. 当调用关联对象的方法时(如user.getOrders().size()),代理对象的拦截器(invoke())触发;
    4. 拦截器发现关联对象为 null,执行事先保存的 “关联查询 SQL”(查 Orders),将结果赋值给 User 的 Orders 属性;
    5. 继续执行user.getOrders().size(),返回正确结果。

记忆要点

  • 支持范围:仅 association(一对一)、collection(一对多);
  • 原理:CGLIB 代理 + 拦截器,“用的时候才查关联数据”。

15、#{} 和 ${} 的区别?

核心解析

#{}和${}是 MyBatis 中两种参数占位符,核心区别是 “是否预编译”,直接影响安全性和使用场景:

对比维度#{}(占位符)${}(拼接符)
预编译处理支持,将 SQL 中的 #{} 替换为 “?”,通过PreparedStatement设参,属于预编译不支持,直接将参数值拼接进 SQL,属于字符串替换,无预编译
安全性能防止 SQL 注入(参数作为纯值处理,不解析为 SQL 片段)不能防止 SQL 注入(参数可能被拼接成非法 SQL,如OR 1=1
变量替换位置DBMS 内部(SQL 预编译后,DBMS 接收参数值)DBMS 外部(MyBatis 在发送 SQL 前,已将参数拼接进 SQL)
适用场景普通参数(如 where 条件中的值:where id = #{id}动态 SQL 片段(如表名、排序字段:select * from ${tableName} order by ${field}

记忆要点

  • 记 “# 安全预编译,$ 拼接有风险”;
  • 日常用 #{},动态表名 / 字段用 ${}(需确保参数安全)。

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

核心解析

模糊查询需拼接 “%”,但需避免 SQL 注入,推荐 3 种写法(按安全性和兼容性排序):

  1. 推荐:使用 CONCAT () 函数(数据库兼容好,无注入风险)利用数据库的CONCAT()函数拼接 “%”,参数用#{}, 示例:xml<select id=”findUserByLikeName” resultType=”User”>
    SELECT * FROM user WHERE name LIKE CONCAT(‘%’, #{name}, ‘%’)
    </select>
  2. 推荐:双引号包裹 %+#{}(注意引号,无注入风险)#{}会自动加单引号,因此 “%” 需用双引号包裹,避免被解析为字符串常量,示例:xml<select id=”findUserByLikeName” resultType=”User”>
    SELECT * FROM user WHERE name LIKE “%”#{name}”%”
    </select>
  3. ** 不推荐:({}拼接**(有SQL注入风险) 直接用`){}` 拼接 “%”,参数可能被篡改,示例:xml<select id=”findUserByLikeName” resultType=”User”>
     <!– 风险:若name为“’ OR 1=1 — ”,会拼接成非法SQL –>
    SELECT * FROM user WHERE name LIKE ‘%${name}%’
    </select>
  4. 不推荐:bind 标签(需额外配置,灵活性低)用<bind>标签定义模糊匹配规则,示例:xml<select id=”findUserByLikeName” resultType=”User”>
     <bind name=”pattern” value=”‘%’ + name + ‘%'”/>
    SELECT * FROM user WHERE name LIKE #{pattern}
    </select>

记忆要点

  • 安全第一,优先用CONCAT()或 “双引号 +#{}”;
  • 绝对避免用${}做模糊查询(除非参数完全可控)。

17、在 mapper 中如何传递多个参数?

核心解析

MyBatis 传递多个参数有 4 种方式,按 “直观性 + 灵活性” 推荐排序:

方式核心逻辑示例代码适用场景
1. @Param 注解(推荐)在接口方法参数前加@Param("参数名"),XML 中直接用参数名引用接口:User selectUser(@Param("userName") String name, @Param("deptId") int deptId); XML:where user_name = #{userName} and dept_id = #{deptId}参数数量少(2-3 个),直观
2. Java Bean(推荐)封装多个参数为一个 POJO,接口方法参数为 POJO,XML 中用 POJO 属性名引用接口:User selectUser(UserQuery query);(Query 含 userName、deptId) XML:where user_name = #{userName} and dept_id = #{deptId}参数数量多(>3 个),业务固定
3. Map(推荐)封装参数为 Map(key = 参数名,value = 参数值),XML 中用 key 引用接口:User selectUser(Map<String, Object> params); 调用:params.put("userName", "张三"); params.put("deptId", 1); XML:where user_name = #{userName} and dept_id = #{deptId}参数易变、灵活传递
4. 顺序传参(不推荐)不做任何封装,XML 中用#{0}、#{1}引用参数(0 代表第一个参数,1 代表第二个)接口:User selectUser(String name, int deptId); XML:where user_name = #{0} and dept_id = #{1}绝对避免(参数顺序变了就错)

记忆要点

  • 小参数用@Param,大参数用Bean,灵活场景用Map
  • 绝对不用顺序传参(维护成本高)。

18、MyBatis 如何执行批量操作?

核心解析

MyBatis 批量操作(如批量插入、批量更新)有 3 种核心方式,按 “性能 + 兼容性” 推荐排序:

方式核心逻辑示例代码(以批量插入为例)适用场景
1. foreach 标签(MySQL 推荐)利用 MySQL 的VALUES (), (), ()语法,通过<foreach>遍历集合,拼接多组值接口:int batchInsert(@Param("users") List<User> users); XML: INSERT INTO user(name, age) VALUES <foreach collection="users" item="user" separator=","> (#{user.name}, #{user.age}) </foreach>MySQL 环境,批量插入 / 更新,性能好
2. ExecutorType.BATCH(推荐)手动指定ExecutorType.BATCH执行器,SQL 会被批量攒批,统一提交Java 代码: SqlSession session = factory.openSession(ExecutorType.BATCH); UserMapper mapper = session.getMapper(UserMapper.class); for (User u : users) { mapper.insert(u); } session.commit(); XML:普通单条插入 SQL跨数据库(MySQL/Oracle),批量操作量大
3. 多 SQL 分隔(不推荐)开启 JDBC 的allowMultiQueries=true,通过<foreach>拼接多条 SQL(用;分隔)JDBC URL:jdbc:mysql://localhost:3306/db?allowMultiQueries=true XML: <foreach collection="users" item="user" separator=";"> INSERT INTO user(name, age) VALUES(#{user.name}, #{user.age}) </foreach>兼容性差(需额外配置),性能一般

记忆要点

  • MySQL 优先用foreach+VALUES,跨库或大数据量用BATCH执行器;
  • 多 SQL 分隔需开启allowMultiQueries,不推荐。

19、如何获取生成的主键?

核心解析

MyBatis 支持获取 “自增主键”(如 MySQL 的AUTO_INCREMENT),核心是通过useGeneratedKeyskeyProperty配置,让 MyBatis 自动将生成的主键赋值给 POJO 的属性:

  • 配置方式:在<insert>标签中添加useGeneratedKeys="true"(启用主键生成)和keyProperty="主键属性名"(指定 POJO 中存储主键的属性);
  • 示例代码:xml<!– 插入用户,获取自增的userId –>
    <insert id=”insertUser” useGeneratedKeys=”true” keyProperty=”userId”>
    INSERT INTO user(user_name, age) VALUES(#{userName}, #{age})
    </insert>java运行// 调用后,user.getUserId()即可获取生成的主键
    User user = new User();
    user.setUserName(“张三”);
    user.setAge(20);
    userMapper.insertUser(user);
    System.out.println(“生成的主键:” + user.getUserId()); // 如1001

记忆要点

  • 关键配置:useGeneratedKeys=true(开)+ keyProperty="POJO主键属性"(存);
  • 注意:主键会赋值给传入的 POJO 对象,而非返回值(返回值是影响行数)。

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

核心解析

属性名与字段名不一致时,MyBatis 无法自动映射,需通过 “别名” 或 “resultMap” 解决,共 2 种方式:

方式核心逻辑示例代码适用场景
1. SQL 别名(简单)在 SQL 中给字段起别名,让别名与 POJO 属性名一致接口:Order getOrderById(int id); XML: select order_id as id, order_no as orderNo, order_price as price from orders where order_id=#{id};字段少、临时映射
2. resultMap(推荐)定义<resultMap>,明确映射 “表字段名(column)” 与 “POJO 属性名(property)”XML: <select id="getOrderById" parameterType="int" resultMap="orderMap"> select * from orders where order_id=#{id} </select> <resultMap type="Order" id="orderMap"> <id property="id" column="order_id"/> <!-- 主键映射 --> <result property="orderNo" column="order_no"/> <result property="price" column="order_price"/> </resultMap>字段多、多次复用映射规则

记忆要点

  • 简单场景用 SQL 别名,复杂场景用resultMap
  • resultMap中,<id>映射主键,<result>映射普通字段。

21、Mapper 编写有哪几种方式?

核心解析

Mapper(DAO 层)的编写方式按 “简化程度” 演进,共 3 种,目前主流用 “Mapper 扫描器”:

方式核心逻辑关键步骤适用场景
1. 接口实现类(继承 SqlSessionDaoSupport)编写 Mapper 接口 + 接口实现类(继承SqlSessionDaoSupport) + Mapper.xml,通过getSqlSession()执行 SQL1. 写接口:UserMapper; 2. 写实现类:UserMapperImpl extends SqlSessionDaoSupport; 3. Spring 配置实现类:<bean id="userMapper" class="xxx.UserMapperImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean>早期方式,繁琐,几乎不用
2. MapperFactoryBean编写 Mapper 接口 + Mapper.xml,通过 Spring 的MapperFactoryBean创建接口代理对象1. 写接口和 XML(namespace = 接口全路径); 2. Spring 配置:<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="xxx.UserMapper"/> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean>单 Mapper 配置,多 Mapper 时繁琐
3. Mapper 扫描器(推荐)编写 Mapper 接口 + Mapper.xml(接口名与 XML 名一致,同目录),通过MapperScannerConfigurer批量扫描接口1. 写接口和 XML(namespace = 接口全路径,文件名一致); 2. Spring 配置扫描器:<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="xxx.mapper"/> <!-- 扫描Mapper接口包 --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean>主流方式,批量创建,简洁

记忆要点

  • 目前只用 “Mapper 扫描器”,核心是 “接口 + XML 同目录同名,配置扫描包”;
  • 前两种方式已过时,了解即可。

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

核心解析

接口绑定是 MyBatis 的核心特性:定义 Mapper 接口,将接口方法与 XML 中的 SQL 通过 “namespace+id” 绑定,调用接口方法即可执行对应 SQL,无需编写接口实现类(MyBatis 动态代理生成)。

  • 核心价值:替代传统 JDBC 的SqlSession.selectOne("namespace.id"),更类型安全、更简洁;
  • 实现方式(2 种):
    1. XML 绑定(推荐):Mapper.xml 的namespace必须等于接口全路径,接口方法名必须等于 XML 中 SQL 的id,示例:
      • 接口:public interface UserMapper { User selectById(int id); }
      • XML:<mapper namespace="xxx.mapper.UserMapper"> <select id="selectById" parameterType="int" resultType="User"> select * from user where id=#{id} </select> </mapper>
    2. 注解绑定(简单 SQL):无需 XML,直接在接口方法上用@Select/@Insert/@Update/@Delete注解写 SQL,示例:java运行public interface UserMapper {
       @Select(“select * from user where id=#{id}”)
       User selectById(int id);
      }

记忆要点

  • 接口绑定:接口方法与 SQL 绑定,代理生成实现类;
  • 实现:复杂 SQL 用 XML(namespace+id),简单 SQL 用注解。

23、使用 MyBatis 的 mapper 接口调用时有哪些要求?

核心解析

Mapper 接口调用的本质是 “MyBatis 生成动态代理对象”,需满足 4 个核心要求,否则代理对象无法匹配 SQL:

  1. 方法名与 SQL id 一致:接口方法名必须等于 Mapper.xml 中<select>/<insert>等标签的id
  2. 参数类型与 parameterType 一致:接口方法的输入参数类型必须等于 XML 中parameterType指定的类型(若省略parameterType,MyBatis 会自动推断);
  3. 返回类型与 resultType/resultMap 一致:接口方法的返回类型必须等于 XML 中resultType(单对象 / 基本类型)或resultMap(自定义映射)指定的类型;
  4. XML 的 namespace 与接口全路径一致:Mapper.xml 的namespace必须是 Mapper 接口的 “包名 + 接口名”(如com.xxx.mapper.UserMapper)。

记忆要点

  • 四一致:方法名 = id,参数类型 = parameterType,返回类型 = resultType,namespace = 接口全路径。

24、Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?

核心解析

(1)Dao 接口的工作原理

Dao 接口(即 Mapper 接口)本身没有实现类,MyBatis 通过JDK 动态代理生成接口的代理对象(Proxy),代理对象的核心逻辑:

  1. 当调用接口方法(如userMapper.selectById(1))时,代理对象拦截该调用;
  2. 代理对象根据 “接口全路径 + 方法名”(如com.xxx.mapper.UserMapper.selectById),从Configuration中找到对应的MappedStatement(封装 SQL 的对象);
  3. 执行MappedStatement对应的 SQL,处理参数和结果映射;
  4. 将结果返回给调用者。

(2)Dao 接口方法能否重载?

不能重载,原因是 MyBatis 查找 SQL 的 key 是 “namespace + 方法名”,与参数无关:

  • 例如:接口中定义User selectUser(String name)User selectUser(int id),两者的 “namespace + 方法名” 都是com.xxx.mapper.UserMapper.selectUser,MyBatis 无法区分,会报 “SQL id 重复” 错误。

记忆要点

  • 工作原理:JDK 动态代理,拦截方法,按 “namespace + 方法名” 找 SQL;
  • 不能重载:key 是 “namespace + 方法名”,与参数无关,无法区分。

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

核心解析

能否重复取决于是否配置namespace,核心原因是 MyBatis 存储 SQL 的 key 是 “namespace+id”:

  1. 配置了 namespace:id 可以重复。 例如:UserMapper.xml(namespace=com.xxx.UserMapper)有id="selectById"OrderMapper.xml(namespace=com.xxx.OrderMapper)也有id="selectById",两者的 key 分别是com.xxx.UserMapper.selectByIdcom.xxx.OrderMapper.selectById,不冲突;
  2. 未配置 namespace:id 不能重复。 此时 MyBatis 存储 SQL 的 key 就是id,若不同 XML 的 id 重复,后加载的 SQL 会覆盖先加载的,导致数据错误。

记忆要点

  • 有 namespace:id 可重复(key 是 namespace+id);
  • 无 namespace:id 不可重复(key 是 id);
  • 最佳实践:必须配置 namespace(对应 Mapper 接口全路径)。

26、简述 MyBatis 的 Xml 映射文件和 Mybatis 内部数据结构之间的映射关系?

核心解析

MyBatis 加载 Xml 映射文件后,会将 XML 中的标签解析为对应的 Java 对象,存储在Configuration(全局配置对象)中,核心映射关系如下:

Xml 映射文件标签MyBatis 内部数据结构(Java 对象)作用
<parameterMap>ParameterMap封装 “参数映射规则”(已过时,推荐直接用parameterType+#{})
<parameterMap>子元素ParameterMapping封装单个参数的映射信息(如参数名、JDBC 类型)
<resultMap>ResultMap封装 “结果映射规则”(表字段与 POJO 属性的对应关系)
<resultMap>子元素ResultMapping封装单个结果的映射信息(如 POJO 属性名、表字段名、JDBC 类型)
<select>/<insert>/<update>/<delete>MappedStatement封装单个 SQL 的完整信息(SQL 语句、参数类型、结果类型、缓存配置等)
标签内的 SQL 语句BoundSql封装解析后的 SQL 语句、参数占位符信息(如#{}对应的参数名

记忆要点

  • 核心映射:标签→对象,存储在Configuration
  • 关键对象:MappedStatement(SQL 整体)、ResultMap(结果映射)、BoundSql(解析后 SQL)。

27、MyBatis 映射文件中,如果 A 标签通过 include 引用了 B 标签的内容,请问,B 标签能否定义在 A 标签的后面,还是说必须定义在 A 标签的前面?

核心解析

B 标签可以定义在任何位置(A 前面或后面),MyBatis 的解析逻辑支持 “先标记,后解析”:

  1. MyBatis 按顺序解析 Xml 文件,当解析到 A 标签时,发现 A 引用了 B 标签;
  2. 若此时 B 标签尚未解析(B 在 A 后面),MyBatis 将 A 标签标记为 “未解析状态”,继续解析后续标签;
  3. 当解析到 B 标签时,正常解析 B 并存储;
  4. 所有标签解析完毕后,MyBatis 重新解析那些 “未解析状态” 的标签(如 A),此时 B 已存在,A 可正常引用 B 完成解析。

记忆要点

  • 引用标签位置无限制,MyBatis 支持 “先标记后解析”。

28、MyBatis 能执行一对多,一对一的联系查询吗,有哪些实现方法?

核心解析

MyBatis完全支持一对一、一对多、多对多的关联查询,核心通过resultMapassociation(一对一)和collection(一对多 / 多对多)标签实现,具体方法:

关联类型实现方法核心标签 / 逻辑
一对一(如 User→IdCard)1. 嵌套查询(分步查):先查 User,再查 IdCard; 2. 嵌套结果(关联查):一次查 User 和 IdCard,用association映射嵌套结果示例: <resultMap type="User" id="userMap"> <id property="id" column="user_id"/> <association property="idCard" javaType="IdCard"> <id property="cardId" column="card_id"/> </association> </resultMap>
一对多(如 User→Order)1. 嵌套查询(分步查):先查 User,再查其所有 Order; 2. 嵌套结果(关联查):一次查 User 和所有 Order,用collection映射嵌套结果示例: <resultMap type="User" id="userMap"> <id property="id" column="user_id"/> <collection property="orders" ofType="Order"> <id property="orderId" column="order_id"/> </collection> </resultMap>
多对多(如 User→Role)本质是 “两个一对多”(User→UserRole→Role),通过中间表关联,用collection嵌套查询 / 结果逻辑:User 关联 UserRole(一对多),UserRole 关联 Role(一对多),最终 User→List<Role>

记忆要点

  • 一对一用association,一对多用collection
  • 实现方式分 “嵌套查询(分步)” 和 “嵌套结果(一次查)”。

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

核心解析

可以映射 Enum 枚举类,MyBatis 支持通过 “自定义 TypeHandler(类型处理器)” 实现 Enum 与数据库字段的映射。

  • TypeHandler 的核心作用:完成 “Java 类型(Enum)” 与 “JDBC 类型(如 VARCHAR/INT)” 的双向转换;
  • 实现步骤:
    1. 自定义 TypeHandler,实现org.apache.ibatis.type.TypeHandler接口,重写 2 个核心方法:
      • setParameter():将 Enum 转换为 JDBC 类型(如将Sex.MALE转换为字符串 “MALE”);
      • getResult():将 JDBC 类型转换为 Enum(如将数据库的 “FEMALE” 转换为Sex.FEMALE);
    2. 在 MyBatis 配置中注册 TypeHandler(或在resultMap/parameterType中指定);
    3. 在 Mapper.xml 中使用该 TypeHandler 映射 Enum 属性。

记忆要点

  • 可以映射,需自定义 TypeHandler;
  • 核心方法:setParameter()(Java→JDBC)、getResult()(JDBC→Java)。

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

核心解析

(1)动态 SQL 的作用

动态 SQL 是 MyBatis 的核心特性,用于在 Xml 中通过标签实现 “逻辑判断 + SQL 动态拼接”,解决 “SQL 条件不固定” 的问题(如查询时,条件可能有也可能没有)。

(2)动态 SQL 标签(共 9 种)

标签类别标签作用
逻辑判断<if>单条件判断(如if test="name != null"> and name = #{name}</if>
分支判断<choose>+<when>+<otherwise>多条件分支(类似 Java 的switch-case
SQL 拼接优化<where>自动处理 “多余的 AND/OR”(如条件都不满足时不生成 WHERE,条件前有 AND 时自动删除)
<set>自动处理 “多余的逗号”(用于 UPDATE,如set <if test="name != null">name=#{name},</if> → 自动删逗号)
<trim>自定义拼接规则(如trim prefix="WHERE" prefixOverrides="AND" → 同 where 标签)
循环<foreach>遍历集合生成 SQL(如in条件:where id in <foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
其他<bind>定义变量(如模糊查询:bind name="pattern" value="'%' + name + '%'"

(3)执行原理

  1. MyBatis 加载动态 SQL 标签时,会将其解析为对应的 “动态 SQL 节点”(如IfNodeForEachNode);
  2. 执行 SQL 前,MyBatis 使用OGNL 表达式(Object-Graph Navigation Language)从参数对象中计算标签的判断条件(如if test="name != null" → 判断参数中 name 是否非空);
  3. 根据 OGNL 计算结果,动态拼接 SQL(如条件为 true 则保留<if>内的 SQL 片段,false 则剔除);
  4. 拼接完成后,生成最终可执行的 SQL,执行并处理结果。

记忆要点

  • 作用:逻辑判断 + 动态拼 SQL;
  • 标签:记 “3 判断(if/choose/trim)、2 优化(where/set)、1 循环(foreach)、1 绑定(bind)”;
  • 原理:OGNL 算条件,动态拼 SQL。

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

核心解析

(1)MyBatis 的分页方式

MyBatis 有 2 种分页方式,核心区别是 “内存分页vs物理分页”:

  1. 内存分页(RowBounds):
    • 原理:先查询所有数据到内存,再通过RowBoundsoffset偏移量、limit条数)截取结果;
    • 缺点:数据量大时内存占用高,性能差;
    • 示例:List<User> list = sqlSession.selectList("selectAll", null, new RowBounds(0, 10));(查前 10 条);
  2. 物理分页:
    • 原理:在 SQL 中直接添加分页语法(如 MySQL 的LIMIT、Oracle 的ROWNUM),让数据库只返回分页数据;
    • 实现方式:
      • 手动写分页 SQL:select * from user limit #{offset}, #{limit}
      • 使用分页插件(如 PageHelper):自动拦截 SQL 并添加分页语法。

(2)分页插件的原理(以 PageHelper 为例)

分页插件基于 MyBatis 的插件机制,核心是 “拦截 SQL→重写 SQL”:

  1. 配置分页插件,指定拦截的接口(如Executorquery方法);
  2. 当执行查询方法时,插件拦截该调用,获取待执行的 SQL(如select * from user);
  3. 插件根据 “数据库方言”(如 MySQL/Oracle),动态重写 SQL,添加分页语法:
    • MySQL:select * from userselect t.* from (select * from user) t limit 0, 10
    • Oracle:select * from userselect * from (select u.*, rownum rn from user u where rownum <= 10) where rn > 0
  4. 执行重写后的 SQL,返回分页结果(含总条数、当前页数据等)。

记忆要点

  • MyBatis 分页:内存分页(RowBounds,差)、物理分页(手动 SQL / 插件,好);
  • 插件原理:拦截 SQL→按方言重写 SQL(加分页语法)。

32、简述 MyBatis 的插件运行原理,以及如何编写一个插件?

核心解析

(1)插件运行原理

MyBatis 的插件是 “基于 JDK 动态代理的接口拦截器”,仅能拦截 4 个核心接口的方法:Executor(执行器)、StatementHandler(SQL 语句处理器)、ParameterHandler(参数处理器)、ResultSetHandler(结果处理器),原理:

  1. 配置插件时,MyBatis 为这 4 个接口生成动态代理对象;
  2. 当调用接口的方法(如Executor.query())时,代理对象拦截该调用;
  3. 执行插件的intercept()方法(自定义逻辑,如修改 SQL、记录日志);
  4. 插件逻辑执行完毕后,调用原接口方法,完成 SQL 执行。

(2)编写插件的步骤(以 “SQL 执行日志插件” 为例)

  1. 实现 Interceptor 接口:重写 3 个方法:
    • intercept(Invocation invocation):核心拦截逻辑(如打印 SQL);
    • plugin(Object target):生成代理对象(一般用Plugin.wrap(target, this));
    • setProperties(Properties properties):读取插件配置的属性(如日志开关);
    java运行@Intercepts({@Signature(
     type = Executor.class, // 拦截的接口
     method = “query”, // 拦截的方法
     args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} // 方法参数
    )})
    public class SqlLogPlugin implements Interceptor {
     @Override
     public Object intercept(Invocation invocation) throws Throwable {
       // 1. 获取MappedStatement(含SQL)
       MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
       String sql = ms.getSqlSource().getBoundSql(invocation.getArgs()[1]).getSql();
       // 2. 打印SQL日志
       System.out.println(“执行SQL:” + sql);
       // 3. 执行原方法
       return invocation.proceed();
    }

     @Override
     public Object plugin(Object target) {
       // 生成代理对象
       return Plugin.wrap(target, this);
    }

     @Override
     public void setProperties(Properties properties) {
       // 读取配置属性(如properties.getProperty(“logEnable”))
    }
    }
  2. 配置插件:在mybatis-config.xml中注册插件:xml<plugins>
     <plugin interceptor=”com.xxx.plugin.SqlLogPlugin”>
       <!– 可选:配置插件属性 –>
       <property name=”logEnable” value=”true”/>
     </plugin>
    </plugins>

记忆要点

  • 运行原理:JDK 动态代理,拦截 4 个核心接口的方法;
  • 编写步骤:实现 Interceptor + 加 @Intercepts 注解 + 配置插件。

33、MyBatis 的一级、二级缓存?

核心解析

MyBatis 的缓存是 “本地缓存”,用于减少数据库查询次数,提升性能,分一级和二级缓存,核心区别是 “作用域”:

对比维度一级缓存(默认开启)二级缓存(默认关闭)
作用域SqlSession(会话):一个 SqlSession 内有效Mapper(Namespace):同一个 Namespace 下的所有 SqlSession 共享
存储实现默认PerpetualCache(基于 HashMap)默认PerpetualCache,可自定义(如 Ehcache、Redis)
开启方式无需配置,默认开启1. 全局配置:mybatis-config.xmllazyLoadingEnabled=true; 2. Mapper 配置:在<mapper>中加<cache/>; 3. POJO 实现Serializable接口(缓存序列化)
失效场景1. SqlSession commit/close; 2. 执行增删改(CUD)操作; 3. 手动调用clearCache()1. 同一 Namespace 下执行 CUD 操作; 2. 手动调用clearCache(); 3. 缓存过期(若配置)
适用场景单次会话内的重复查询(如同一请求中多次查同一用户)多会话共享的重复查询(如多个请求查同一商品列表)

记忆要点

  • 一级缓存:Session 级,默认开,CUD/close 失效;
  • 二级缓存:Namespace 级,默认关,需配<cache/>+POJO 序列化,CUD 失效;
  • 核心:缓存是本地的,分布式场景需用 Redis 等分布式缓存。
文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇