1. 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
Hibernate属于全自动ORM映射工具,使用Hibernate查找关联对象或关联集合对象时,根据关系模型对象直接获取,Mybatis在查找关联对象或关联集合对象时,需要手动编写sql来完成。
2. #{}和${}的区别是什么?
- #{}是预编译处理,${}是字符串替换
总结:优先使用#{}。仅在需要动态插入SQL关键字(非值)切能保证输入安全时,才考虑使用${}
#{}(预编译占位符)
原理:会被解析为JDBC中preparedStatement的参数占位符?
安全性:能有效防止SQL注入,因为传入的值会被当作参数处理,而不是直接拼到SQL语句中
类型处理:MyBatis会根据参数类型自动进行转换(如String加单引号)
使用场景:几乎适用于所有传入参数值的地方
${}(字符串替换)
原理:会被直接替换成对应参数值,原样直接拼到SQL语句中
安全性:存在SQL注入的风险,因为传入的值直接成为SQL语句中一部分
使用场景:
动态指定ORDER BY列名 (如ORDER BY ${columnName})
动态指定表名(如 FROM ${tableName})
插入SQL语句片段
3. MyBatis工作流程
- 创建SqlSessionFactoryBuilder对象,调用build(inputStream)方法读取并解析配置文件,返回SqlSessionFactory对象。
- SqlSessionFactory创建SqlSession对象,默认开启
- 调用SqlSession中的api,传递Startment Id和参数,最后调用jdbc执行sql语句执行,完成封装并返回。
4. MyBatis 的工作原理
1.读取MyBaits配置文件:在mybatis-config.xml中配置MyBatis的运行环境等信息。
2.加载映射文件:SQL映射文件,在mybatis-config.xml中配加载多个映射文件,每个文件对应数据库中的一张表
3.构造会话工厂:通过mybatis的环境等配置信息构架会话工厂SqlSessionFactory。
4.创建会话对象:由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。
5.Executor执行器:mybatis底层定义了一个Executor接口来操作数据库,它根据SqlSession传递的参数动态的生成需要执行的SQL语句,同时负责查询缓存的维护。
6.MappedStatement对象:在Executor接口中执行方法中有一个MappedStatement类型参数,主要用于存储要映射的SQL语句的id,参数等信息。
7.输入参数映射
8.输出参数映射
5. MyBatis有几种分页方式?逻辑分页(内存分页)
5.1 逻辑分页(内存分页)
(1) 使用MyBatis内置的RowBounds对象
(2) 原理:一次性查出所有满足条件的数据到内存中,然后在内存中进行分页截取
(3) 优点:实现简单
(4) 缺点:性能差(数据量大时内存消耗大、查询慢)、有OOM风险
5.2 物理分页(数据库分页)
(1)原生SQL分页:在SQL语句中直接使用数据库特定的分页语法(如MySQL的LIMIT offset,size,Oracle的ROWNUM/窗口函数)
(2)分页插件:使用第三方分页插件(如PageHelper)
(3)原理:在SQL执行前,插件根据分页参数(页码、每页条数)动态修改SQL,添加数据库特定的分页语句,让数据库只返回当前的数据
(4)优点:性能好(数据库只处理一页数据)、内存消耗小
(5)缺点:需要编写特定SQL或引入插件
6. RowBounds是一次性查询全部结果吗?为什么?
是的,RowBounds是一次性查询全部结果的逻辑分页
因为RowBounds本身只是一个参数对象(包含offset和limit),它传递给Executor。默认的Executor实现(如SimpleExecutor)在执行查询时,会忽略RowBounds的offset和limit,执行完整的SQL查询,获取所有结果集ResultSet。在将 ResultSet 映射成对象的过程中,ResultSetHandler 会根据 RowBounds 的 offset 和 limit 在内存中跳过前面的记录并只取指定条数。因此,数据库返回的是所有数据,分页是在应用层内存中完成的。
7. MyBatis 逻辑分页和物理分页的区别是什么?
特性 |
逻辑分页(RowBounds) |
物理分页(如原生SQL LIMIT/PageHel) |
执行位置 |
应用层内存 |
数据库层 |
SQL |
执行完整的查询SQL |
执行添加分页子句的SQL 如LIMIT ? |
返回数据 |
数据库返回所有满足条件的数据 |
数据只返回当前查询页的数据 |
性能 |
差(占用内存,网络传输慢,慢查询),大数据量不推荐 |
好(只查询当前页数据) |
内存风险 |
高(大数据量OOM风险) |
低 |
实现方式 |
MyBatis内置(RowBounds) |
SQL语句分页或使用分页插件(PageHelper) |
使用场景 |
数据量非常小 |
绝大多数分页场景 |
8. MyBatis 是否支持延迟加载?延迟加载的原理是什么?
(1)支持延迟加载:MyBatis支持关联对象(association,collection)的延迟加载(懒加载)
(2)原理:
代理对象:当查询主对象时,MyBatis 返回的是一个动态代理对象(如 JavassistProxyFactory 或 CglibProxyFactory 生成),而不是真实的实体类对象。
拦截调用:当应用程序首次访问这个代理对象的某个延迟加载属性(如 user.getOrders())时。
触发查询:代理对象内部会拦截这个访问方法调用。
执行子查询:拦截器(通常是 LazyLoader)根据配置的关联查询 SQL 和参数,发起一次新的 SQL 查询去加载关联数据。
填充数据:将查询到的关联数据设置到代理对象中。
返回结果:之后对该属性的访问将直接返回已加载的数据。
(3)配置:需要在全局配置文件 (mybatis-config.xml) 中显式开启:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/> <!-- 推荐false, 按需加载 -->
</settings>
(4)注意事项:需确保延迟加载操作在同一个 SqlSession 生命周期内完成,否则可能因 Session 关闭而加载失败。