Mybatis 面试题

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

#{}是预编译处理,${}是字符串替换。
Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set
方法来赋值;
Mybatis 在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止 SQL 注入,提高系统安全性。

2. 通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应, 请问,这个 Dao 接口的工作原理是什么?Dao 接口里的方法, 参数不同时,方法能重载吗?

Dao 接口,就是人们常说的 Mapper 接口,接口的全限名,就是映射文件中的 namespace
的值,接口的方法名,就是映射文件中 MappedStatement 的 id 值,接口方法内的参数,
就是传递给 sql 的参数。Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+
方法名拼接字符串作为 key 值,可唯一定位一个 MappedStatement
Dao 接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。
Dao 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK 动态代理为 Dao
接 口 生 成 代 理 proxy 对 象 , 代 理 对 象 proxy 会 拦 截 接 口 方 法 , 转 而 执 行
MappedStatement 所代表的 sql,然后将 sql 执行结果返回。

3. Mybatis 是如何进行分页的?分页插件的原理是什么?

Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,
而非物理分页,可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,也可以使
用分页插件来完成物理分页。
分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,在插件的
拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分
页语句和物理分页参数。

4. Mybatis 是如何将 sql 执行结果封装为目标对象并返回的?

都有哪些映射形式?

第一种是使用<resultMap>标签,逐一定义列名和对象属性名之间的映射关系。第二
种是使用 sql 列的别名功能,将列别名书写为对象属性名
有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使用反射给对象
的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。

5. Xml 映射文件中,除了常见的 select|insert|update|delete 标 签之外,还有哪些标签?

还 有 很 多 其 他 的 标 签 , 加 上 动 态 sql 的 9 个 标 签 ,
trim|where|set|foreach|if|choose|when|otherwise|bind 等 , 其 中 为 sql 片
段标签,通过标签引入 sql 片段,为不支持自增的主键生成策略标签。

6. 简述 Mybatis 的插件运行原理,以及如何编写一个插件

Mybatis 仅 可 以 编 写 针 对 ParameterHandler 、 ResultSetHandler 、
StatementHandler、Executor 这 4 种接口的插件,Mybatis 使用 JDK 的动态代理,
为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象
的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke()方法,当
然,只会拦截那些你指定需要拦截的方法。实现 Mybatis 的 Interceptor 接口并复写
intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,
记住,还需要在配置文件中配置你编写的插件。

7. 一级、二级缓存

1)一级缓存: 基于 PerpetualCache永久缓存 的 HashMap 本地缓存,其存储作用域为
Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将
清空。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存
储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如
Ehcache。要开启二级缓存,你需要在你的 SQL 映射文件中添加一行:<cache/>
3 ) 对 于 缓 存 数 据 更 新 机 制 , 当 某 一 个 作 用 域 ( 一 级 缓 存 Session/ 二 级 缓 存
Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将
被 clear。

8. Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?

Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。
概念: 延迟加载的原理是在查询时只加载部分数据,当需要访问未加载的数据时再进行加载。这样可以减少查询所需的时间和资源,提高系统性能。
原理: MyBatis实现延迟加载的方式是使用代理对象,在访问未加载的数据时触发代理对象的方法,从而进行数据的加载。延迟加载可以通过配置文件或注解来实现。

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

虽然 Mybatis 解析 Xml 映射文件是按照顺序解析的,但是,被引用的 B 标签依然可
以定义在任何地方,Mybatis 都可以正确识别。
原理是,Mybatis 解析 A 标签,发现 A 标签引用了 B 标签,但是 B 标签尚未解析到,
尚不存在,此时,Mybatis 会将 A 标签标记为未解析状态,然后继续解析余下的标签,
包含 B 标签,待所有标签解析完毕,Mybatis 会重新解析那些被标记为未解析的标
签,此时再解析 A 标签时,B 标签已经存在,A 标签也就可以正常解析完成了。

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

Mybatis 将所有 Xml 配置信息都封装到 All-In-One 重量级对象 Configuration 内
部。在 Xml 映射文件中,<parameterMap>标签会被解析为 ParameterMap 对象,
其每个子元素会被解析为 ParameterMapping 对象。<resultMap>标签会被解析为
ResultMap 对象,其每个子元素会被解析为 ResultMapping 对象。每一个<select>、
<insert>、<update>、<delete>标签均会被解析为 MappedStatement 对象,标签
内的 sql 会被解析为 BoundSql 对象。

3. MyBatis 使用过程?生命周期?

MyBatis 基本使用的过程大概可以分为这么几步:

Mybatis基本使用步骤

Mybatis基本使用步骤
  • 1)创建 SqlSessionFactory

可以从配置或者直接编码来创建 SqlSessionFactory

  • 2)通过 SqlSessionFactory 创建 SqlSession

SqlSession(会话)可以理解为程序和数据库之间的桥梁

SqlSession session = sqlSessionFactory.openSession();
  • 3)通过 sqlsession 执行数据库操作

可以通过 SqlSession 实例来直接执行已映射的 SQL 语句:

Blog blog = (Blog)session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);

更常用的方式是先获取 Mapper(映射),然后再执行 SQL 语句:

BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
  • 4)调用 session.commit()提交事务

如果是更新、删除语句,我们还需要提交一下事务。

  • 5)调用 session.close()关闭会话

最后一定要记得关闭会话

4. 在 mapper 中如何传递多个参数?

mapper传递多个参数方法

mapper传递多个参数方法

方法 1:顺序传参法

public User selectUser(String name, int deptId);

<select id="selectUser" resultMap="UserResultMap">
    select * from user
    where user_name = #{0} and dept_id = #{1}
</select>
  • \#{}里面的数字代表传入参数的顺序。
  • 这种方法不建议使用,sql 层表达不直观,且一旦顺序调整容易出错。

方法 2:@Param 注解传参法

public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);

<select id="selectUser" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>
  • \#{}里面的名称对应的是注解@Param 括号里面修饰的名称。
  • 这种方法在参数不多的情况还是比较直观的,(推荐使用)。

方法 3:Map 传参法

public User selectUser(Map<String, Object> params);

<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>
  • \#{}里面的名称对应的是 Map 里面的 key 名称。
  • 这种方法适合传递多个参数,且参数易变能灵活传递的情况。

方法 4:Java Bean 传参法

public User selectUser(User user);

<select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>
  • \#{}里面的名称对应的是 User 类里面的成员属性。
  • 这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑处理方便,推荐使用。(推荐使用)。

6. Mybatis 是否可以映射 Enum 枚举类?

  • Mybatis 当然可以映射枚举类,不单可以映射枚举类,Mybatis 可以映射任何对象到表的一列上。映射方式为自定义一个 TypeHandler,实现 TypeHandler 的 setParameter()和 getResult()接口方法。
  • TypeHandler 有两个作用,一是完成从 javaType 至 jdbcType 的转换,二是完成 jdbcType 至 javaType 的转换,体现为 setParameter()和 getResult()两个方法,分别代表设置 sql 问号占位符参数和获取列查询结果

8. 模糊查询 like 语句该怎么写?

concat拼接like

concat拼接like
  • 1 ’%${question}%’ 可能引起 SQL 注入,不推荐
  • "%"#{question}"%" 注意:因为#{…}解析成 sql 语句时候,会在变量外侧自动加单引号’ ',所以这里 % 需要使用双引号" ",不能使用单引号 ’ ',不然会查不到任何结果。
  • CONCAT('%',#{question},'%') 使用 CONCAT()函数,(推荐 ✨)
  • 4 使用 bind 标签(不推荐)

11. 如何获取生成的主键?

  • 新增标签中添加:keyProperty=" ID " 即可

<insert id="insert" useGeneratedKeys="true" keyProperty="userId" >
    insert into user(
    user_name, user_password, create_time)
    values(#{userName}, #{userPassword} , #{createTime, jdbcType= TIMESTAMP})
</insert>

12. MyBatis 支持动态 SQL 吗?

MyBatis 中有一些支持动态 SQL 的标签,它们的原理是使用 OGNL 从 SQL 参数对象中计算表达式的值,根据表达式的值动态拼接 SQL,以此来完成动态 SQL 的功能。

MyBatis

MyBatis
  • if

根据条件来组成 where 子句

<select id="findActiveBlogWithTitleLike"
   resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
  AND title like #{title}
</if>
</select>
  • choose (when, otherwise)

这个和 Java 中的 switch 语句有点像

  • <where>可以用在所有的查询条件都是动态的情况

<select id="findActiveBlogLike"
   resultType="Blog">
SELECT * FROM BLOG
<where>
  <if test="state != null">
       state = #{state}
  </if>
  <if test="title != null">
      AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
      AND author_name like #{author.name}
  </if>
</where>
</select>
  • <set> 可以用在动态更新的时候

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>
  • foreach

    看到名字就知道了,这个是用来循环的,可以对集合进行遍历

<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
  <foreach item="item" index="index" collection="list"
      open="ID in (" separator="," close=")" nullable="true">
        #{item}
  </foreach>
</where>
</select>

13. MyBatis 如何执行批量操作?

MyBatis批量操作

MyBatis批量操作

第一种方法:使用 foreach 标签

foreach 的主要用在构建 in 条件中,它可以在 SQL 语句中进行迭代一个集合。foreach 标签的属性主要有 item,index,collection,open,separator,close。

  • item   表示集合中每一个元素进行迭代时的别名,随便起的变量名;
  • index   指定一个名字,用于表示在迭代过程中,每次迭代到的位置,不常用;
  • open   表示该语句以什么开始,常用“(”;
  • separator 表示在每次进行迭代之间以什么符号作为分隔符,常用“,”;
  • close   表示以什么结束,常用“)”。

在使用 foreach 的时候最关键的也是最容易出错的就是 collection 属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有以下 3 种情况:

  1. 如果传入的是单参数且参数类型是一个 List 的时候,collection 属性值为 list
  2. 如果传入的是单参数且参数类型是一个 array 数组的时候,collection 的属性值为 array
  3. 如果传入的参数是多个的时候,我们就需要把它们封装成一个 Map 了

15. 能说说 MyBatis 的工作原理吗?

我们最后把整个的工作流程串联起来,简单总结一下:

MyBatis整体工作原理图

MyBatis整体工作原理图
  1. 读取 MyBatis 配置文件——mybatis-config.xml 、加载映射文件——映射文件即 SQL 映射文件,文件中配置了操作数据库的 SQL 语句。最后生成一个配置对象。
  2. 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
  3. 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
  4. Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
  5. StatementHandler:数据库会话器,串联起参数映射的处理和运行结果映射的处理。
  6. 参数处理:对输入参数的类型进行处理,并预编译。
  7. 结果处理:对返回结果的类型进行处理,根据对象映射规则,返回相应的对象。

16. MyBatis 的功能架构是什么样的?

MyBatis功能架构

 

我们一般把 Mybatis 的功能架构分为三层:

  • API 接口层:提供给外部使用的接口 API,开发人员通过这些本地 API 来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
  • 数据处理层:负责具体的 SQL 查找、SQL 解析、SQL 执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
  • 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

7. 为什么 Mapper 接口不需要实现类?

我们来看一下获取 Mapper 的过程:

Mapper代理

定义的 Mapper 接口是没有实现类的,Mapper 映射其实是通过动态代理实现的

获取 Mapper 的过程,需要先获取 MapperProxyFactory——Mapper 代理工厂。

MapperProxyFactory 代理工厂的作用是生成 MapperProxy(Mapper 代理对象)。

MapperProxy 里,通常会生成一个 MapperMethod 对象

MapperMethod 里的 excute 方法,会真正去执行 sql。这里用到了命令模式

18.Mybatis 都有哪些 Executor 执行器?

Mybatis Executor类型

Mybatis Executor类型

Mybatis 有三种基本的 Executor 执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

  • SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。
  • ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map<String, Statement>内,供下一次使用。简言之,就是重复使用 Statement 对象。
  • BatchExecutor:执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行 executeBatch()批处理。与 JDBC 批处理相同

如何编写一个插件?

  • 实现 Mybatis 的 Interceptor 接口并重写 intercept()方法
  • 然后再给插件编写注解,确定要拦截的对象,要拦截的方法
  • 最后,再 MyBatis 配置文件里面配置插件

<plugins>
    <plugin interceptor="xxx.MyPlugin">
       <property name="dbType",value="mysql"/>
    </plugin>
</plugins>

19. 说说 Mybatis 的插件运行原理,如何编写一个插件?

Mybatis 会话的运行需要 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这四大对象的配合,插件的原理就是在这四大对象调度的时候,插入一些我我们自己的代码。

 

具体使用 JDK 的动态代理,为目标对象生成代理对象。它提供了一个工具类Plugin,实现了InvocationHandler接口。代理对象在调用方法的时候,就会进入 invoke 方法,在 invoke 方法中,如果存在签名的拦截方法,拦截方法就会在这里被我们调用,然后就返回结果。如果不存在签名的拦截方法,那么将直接反射调用我们要执行的方法。

21.说说 JDBC 的执行步骤?

通过Class.forName()方法加载对应的数据库驱动

使用DriverManager.getConnection()方法建立到数据库的连接。这一步需要提供数据库 URL、用户名和密码作为参数。

第三步,创建Statement对象

通过建立的数据库连接对象Connection创建StatementPreparedStatementCallableStatement对象,用于执行 SQL 语句。

使用StatementPreparedStatement对象执行 SQL 语句

执行查询(SELECT)语句时,使用executeQuery()方法,它返回ResultSet对象;

执行更新(INSERT、UPDATE、DELETE)语句时,使用executeUpdate()方法,它返回一个整数表示受影响的行数。

第五步,处理结果集

如果执行的是查询操作,需要处理ResultSet对象来获取数据。

第六步,关闭资源

最后,需要依次关闭ResultSetStatementConnection等资源,释放数据库连接等资源。

22.创建连接拿到的是什么对象?

在 JDBC 的执行步骤中,创建连接后拿到的对象是java.sql.Connection对象。

Connection对象代表了应用程序和数据库的一个连接会话。

通过调用DriverManager.getConnection()方法并传入数据库的 URL、用户名和密码等信息来获得这个对象。

一旦获得Connection对象,就可以使用它来创建执行 SQL 语句的StatementPreparedStatementCallableStatement对象,以及管理事务等。

23.Statement 与 PreparedStatement 的区别

StatementPreparedStatement都是用于执行 SQL 语句的接口,

①、每次执行Statement对象的executeQueryexecuteUpdate方法时,SQL 语句在数据库端都需要重新编译和执行。这适用于一次性执行的 SQL 语句。

②、PreparedStatement 代表预编译的 SQL 语句的对象。这意味着 SQL 语句在PreparedStatement对象创建时就被发送到数据库进行预编译。

之后,可以通过设置参数值来多次高效地执行这个 SQL 语句。这不仅减少了数据库编译 SQL 语句的开销,也提高了性能,尤其是对于重复执行的 SQL 操作。

Statement 不支持参数化查询。如果需要在 SQL 语句中插入变量,通常需要通过字符串拼接的方式来实现,这会增加 SQL 注入攻击的风险。

PreparedStatement 支持参数化查询,即可以在 SQL 语句中使用问号(?)作为参数占位符。通过setXxx方法(如setStringsetInt)设置参数,可以有效防止 SQL 注入。

24. 什么是 SQL 注入?如何防止 SQL 注入?

SQL 注入是一种代码注入技术,通过在输入字段中插入专用的 SQL 语句,从而欺骗数据库执行恶意 SQL,从而获取敏感数据、修改数据,或者删除数据等。

实际的 SQL 语句类似于:

SELECT * FROM students WHERE studentId = 117

这是我们期望用户输入的正确方式。但是,如果用户输入了117 OR 1=1,那么 SQL 语句就变成了:

SELECT * FROM students WHERE studentId = 117 OR 1=1

由于1=1为真,所以这个查询将返回所有学生的信息,而不仅仅是 ID 为 117 的学生。

①、使用参数化查询

使用参数化查询,即使用PreparedStatement对象,通过setXxx方法设置参数值,而不是通过字符串拼接 SQL 语句。这样可以有效防止 SQL 注入。

②、限制用户输入

对用户输入进行验证和过滤,只允许输入预期的数据,不允许输入特殊字符或 SQL 关键字

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值