MyBatis学习笔记
1、#{}和${}的区别是什么?
注:面试官真题。
⭐参考阅读一篇写得非常好的文章:#{}与${}的区别
#{}
是预编译处理,${}
是字符串替换。
#{}
- mybatis在处理
#{}
时,会将sql中的#{}
替换为?
号,调用PreparedStatement
的setString(int parameterIndex, String x)
方法来赋值,这个方法会将用户输入的单引号'
加上转义字符\
,以起到阻止恶意sql注入的问题; - 同时对
#{}
会有预编译的机制。- 预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。
- 也就是说,编译之后SQL语句的结构不会再发生变化,
#{}
指代的位置也仅仅作为字符串填入,因此恶意SQL字符串不会再改变SQL语句的结构。
- mybatis在处理
⭐关于
PreparedStatement
推荐阅读:java中PreparedStatement和Statement详细讲解
其中讲到一个例子:
sql语句:**SELECT * FROM admin WHERE username = '韦小宝' AND password = ?**
如此调用**PreparedStatement.setString**
方法时:
**parepaerStatement.setString(2, "222' OR '8'='8'"**
也就是想将第二个参数password
变为**222' OR '8'='8'**
,想使得整句是
**SELECT * FROM admin WHERE username = '韦小宝' AND password = 222' OR '8'='8'**
,注入**'8'='8'**
这个恒为真的条件判断,起到绕过密码认证的过程。这就是sql注入。
而**PreparedStatement.setString**
会起到作用,使得实际上的sql语句变成:
**SELECT * FROM admin WHERE username = '韦小宝' AND password = '222\' OR \'8\'=\'8'**
,将'
给转义成了普通字符,而没有了断开字符的功能,从而阻止了sql注入。
${}
- mybatis在处理
${}
时,就是把${}
替换成变量的值。 - 也就是说,恶意SQL语句还是可以改变原本SQL语句的结构,无法阻止SQL注入。
- mybatis在处理
使用 #{} 可以有效的防止SQL注入,提高系统安全性。
2、XML映射文件中,除了常见的 select|insert|update|delete
标签之外,还有哪些标签?
注:京东面试。
答:还有很多其他的标签,
<resultMap>
:
resultMap是Mybatis最强大的元素,它可以将查询到的复杂数据(比如查询到几个表中数据)映射到一个结果集当中。
<resultMap id="唯一的标识" type="映射的pojo对象">
<id column="表的主键字段,或者可以为查询语句中的别名字段" jdbcType="字段类型" property="映射pojo对象的主键属性" />
<result column="表的一个字段(可以为任意表的一个字段)" jdbcType="字段类型" property="映射到pojo对象的一个属性(须为type定义的pojo对象中的一个属性)"/>
<result column=... .../>
<result column=... .../>
...
<!--POJO的对象属性-->
<association property="pojo的一个对象属性" javaType="pojo关联的pojo对象">
<id column="关联pojo对象对应表的主键字段" jdbcType="字段类型" property="关联pojo对象的主席属性"/>
<result column="任意表的字段" jdbcType="字段类型" property="关联pojo对象的属性"/>
</association>
<association property=.../>
<association property=.../>
...
<!-- 集合中的property须为oftype定义的pojo对象的属性-->
<collection property="pojo的集合属性" ofType="集合中的pojo对象">
<id column="集合中pojo对象对应的表的主键字段" jdbcType="字段类型" property="集合中pojo对象的主键属性" />
<result column="可以为任意表的字段" jdbcType="字段类型" property="集合中的pojo对象的属性" />
</collection>
<collection .../>
<collection .../>
...
</resultMap>
~~<parameterMap>~~
(已弃用, 改用内联参数映射和parameterType
属性)<sql>
- 使用sql标签减少重复的sql语句,不仅可以减少我们的代码量,还更加方便我们查看!
⭐参考:
<include>
- include标签引用,可以复用SQL片段
- mybatis中
标签的作用
<selectKey>
<selectKey>
在Mybatis中是为了解决Insert数据时不支持主键自动生成的问题, 将<selectKey>
放在<insert>
之后,通过LAST_INSERT_ID()
获得刚插入的自动增长的id的值。- Mybatis 示例之 SelectKey
- 加上动态
的 9 个标签, trim|where|set|foreach|if|choose|when|otherwise|bind
等,其中<sql>
为 sql 片段标签,通过<include>
标签引入 sql 片段,<selectKey>
为不支持自增的主键生成策略标签
3、最佳实践中,通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应,请问,这个 Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?
注:京东面试。
答:Dao
接口,就是人们常说的 Mapper 接口
- 接口的全限名,就是映射文件中的
namespace
的值
<mapper namespace="com.project.module.dao.PojoDao">
...
- 接口的方法名,就是映射文件中
MappedStatement
的 id 值
<select id="getXXXX" resultType="java.util.Map">
...
</select>
- 接口方法内的参数,就是传递给 sql 的参数。 Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个
MappedStatement
,举例:com.mybatis3.mappers.StudentDao.findStudentById
,可以唯一找到namespace
为com.mybatis3.mappers.StudentDao
下面id = findStudentById
的MappedStatement
。 - 在 MyBatis 中,每一个
<select>
、<insert>
、<update>
、<delete>
标签,都会被解析为一个MappedStatement
对象。
Dao 接口里的方法可以重载,但是 Mybatis 的 XML 里面的 ID 不允许重复, 但是可以用动态sql的方式实现
比如:
Mapper
:
/**
* Mapper接口里面方法重载
*/
public interface StuMapper {
List<Student> getAllStu();
List<Student> getAllStu(@Param("id") Integer id);
}
xml
文件:
<select id="getAllStu" resultType="com.pojo.Student">
select * from student
<where>
<if test="id != null">
id = #{id}
</if>
</where>
</select>
能正常运行,并能得到相应的结果,这样就实现了在 Dao 接口中写重载方法。
Mybatis 的 Dao 接口可以有多个重载方法,但是多个接口对应的映射必须只有一个,否则启动会报错。
Dao 接口方法可以重载,但是需要满足以下条件:
- 仅有一个无参方法和一个有参方法
- 多个有参方法时,参数数量必须一致。且使用相同的
@Param
,或者使用param1
这种, 不一致时取最长的参数数量,其他数量不符合的方法报错。
测试如下:
PersonDao.java
Person queryById();
Person queryById(@Param("id") Long id);
Person queryById(@Param("id") Long id, @Param("name") String name);
PersonMapper.xml
<select id="queryById" resultMap="PersonMap">
select
id, name, age, address
from person
<where>
<if test="id != null">
id = #{id}
</if>
<if test="name != null and name != ''">
name = #{name}
</if>
</where>
limit 1
</select>
queryById()
方法执行时,无参数,动态 sql 可以正常执行。queryById(1L,"1")
方法执行时,参数id和name属性都可以获取到,动态 sql 正常执行。queryById(1L)
方法执行时,只有一个参数,报错。
4、MyBatis 是如何进行分页的?分页插件的原理是什么?
答:
- MyBatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页;
- 可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能(Limit)
- 也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用 MyBatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。
举例: select _ from student
,拦截 sql 后重写为: select t._ from (select \* from student)t limit 0,10
10、MyBatis 是否支持延迟加载?如果支持,它的实现原理是什么?
答:MyBatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association
指的就是一对一
,collection
指的就是一对多
查询。在 MyBatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false
。
⭐推荐阅读:Mybatis延迟加载的实现以及使用场景
它的原理是,使用 CGLIB
创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,
比如调用 a.getB().getName()
,拦截器 invoke()
方法发现 a.getB()
是 null
值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b)
,于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()
方法的调用。这就是延迟加载的基本原理。
当然了,不光是 MyBatis,几乎所有的包括 Hibernate,支持延迟加载的原理都是一样的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构