MyBatis 面试题 - litter-fish/ReadSource GitHub Wiki

MyBatis 的缓存

一级缓存和二级缓存。

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

配置解析阶段:
    解析配置文件,将插件的实例对象加入拦截器链InterceptorChain的interceptors集合中。
获取SqlSession实例阶段:
    在创建执行器Executor时会调用具体插件的plugin方法,该方法会通过JDK的动态代理创建执行器Executor的一个代理类
执行代码阶段:
    在执行逻辑时,会调用执行器Executor代理类的invoke方法,该方法会先执行自定义插件的intercept方法,然后接着执行真正逻辑。

可以通过实现接口Interceptor,然后重写方法。并配置类注解

@Intercepts({
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
})

Mybatis 动态 sql 是做什么的?都有哪些动态 sql?能简述一下动态 sql 的执行原理不

#{}和${}的区别

#{} 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符,一个 #{ } 被解析为一个参数占位符;
${}仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换。
#{} 解析之后会将String类型的数据自动加上引号,其他数据类型不会;而${} 解析之后是什么就是什么,他不会当做字符串处理。
#{} 很大程度上可以防止SQL注入。
排序时使用order by 动态参数时需要注意,用$而不是#。

Mybatis 延迟加载的实现原理

1. 首先在解析一个查询结果时,在进行行记录解析时,如果发现开启类延迟加载且存在嵌套select查询,则会通过Javassist为结果对象生成代理类。
2. 接着在给对象值进行数据填充时,如果发现这个属性是一个嵌套的select查询,则会通过指定的ID获取MappedStatement、Sql等查询需要的信息
封装成一个ResultLoader对象,接着将其存储在HashMap中。
3. 当调用获取属性时,会通过调用代理对象注册的回调逻辑,执行延迟加载逻辑

MyBatis的接口绑定,有几种实现方式,分别是怎么实现的?

MyBatis实现一对一有几种方式?具体怎么操作的

方式一: 联合查询,然后通过association节点的resultMap属性指定
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
  <result property="password" column="author_password"/>
  <result property="email" column="author_email"/>
  <result property="bio" column="author_bio"/>
</resultMap>

方式二:关联的嵌套 Select 查询,通过association节点的select属性指定
<resultMap id="blogResult" type="Blog">
  <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</select>

方式三:关联的多结果集(ResultSet),存储过程返回多个结果集
<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
  {call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>

<resultMap id="blogResult" type="Blog">
  <id property="id" column="id" />
  <result property="title" column="title"/>
  <association property="author" javaType="Author" resultSet="authors" column="author_id" foreignColumn="id">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="password" column="password"/>
    <result property="email" column="email"/>
    <result property="bio" column="bio"/>
  </association>
</resultMap>

MyBatis实现一对多有几种方式,怎么操作的

方式一: 联合查询,然后通过collection节点的resultMap属性指定
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
  </collection>
</resultMap>

方式二:集合的嵌套 Select 查询
<resultMap id="blogResult" type="Blog">
  <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectPostsForBlog" resultType="Post">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

方式三:关联的多结果集(ResultSet),存储过程返回多个结果集
<resultMap id="blogResult" type="Blog">
  <id property="id" column="id" />
  <result property="title" column="title"/>
  <collection property="posts" ofType="Post" resultSet="posts" column="id" foreignColumn="blog_id">
    <id property="id" column="id"/>
    <result property="subject" column="subject"/>
    <result property="body" column="body"/>
  </collection>
</resultMap>

解决 N+1 查询问题的方法。

使用存储过程返回多个结果集,或一次性执行多个语句,每个语句返回一个结果集。 例:存储过程执行下面的查询并返回两个结果集。第一个结果集会返回博客(Blog)的结果,第二个则返回作者(Author)的结果

SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM AUTHOR WHERE ID = #{id}

在映射语句中,必须通过 resultSets 属性为每个结果集指定一个名字,多个名字使用逗号隔开。

<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
  {call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>

<resultMap id="blogResult" type="Blog">
  <id property="id" column="id" />
  <result property="title" column="title"/>
  <association property="author" javaType="Author" resultSet="authors" column="author_id" foreignColumn="id">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="password" column="password"/>
    <result property="email" column="email"/>
    <result property="bio" column="bio"/>
  </association></resultMap>
⚠️ **GitHub.com Fallback** ⚠️