MyBatis学习笔记(含面试题) 一、核心知识点梳理 1. MyBatis简介 1.1 核心定位 MyBatis是一款优秀的持久层框架 ,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集的过程,通过XML或注解的方式将Java对象与SQL语句动态映射,简化数据持久化操作。
核心思想:将SQL与Java代码分离,通过配置文件管理SQL,降低耦合度,提高代码可维护性。
1.2 与JDBC的对比优势
JDBC需要手动加载驱动、创建连接、编写SQL、设置参数、处理结果集、关闭资源,代码冗余且繁琐;MyBatis通过配置和映射自动完成这些操作,简化开发。
JDBC的SQL语句硬编码在Java代码中,修改SQL需重新编译代码;MyBatis的SQL写在XML或注解中,修改无需编译,灵活度高。
JDBC需要手动处理参数类型匹配和结果集与Java对象的映射;MyBatis支持自动参数映射和结果集映射,支持复杂对象(一对一、一对多)映射。
MyBatis提供缓存机制(一级缓存、二级缓存),提升查询效率;JDBC需手动实现缓存。
1.3 核心依赖(Maven) 1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.15</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.36</version > </dependency >
2. MyBatis核心组件 MyBatis的核心组件围绕“SQL执行流程”展开,各组件分工明确,协同完成数据持久化操作:
2.1 SqlSessionFactoryBuilder(构建者) 作用:根据MyBatis核心配置文件(mybatis-config.xml)或配置类,构建SqlSessionFactory对象。
特点:
2.2 SqlSessionFactory(会话工厂) 作用:创建SqlSession对象(数据库会话),是MyBatis的核心工厂类。
特点:
2.3 SqlSession(数据库会话) 作用:代表与数据库的一次会话,是MyBatis操作数据库的核心对象,可执行SQL语句、管理事务。
特点:
2.4 Mapper接口(映射器) 作用:定义数据操作的接口,MyBatis通过动态代理机制自动生成接口的实现类,实现SQL与Java方法的映射。
特点:
2.5 核心配置文件(mybatis-config.xml) 作用:配置MyBatis的核心参数,如数据库连接信息、映射器路径、缓存策略、类型别名等。
核心配置节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <configuration > <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" /> <property name ="driver" value ="com.mysql.cj.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/mybatis_db" /> <property name ="username" value ="root" /> <property name ="password" value ="123456" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="mapper/UserMapper.xml" /> <mapper class ="com.example.mapper.UserMapper" /> <package name ="com.example.mapper" /> </mappers > <typeAliases > <typeAlias type ="com.example.pojo.User" alias ="User" /> <package name ="com.example.pojo" /> </typeAliases > </configuration >
2.6 映射文件(XXXMapper.xml) 作用:存储SQL语句,定义Java对象与SQL参数、结果集的映射关系,是MyBatis中SQL与代码分离的核心载体。
核心节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?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 ="com.example.mapper.UserMapper" > <resultMap id ="UserMap" type ="User" > <id column ="user_id" property ="userId" /> <result column ="user_name" property ="userName" /> <result column ="age" property ="age" /> </resultMap > <select id ="getUserById" parameterType ="int" resultMap ="UserMap" > SELECT user_id, user_name, age FROM user WHERE user_id = #{id} </select > <insert id ="addUser" parameterType ="User" useGeneratedKeys ="true" keyProperty ="userId" > INSERT INTO user (user_name, age) VALUES (#{userName}, #{age}) </insert > <update id ="updateUser" parameterType ="User" > UPDATE user SET user_name = #{userName}, age = #{age} WHERE user_id = #{userId} </update > <delete id ="deleteUser" parameterType ="int" > DELETE FROM user WHERE user_id = #{id} </delete > </mapper >
3. MyBatis核心工作原理 MyBatis的工作流程可分为“初始化阶段”和“SQL执行阶段”两大步骤,核心是“配置解析”和“动态代理”:
3.1 初始化阶段(启动时)
加载核心配置文件:SqlSessionFactoryBuilder读取mybatis-config.xml,解析配置信息(环境、数据源、映射器等)。
创建SqlSessionFactory:通过配置信息初始化数据源(连接池)和事务管理器,生成SqlSessionFactory单例对象。
解析映射文件:SqlSessionFactory加载XXXMapper.xml,解析SQL语句、参数映射、结果集映射,存储到Configuration对象中(MyBatis的核心配置容器)。
3.2 SQL执行阶段(运行时)
创建SqlSession:通过SqlSessionFactory.openSession()创建SqlSession,关联数据库连接,开启事务。
获取Mapper代理对象:SqlSession.getMapper(Mapper接口.class),MyBatis通过JDK动态代理生成Mapper接口的代理实现类(MapperProxy)。
执行SQL:调用Mapper接口的方法,代理对象拦截方法调用,从Configuration中获取对应的SQL语句和映射规则。
参数处理:将Java方法参数转换为SQL参数,通过参数处理器(ParameterHandler)设置到PreparedStatement中。
执行SQL并处理结果:通过执行器(Executor)执行SQL,获取结果集,通过结果集处理器(ResultSetHandler)将结果集映射为Java对象。
事务管理:执行完成后,通过SqlSession.commit()提交事务或rollback()回滚事务,关闭SqlSession释放资源。
4. MyBatis核心特性 4.1 动态SQL MyBatis提供动态SQL标签,可根据参数条件动态拼接SQL语句,避免手动拼接SQL的繁琐和SQL注入风险。
常用动态SQL标签:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <select id ="getUserList" parameterType ="User" resultMap ="UserMap" > SELECT user_id, user_name, age FROM user <where > <if test ="userName != null and userName != ''" > AND user_name LIKE CONCAT('%', #{userName}, '%') </if > <if test ="age != null" > AND age = #{age} </if > </where > <choose > <when test ="sortBy == 'age'" > ORDER BY age ASC</when > <when test ="sortBy == 'userName'" > ORDER BY user_name ASC</when > <otherwise > ORDER BY user_id ASC</otherwise > </choose > </select > <delete id ="deleteBatchUser" parameterType ="java.util.List" > DELETE FROM user WHERE user_id IN <foreach collection ="list" item ="id" open ="(" separator ="," close =")" > #{id} </foreach > </delete >
4.2 结果集映射 解决Java对象属性名与数据库字段名不一致的问题,常用两种方式:
使用resultMap标签:在XML中定义字段名与属性名的映射关系(推荐,适合复杂对象)。
使用别名:在SQL语句中给字段起别名,别名与Java属性名一致(适合简单场景),如SELECT user_id AS userId, user_name AS userName FROM user。
复杂映射(关联查询):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <resultMap id ="UserOrderMap" type ="User" > <id column ="user_id" property ="userId" /> <result column ="user_name" property ="userName" /> <collection property ="orderList" ofType ="Order" > <id column ="order_id" property ="orderId" /> <result column ="order_no" property ="orderNo" /> </collection > </resultMap > <select id ="getUserWithOrders" parameterType ="int" resultMap ="UserOrderMap" > SELECT u.user_id, u.user_name, o.order_id, o.order_no FROM user u LEFT JOIN order o ON u.user_id = o.user_id WHERE u.user_id = #{id} </select >
4.3 缓存机制 MyBatis提供两级缓存,用于减少数据库查询次数,提升性能:
4.3.1 一级缓存(本地缓存)
作用域:SqlSession级别,默认开启,不可关闭。
原理:同一个SqlSession中,执行相同的查询SQL(参数一致),第一次查询从数据库获取结果并缓存到SqlSession中,后续查询直接从缓存获取,无需访问数据库。
失效场景:执行insert/update/delete操作(会清空一级缓存)、手动调用SqlSession.clearCache()、SqlSession关闭。
4.3.2 二级缓存(全局缓存)
作用域:Mapper接口级别(同一个namespace),默认关闭,需手动开启。
开启方式:① 核心配置文件中设置 (默认true,可省略);② 映射文件中添加 标签。
原理:多个SqlSession共享同一个Mapper的缓存,查询结果先存入一级缓存,当SqlSession关闭时,一级缓存的内容会同步到二级缓存。
注意事项:缓存的对象需实现Serializable接口(支持序列化,便于缓存存储);执行insert/update/delete操作会清空当前Mapper的二级缓存。
4.4 分页机制 MyBatis支持两种分页方式:
RowBounds分页(内存分页):通过RowBounds对象设置起始行和查询条数,原理是查询全部结果后在内存中截取,效率低,适合小数据量场景。
插件分页(物理分页,推荐):使用第三方插件(如PageHelper),通过拦截SQL语句动态添加LIMIT(MySQL)、ROWNUM(Oracle)等分页语法,直接从数据库查询分页结果,效率高。
5. MyBatis注解开发 MyBatis支持通过注解直接在Mapper接口上编写SQL,无需XML映射文件,适合简单SQL场景,复杂SQL仍推荐XML方式。
常用注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public interface UserMapper { @Select("SELECT user_id AS userId, user_name AS userName, age FROM user WHERE user_id = #{id}") User getUserById (int id) ; @Insert("INSERT INTO user (user_name, age) VALUES (#{userName}, #{age})") @Options(useGeneratedKeys = true, keyProperty = "userId") int addUser (User user) ; @Update("UPDATE user SET user_name = #{userName}, age = #{age} WHERE user_id = #{userId}") int updateUser (User user) ; @Delete("DELETE FROM user WHERE user_id = #{id}") int deleteUser (int id) ; @SelectProvider(type = UserSqlProvider.class, method = "getUserListSql") List<User> getUserList (User user) ; class UserSqlProvider { public String getUserListSql (User user) { return new SQL () {{ SELECT("user_id, user_name, age" ); FROM("user" ); if (user.getUserName() != null && !user.getUserName().isEmpty()) { WHERE("user_name LIKE CONCAT('%', #{userName}, '%')" ); } if (user.getAge() != null ) { WHERE("age = #{age}" ); } }}.toString(); } } }
二、面试高频问题及解析 1. 基础概念类 Q1:MyBatis是什么?它的核心优势是什么? 解析:
MyBatis是一款持久层框架,支持定制化SQL、存储过程和高级映射,通过XML或注解将Java对象与SQL动态映射,简化JDBC开发。
核心优势:
SQL与Java代码分离,便于维护和修改,无需重新编译代码。
自动完成参数映射和结果集映射,避免手动处理JDBC的参数和结果集,减少冗余代码。
支持动态SQL,可根据参数条件动态拼接SQL,灵活应对复杂查询场景。
提供缓存机制(一级、二级),提升查询效率。
支持复杂映射(一对一、一对多),满足复杂业务场景需求。
轻量级框架,配置简单,学习成本低,易于集成到Spring等主流框架。
Q2:MyBatis与Hibernate的区别是什么?适用场景分别是什么? 解析:
两者均为持久层框架,核心区别在于“SQL控制粒度”和“开发模式”:
维度
MyBatis
Hibernate
SQL控制
手动编写SQL,灵活度高,可优化SQL性能
自动生成SQL,无需手动编写,灵活度低
开发模式
半ORM(对象关系映射),需要关注SQL和数据库细节
全ORM,完全面向对象开发,无需关注数据库细节
学习成本
低,重点掌握SQL和映射配置
高,需要掌握HQL、缓存、关联映射等复杂特性
性能优化
手动优化SQL,针对性强,适合复杂查询场景
自动生成的SQL可能低效,优化难度大,需通过复杂配置优化
适用场景
需求多变、SQL复杂的项目(如电商、金融);对性能要求高,需要手动优化SQL的场景
需求稳定、SQL简单的项目(如后台管理系统);快速开发原型的场景
2. 核心组件与工作原理类 Q1:MyBatis的核心组件有哪些?各自的作用是什么? 解析:
SqlSessionFactoryBuilder:构建者,根据核心配置文件构建SqlSessionFactory,构建完成后可销毁。
SqlSessionFactory:会话工厂,单例对象,管理数据库连接池,负责创建SqlSession。
SqlSession:数据库会话,线程不安全,代表与数据库的一次交互,可执行SQL、管理事务。
Mapper接口:映射器,定义数据操作接口,MyBatis通过动态代理生成实现类,绑定SQL语句。
Configuration:核心配置容器,存储MyBatis的所有配置信息(环境、映射器、SQL语句等)。
Executor:执行器,SqlSession的底层核心组件,负责执行SQL语句,管理一级缓存。
ParameterHandler:参数处理器,将Java方法参数转换为SQL参数,设置到PreparedStatement中。
ResultSetHandler:结果集处理器,将SQL查询结果集映射为Java对象。
Q2:MyBatis的工作原理是什么?请简述完整的执行流程。 解析:
MyBatis的工作流程分为“初始化阶段”和“SQL执行阶段”:
初始化阶段: 加载配置:SqlSessionFactoryBuilder读取mybatis-config.xml和XXXMapper.xml,解析配置信息(数据源、事务、SQL语句、映射规则等)。
创建SqlSessionFactory:根据解析后的配置信息,初始化数据源和事务管理器,生成SqlSessionFactory单例对象。
存储配置:将解析后的SQL语句、映射规则等存入Configuration对象中,供运行时使用。
SQL执行阶段: 创建SqlSession:通过SqlSessionFactory.openSession()创建SqlSession,关联数据库连接,开启事务。
获取Mapper代理:调用SqlSession.getMapper(),MyBatis通过JDK动态代理生成Mapper接口的代理对象(MapperProxy)。
拦截方法调用:调用Mapper接口方法时,代理对象拦截调用,从Configuration中获取对应的SQL语句和映射规则。
参数处理:ParameterHandler将Java方法参数转换为SQL参数,设置到PreparedStatement。
执行SQL:Executor执行SQL语句,获取结果集。
结果映射:ResultSetHandler将结果集映射为Java对象。
事务管理:执行完成后,通过SqlSession.commit()/rollback()管理事务,关闭SqlSession释放资源。
Q3:MyBatis的Mapper接口为什么不需要实现类? 解析:
MyBatis通过JDK动态代理机制 自动生成Mapper接口的实现类,无需手动编写:
当调用SqlSession.getMapper(Mapper接口.class)时,MyBatis会创建一个实现了该接口的代理对象(MapperProxy)。
代理对象会拦截接口中所有方法的调用,根据方法名从Configuration中找到对应的SQL语句和映射规则。
代理对象通过Executor、ParameterHandler、ResultSetHandler等组件完成SQL执行和结果映射,最终将结果返回给调用者。
核心优势:简化开发,避免编写大量重复的实现类代码,同时实现SQL与Java代码的解耦。
3. 映射与SQL相关类 Q1:MyBatis中如何解决Java属性名与数据库字段名不一致的问题? 解析:
常用三种解决方案,优先级:resultMap > SQL别名 > 开启驼峰命名自动映射:
使用resultMap标签(推荐):在XML映射文件中定义resultMap,明确指定数据库字段名(column)与Java属性名(property)的映射关系。 `
SELECT user_id, user_name FROM user WHERE user_id = #{id}
</select>`
SQL语句中使用别名:给数据库字段起与Java属性名一致的别名,适合简单场景。<select id="getUserById" parameterType="int" resultType="User"> SELECT user_id AS userId, user_name AS userName FROM user WHERE user_id = #{id} </select>
开启驼峰命名自动映射:MyBatis支持自动将数据库的下划线命名(如user_id)转换为Java的驼峰命名(userId),需在核心配置文件中开启: `
`注意:仅适用于“下划线命名”与“驼峰命名”严格对应的场景(如user_name → userName)。
Q2:MyBatis的动态SQL有哪些标签?分别作用是什么? 解析:
动态SQL标签用于根据参数条件动态拼接SQL,避免手动拼接导致的语法错误和SQL注入风险,常用标签:
:条件判断,满足test属性中的表达式则拼接标签内的SQL。<if test="userName != null and userName != ''"> AND user_name LIKE CONCAT('%', #{userName}, '%') </if>
:自动拼接WHERE关键字,智能去除标签内开头的多余AND/OR,替代手动写WHERE 1=1。
:类似Java的switch-case,只执行第一个满足条件的,都不满足则执行。
:遍历集合(List、Array、Map),常用于批量操作(批量删除、批量插入),核心属性: collection:集合参数名(如list、array)。
item:遍历的单个元素别名。
open:拼接SQL的开头字符(如()。
separator:元素之间的分隔符(如,)。
close:拼接SQL的结尾字符(如))。
:用于update语句,自动拼接SET关键字,去除多余的逗号,适合动态更新字段。 <update id="updateUser" parameterType="User"> UPDATE user <set> <if test="userName != null">user_name = #{userName},</if> <if test="age != null">age = #{age}</if> </set> WHERE user_id = #{userId} </update>
Q3:MyBatis中#{}和${}的区别是什么?使用时需要注意什么? 解析:
两者均为MyBatis的参数占位符,核心区别在于“参数解析方式”和“SQL注入风险”:
维度
#{}
${}
解析方式
将参数解析为PreparedStatement的参数占位符(?),自动进行参数类型匹配和转义
将参数直接拼接为SQL字符串,不进行转义
SQL注入风险
无风险,参数被当作普通字符串处理,避免注入攻击
有风险,参数直接拼接进SQL,若参数为用户输入,可能被篡改SQL逻辑
适用场景
传递SQL参数(如WHERE条件、INSERT VALUES),绝大多数场景推荐使用
动态拼接SQL片段(如表名、列名、排序方式),需确保参数安全(非用户输入)
示例
SELECT * FROM user WHERE id = #{id} → 解析为 SELECT * FROM user WHERE id = ?
SELECT * FROM ${tableName} → 若tableName=user,解析为 SELECT * FROM user
注意事项:优先使用#{},避免使用${};若必须使用${},需对参数进行严格校验,确保参数不是用户可控的输入,防止SQL注入。
4. 缓存相关类 Q1:MyBatis的一级缓存和二级缓存有什么区别? 解析:
维度
一级缓存(本地缓存)
二级缓存(全局缓存)
作用域
SqlSession级别,仅同一个SqlSession内有效
Mapper接口级别(namespace),多个SqlSession共享
开启方式
默认开启,不可关闭
默认关闭,需在映射文件中添加 标签开启
缓存存储位置
SqlSession对象中
Configuration对象中(对应Mapper的namespace下)
数据同步时机
查询时存入,insert/update/delete或SqlSession关闭时清空
SqlSession关闭时,将一级缓存数据同步到二级缓存;insert/update/delete时清空当前Mapper的二级缓存
序列化要求
无需实现Serializable接口
需要实现Serializable接口(支持序列化存储)
使用场景
同一事务内的多次查询,减少数据库访问
多个事务共享的查询数据(如字典数据),提升全局查询效率
Q2:MyBatis的一级缓存什么时候会失效? 解析:
一级缓存默认开启,在以下场景会失效:
执行insert、update、delete操作:MyBatis会自动清空当前SqlSession的一级缓存,避免缓存数据与数据库数据不一致。
手动调用SqlSession.clearCache()方法:主动清空一级缓存。
SqlSession关闭:SqlSession关闭后,一级缓存随之销毁,数据丢失。
查询参数或SQL语句不同:一级缓存的key由SQL语句、参数、RowBounds等组成,若任意条件不同,视为不同的查询,不会命中缓存。
跨SqlSession查询:不同的SqlSession拥有独立的一级缓存,无法共享数据。
5. 综合应用类 Q1:MyBatis如何实现分页?有哪些方式? 解析:
MyBatis支持两种分页方式,核心区别在于“分页时机”(内存中分页 vs 数据库中分页):
RowBounds分页(内存分页,不推荐): 原理:通过RowBounds对象设置起始行(offset)和查询条数(limit),MyBatis先查询出全部结果,再在内存中截取指定范围的数据。
缺点:大数据量场景下效率极低,会占用大量内存。
插件分页(物理分页,推荐): 原理:使用第三方插件(如PageHelper),通过MyBatis的插件机制拦截SQL语句,动态添加分页语法(如MySQL的LIMIT、Oracle的ROWNUM),直接从数据库查询分页结果。
Q2:MyBatis如何集成Spring?核心配置有哪些? 解析:
MyBatis与Spring集成的核心是“将MyBatis的核心组件(SqlSessionFactory、SqlSession、Mapper)交给Spring容器管理”,实现依赖注入和事务统一管理,核心配置步骤:
导入集成依赖(Maven): `
org.mybatis
mybatis-spring
2.0.7
org.springframework
spring-jdbc
5.3.29
`
Spring配置文件(applicationContext.xml)核心配置: 配置数据源(交给Spring管理,替代MyBatis的dataSource配置): `
`
配置SqlSessionFactoryBean(替代SqlSessionFactoryBuilder,创建SqlSessionFactory)