mybatis 结果集中嵌套查询中的循环依赖问题分析

  • Post author:
  • Post category:其他




循环依赖实例

mybatis的循环依赖,即是mapper.xml里面的A查询的resultMap包含了B属性(B属性是通过子查询得到的),而B属性中又包含了A(B查询的resultMap中又包含了A的查询),就会造成A-B-A的情况。

实际的代码样例如下:

mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.BlogMapper">
    <cache></cache>

    <resultMap id="blogMap" type="com.entity.Blog" autoMapping="true">
        <result column="title" property="title"></result>
        <collection property="comments" column="id" select="selectCommentsByBlogId">
        </collection>
    </resultMap>

    <resultMap id="commentMap" type="com.entity.Comment" autoMapping="true">
        <result column="title" property="title"></result>
        <association property="blog" column="blog_id" select="selectBlogById"></association>
    </resultMap>

    <select id="selectCommentsByBlogId" resultMap="commentMap">
        select * from  comment where blog_id = #{id}
    </select>

    <select id="selectBlogById" resultMap="blogMap">
        select * from  blog where id = #{id}
    </select>

</mapper>

实体类Blog

package com.entity;

import java.io.Serializable;
import java.util.List;

public class Blog implements Serializable {
   private List<Comment> comments;
   private String title;
 

   public  Blog(){
   }
   
    public List<Comment> getComments() {
        return comments;
    }

    public void setComments(List<Comment> comments) {
        this.comments = comments;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

实体类Comment

package com.entity;

import java.io.Serializable;
import java.util.Date;

public class Comment implements Serializable {
    private int id;
    private String content;
    private Date date;

    private Blog blog;

    public Blog getBlog() {
        return blog;
    }

    public void setBlog(Blog blog) {
        this.blog = blog;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}



循环依赖流程

在这里插入图片描述



循环依赖源码解决方案解析

以下解析可能需要一定的mybatis基础才能看懂,因为源码跳转比较复杂,所以我就不列出来源码链了,看关键位置即可,先总结一下解决思路:

主要使用延迟加载+缓存占位符的做法,在嵌套查询结束时再填充属性,执行流程如下:

1、第一次查询,也就是查询第一层,发现没缓存,直接查数据库:

在这里插入图片描述

在查之前将自身放入缓存中,缓存的值为一个占位符:

在这里插入图片描述

在查询解析过程中发现了resultMap中还有嵌套查询,于是进入嵌套查询的解析中,而该嵌套查询没有命中缓存(还没执行),也没指定延迟加载,于是直接实时加载,进入2

在这里插入图片描述

2、实时加载即是直接调用执行器excetor进行查询,此时进入了第二层查询,同样发现没缓存,直接查数据库,查之前也将自身作为key和占位符放入缓存,在查询解析过程中发现了自己还有嵌套查询,先判断嵌套查询在不在缓存中,此时就在了(因为1步骤时第一层把自身为key值为占位符放入缓存了),将这个嵌套查询语句放入延迟加载的列表中,并返回表示第二层结束了。

在这里插入图片描述

在这里插入图片描述

3、因为第二层退出了,所以第一层可以继续执行,执行完后将真正的结果放入缓存:

在这里插入图片描述

然后进入当前是否为第一层的判断,此时为是,则将延迟加载列表中的操作遍历执行:

在这里插入图片描述

延迟加载里面将第一层的结果取出来放入2步骤中的第二层结果里,然后结束查询。

在这里插入图片描述

一级缓存不能关闭的原因就在于要解决嵌套查询的循环依赖问题。



版权声明:本文为lyd135364原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。