Mybatis一级缓存的锅

时间:2024-02-23 16:01:41

问题背景

项目开发中有一个树形数据结构,不像经典组织结构树、菜单级别树,我们这个树形结构是用户后期手动建立起来的关系。因此数据库表结构为两张表:数据记录表、记录关系表,通过业务规则限制,形成的树形结构像下面这样:

特殊之处就是树结构节点是有重复的

  • 不复制重复节点
    5NVrIU.png

  • 复制重复节点

5NVDaT.png

项目要求前端展示、导出时使用复制重复节点的方式。开搞吧

Mybatis树结构查询

树结构查询,在mysql下当然是使用Mybatis框架提供的递归查询了。

  1. xml配置文件
<resultMap type="(...).OKRAlignTreeNode" id="TreeNodeResult">
    <result property="id"    column="objective_id"    />
    <result property="content"    column="content"    />
    <result property="theOrder"    column="the_order"    />
    <collection property="children" select="getChildren" column="objective_id" ofType="(...).OKRAlignTreeNode"/>
</resultMap>

<select id="getTree" parameterType="Map"  resultMap="TreeNodeResult">
    select
        objective_id,content,the_order
    from okr_objective oo
    where oo.objective_id = #{id}
    order by the_order
</select>
<select id="getChildren" resultMap="TreeNodeResult">
        select objective_id,content,the_order
        from
        (select objective_id from okr_aline where parent_ids = #{objective_id} ) a
        left join okr_objective oo on a.objective_id = oo.objective_id
        order by b.the_order
</select>
  1. mapper文件
public interface OKRAlignExportMapper {
    TreeNode getTree(Long objectiveId);
}
  1. 查询结果

5NVyiF.png

树结构导出到Excel

关于树形结构数据导出,我参考这篇博客,并针对OKR的特点做了修改。

Java 树形结构数据生成导出excel文件

OKR对齐视图数据结构的特点是:

  • 1.以本人的目标为中心,向左右两侧发散。
  • 2.左侧是自己对齐的目标,以及对齐目标再次对齐的目标,递归到顶。
  • 3.右侧是向自己对齐的目标,递归到底。

关于OKR对齐视图这种数据结构的导出,我们下篇博客会把完整的代码放上来,并分析一下。这里说一下导出这种树形结构数据的主要步骤:

  • 1.计算每条数据的行列坐标,这里采用递归的算法,最终可以计算出父级节点需要合并的行数,以及Excel文件的最大列数。
  • 2.根据行列坐标递归输出每条数据的值到Excel单元格。

Mybatis一级缓存导致的问题

首先我们来了解一下Mybatis一级缓存:

Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。

mybatis一级缓存二级缓存

由于Mybatis的缓存机制,导致在出现重复的叶子节点时,虽然树结构正常构建,但是指向的是同一个java对象。因为是使用的Mybatis的递归查询,因此确认整个查询在一个SqlSession中执行完成,肯定是一级缓存导致的。这样会造成的后果,就是无法设置重复叶子节点的正确位置,因为指向同一个java对象,后遍历到的节点设置会覆盖前面的节点设置。

解决方案

既然确定是一级缓存导致的,那关闭或者清除一级缓存就行了吧。因为是框架的递归查询,因此无法
调用SqlSession的修改、添加、删除、commit(),close等清空一级缓存。那怎么办呢,笨办法了:

既然树的结构关系时正确的,只是重复节点指向了同一个java对象,那就遍历重建对象吧

/**
 * 深度拷贝树结构
 * @param node
 * @return
 */
private static OKRAlignTreeNode deepCopyTree(OKRAlignTreeNode node){
    OKRAlignTreeNode newNode = node.clone();
    List<OKRAlignTreeNode> children = node.getChildren();
    if(children!=null&&children.size()>0){
        List<OKRAlignTreeNode> newChildren = new LinkedList<>();
        for (OKRAlignTreeNode child:children){
            if(child!=null){
                OKRAlignTreeNode newChild = deepCopyTree(child);
                newChildren.add(newChild);
            }
        }
        newNode.setChildren(newChildren);
    }
    return newNode;
}