循环依赖实例
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步骤中的第二层结果里,然后结束查询。
一级缓存不能关闭的原因就在于要解决嵌套查询的循环依赖问题。