MyBatis学习笔记

1、#{}和${}的区别是什么?

注:面试官真题。

⭐参考阅读一篇写得非常好的文章:#{}与${}的区别

#{}是预编译处理,${}是字符串替换。

  • #{}
    • mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatementsetString(int parameterIndex, String x)方法来赋值,这个方法会将用户输入的单引号'加上转义字符\,以起到阻止恶意sql注入的问题;
    • 同时对#{}会有预编译的机制。
      • 预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。
      • 也就是说,编译之后SQL语句的结构不会再发生变化,#{}指代的位置也仅仅作为字符串填入,因此恶意SQL字符串不会再改变SQL语句的结构。

⭐关于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注入。

使用 #{} 可以有效的防止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>

⭐参考:Mybatis:resultMap的使用总结

  • ~~<parameterMap>~~(已弃用, 改用内联参数映射和 parameterType 属性)
  • <sql>
    • 使用sql标签减少重复的sql语句,不仅可以减少我们的代码量,还更加方便我们查看!

⭐参考:

  • <include>
  • <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 ,可以唯一找到 namespacecom.mybatis3.mappers.StudentDao 下面 id = findStudentByIdMappedStatement
  • 在 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 接口方法可以重载,但是需要满足以下条件:

  1. 仅有一个无参方法和一个有参方法
  2. 多个有参方法时,参数数量必须一致。且使用相同的 @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 是如何进行分页的?分页插件的原理是什么?

答:

  1. MyBatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页;
    1. mybatis之RowBounds分页代码实现
  2. 可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能(Limit)
  3. 也可以使用分页插件来完成物理分页。

分页插件的基本原理是使用 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,支持延迟加载的原理都是一样的。

参考:MyBatis 常见面试总结

posted @   Excelsiorly  阅读(67)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示