Bootstrap

Mybatis如何解决循环依赖问题

1、Mybatis如何解决循环依赖问题

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

<?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>

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;
    }
}


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中利用的是一级缓存、空占位符,和延迟加载来解决循环依赖问题的。

Mybatis 框架是一个轻量级的 ORM 框架,通常不会出现循环依赖问题。但是,如果在实际应用场景中出现了循环依赖的情况,Mybatis 也提供了一些解决方案,以下是其中两种:

  • 使用延迟加载(Lazy Loading)特性
    Mybatis 支持延迟加载,可以将对象的加载推迟到真正需要使用它时再进行加载,这样就可以避免一些不必要的依赖。具体实现方式是通过在 Mapper 文件中配置使用延迟加载的关联属性或集合属性。

    • 全局配置
    <settings>
          <!-- 开启延迟加载 -->
         <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
    
    • 局部配置
      在和标签中修改"fetchType"属性的值
      fetchType = FetchType.EAGER 立即加载
      fetchType = FetchType.LAZY 延迟加载
<association property="idCard" column="card_id" select="com.ycy.mapper.IdCardMapper.findById" fetchType="eager">
  • 使用二级缓存(Second Level Cache)
    Mybatis 的二级缓存是在 SqlSessionFactory 层面上的,可以共享多个 SqlSession 实例之间的缓存数据。在使用二级缓存时,需要对每个需要缓存的 Mapper 进行配置,将其标识为需要开启缓存的 Mapper。这样在查询数据时,先从缓存中获取数据,如果缓存中不存在,则执行 SQL 查询,并将查询结果放入缓存中。

需要注意的是,虽然 Mybatis 提供了延迟加载和二级缓存等解决方案,但是过度地依赖这些特性可能会增加系统的复杂度和调试难度,因此在实际使用中需要根据具体情况进行选择和权衡。同时,在设计数据库表结构和应用程序架构时,应尽量避免出现循环依赖的情况,以确保系统的稳定性和可维护性。

2、一级缓存

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的

过程分析

  1. 第一次查询id为1 的用户,此时先去一级缓存查找,如果查找不到,则去数据库查询,把查询后的 结果存储到一级缓存中。
  2. 第二次查询id为1 的用户,此时先去一级缓存查找,如果查找到,则直接从一级缓存中把数据取出,不去查询数据库。
  3. 只要中间发生增删改操作,那么一级缓存就清空,默认开启一级缓存。

在这里插入图片描述

3、二级缓存

二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的

过程分析

  1. 第一次查询id为1 的用户,此时先去二级缓存查找,如果查找不到,则去数据库查询,把查询后的 结果存储到二级缓存中
  2. 第二次查询id为1 的用户,此时先去二级缓存查找,如果查找到,则直接从二级缓存中把数据取出,不去查询数据库
  3. 只要中间发生增删改操作,那么二级缓存就清空,二级缓存默认不开启,需要手动开启。

在这里插入图片描述

一级缓存和二级缓存的作用范围图

在这里插入图片描述

3.1、开启二级缓存

使用 Mybatis 的二级缓存需要进行以下配置:

  1. 开启全局缓存支持
    在 mybatis-config.xml 配置文件中配置如下内容:
<configuration>
  <settings>
    <setting name="cacheEnabled" value="true" />
  </settings>
</configuration>

其中,cacheEnabled 表示开启全局缓存支持。

  1. 配置 Mapper 接口开启缓存支持,在 Mapper.xml 文件中,可以针对每个 Mapper 接口进行配置,以标识某个 Mapper 接口需要使用二级缓存。例如:
<mapper namespace="com.example.mybatis.mapper.UserMapper">
  <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
  <select id="getUserById" resultType="com.example.mybatis.model.User" useCache="true">
    select * from user where id = #{id}
  </select>
</mapper>

其中,cache 标签表示使用的缓存实现类,这里使用 Ehcache 作为缓存实现;useCache 属性表示是否启用二级缓存,这里设置为 true。

  1. 配置 Ehcache 缓存实现
    如果使用 Ehcache 作为缓存实现,则需要在项目中引入 Ehcache 相关依赖,并在 Ehcache 的配置文件 ehcache.xml 中进行配置。关于 Ehcache 的详细配置和使用方法可以参考 Ehcache 官方文档。

以上就是使用 Mybatis 二级缓存的配置方法。需要注意的是,二级缓存虽然可以提高查询性能,但也会增加系统的复杂度,因此在实际使用中需要根据具体情况进行选择和权衡。同时,在设计数据库表结构和应用程序架构时,应尽量避免出现循环依赖的情况,以确保系统的稳定性和可维护性。

;