springboot mybatis 二级缓存问题

1. 原理

二级缓存原理:
Mybatis的二级缓存是指mapper映射文件。二级缓存是多个sqlSession共享的,其作用域是mapper下的同一个namespace。
在不同的sqlSession中,相同的namespace下,相同的查询sql语句并且参数也相同的情况下,会命中二级缓存。如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。

2. 同一张表的 xml 和 接口 文件copy 到自己的目录下,有自己的增加和修改方法,这样分包很容易 造成脏读,获取到的数据不一样

下面举例说明:

先搭建一个简单的springboot + mybatis 项目,并开启mybatis 的二级缓存

#开启MyBatis的二级缓存
mybatis.configuration.cache-enabled=true

将订单模块和销售变更模块放到 copy相同的 订单ordermapper xml 文件到各自模块:

订单模块:

package com.example.demo.mapper;

import com.example.demo.entity.SOrder;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;



@Repository("orderSOrderMapper")
public interface OrderSOrderMapper {
    int deleteByPrimaryKey(Integer id);

    int insert(SOrder record);

    int insertSelective(SOrder record);

    int updateByExampleSelective(SOrder record);

    SOrder selectByPrimaryKey(@Param("id") Integer id);


    int updateByPrimaryKey(SOrder record);
}

 

<?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.example.demo.mapper.OrderSOrderMapper">
  <!-- 开启 这个 namespace  的 二级缓存 -->
  <cache/>


  <resultMap id="BaseResultMap" type="com.example.demo.entity.SOrder" >

    <id column="id" property="id" jdbcType="INTEGER" />
    <result column="name" property="name" jdbcType="VARCHAR" />
    <result column="description" property="description" jdbcType="VARCHAR" />
    <result column="created_GUID" property="createdGuid" jdbcType="VARCHAR" />
    <result column="updated_GUID" property="updatedGuid" jdbcType="VARCHAR" />
    <result column="created_Time" property="createdTime" jdbcType="TIMESTAMP" />
    <result column="updated_Time" property="updatedTime" jdbcType="TIMESTAMP" />
  </resultMap>

  <sql id="Base_Column_List" >
    id, name, description, created_GUID, updated_GUID, created_Time, updated_Time
  </sql>
  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
    select 
    *
    from s_order
    where id = #{id,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
    delete from s_order
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <insert id="insert" parameterType="com.example.demo.entity.SOrder" useGeneratedKeys="true" keyProperty="id" >

    insert into s_order (name, description, created_GUID, 
      updated_GUID, created_Time, updated_Time
      )
    values (#{name,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR}, #{createdGuid,jdbcType=VARCHAR}, 
      #{updatedGuid,jdbcType=VARCHAR}, #{createdTime,jdbcType=TIMESTAMP}, #{updatedTime,jdbcType=TIMESTAMP}
      )
  </insert>
  <insert id="insertSelective" parameterType="com.example.demo.entity.SOrder" useGeneratedKeys="true" keyProperty="id" >
    insert into s_order
    <trim prefix="(" suffix=")" suffixOverrides="," >
      <if test="name != null" >
        name,
      </if>
      <if test="description != null" >
        description,
      </if>
      <if test="createdGuid != null" >
        created_GUID,
      </if>
      <if test="updatedGuid != null" >
        updated_GUID,
      </if>
      <if test="createdTime != null" >
        created_Time,
      </if>
      <if test="updatedTime != null" >
        updated_Time,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides="," >
      <if test="name != null" >
        #{name,jdbcType=VARCHAR},
      </if>
      <if test="description != null" >
        #{description,jdbcType=VARCHAR},
      </if>
      <if test="createdGuid != null" >
        #{createdGuid,jdbcType=VARCHAR},
      </if>
      <if test="updatedGuid != null" >
        #{updatedGuid,jdbcType=VARCHAR},
      </if>
      <if test="createdTime != null" >
        #{createdTime,jdbcType=TIMESTAMP},
      </if>
      <if test="updatedTime != null" >
        #{updatedTime,jdbcType=TIMESTAMP},
      </if>
    </trim>
  </insert>
  <update id="updateByExampleSelective" >

    update s_order
    <set >
      <if test="id != null" >
        id = #{id,jdbcType=INTEGER},
      </if>
      <if test="name != null" >
        name = #{name,jdbcType=VARCHAR},
      </if>
      <if test="description != null" >
        description = #{description,jdbcType=VARCHAR},
      </if>
      <if test="createdGuid != null" >
        created_GUID = #{createdGuid,jdbcType=VARCHAR},
      </if>
      <if test="updatedGuid != null" >
        updated_GUID = #{updatedGuid,jdbcType=VARCHAR},
      </if>
      <if test="createdTime != null" >
        created_Time = #{createdTime,jdbcType=TIMESTAMP},
      </if>
      <if test="updatedTime != null" >
        updated_Time = #{updatedTime,jdbcType=TIMESTAMP},
      </if>
    </set>
    <if test="_parameter != null" >

    </if>
  </update>
  
  <update id="updateByPrimaryKey" parameterType="com.example.demo.entity.SOrder" >
    update s_order
    set name = #{name,jdbcType=VARCHAR},
      description = #{description,jdbcType=VARCHAR}
    where id = #{id,jdbcType=INTEGER}
  </update>
</mapper>

销售变更模块:

package com.example.demo.mapper;

import com.example.demo.entity.SOrder;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository("salechangeSOrderMapper")
public interface SaleChangeSOrderMapper {
    int deleteByPrimaryKey(Integer id);

    int insert(SOrder record);

    int insertSelective(SOrder record);

    int updateByExampleSelective(SOrder record);

    SOrder selectByPrimaryKey(@Param("id") Integer id);


    int updateByPrimaryKey(SOrder record);
}
<?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.example.demo.mapper.SaleChangeSOrderMapper" >
  <!-- 开启 这个 namespace  的 二级缓存 -->
  <cache/>

  <resultMap id="BaseResultMap" type="com.example.demo.entity.SOrder" >

    <id column="id" property="id" jdbcType="INTEGER" />
    <result column="name" property="name" jdbcType="VARCHAR" />
    <result column="description" property="description" jdbcType="VARCHAR" />
    <result column="created_GUID" property="createdGuid" jdbcType="VARCHAR" />
    <result column="updated_GUID" property="updatedGuid" jdbcType="VARCHAR" />
    <result column="created_Time" property="createdTime" jdbcType="TIMESTAMP" />
    <result column="updated_Time" property="updatedTime" jdbcType="TIMESTAMP" />
  </resultMap>

  <sql id="Base_Column_List" >
    id, name, description, created_GUID, updated_GUID, created_Time, updated_Time
  </sql>
  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
    select 
    <include refid="Base_Column_List" />
    from s_order
    where id = #{id,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
    delete from s_order
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <insert id="insert" parameterType="com.example.demo.entity.SOrder" useGeneratedKeys="true" keyProperty="id" >

    insert into s_order (name, description, created_GUID, 
      updated_GUID, created_Time, updated_Time
      )
    values (#{name,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR}, #{createdGuid,jdbcType=VARCHAR}, 
      #{updatedGuid,jdbcType=VARCHAR}, #{createdTime,jdbcType=TIMESTAMP}, #{updatedTime,jdbcType=TIMESTAMP}
      )
  </insert>
  <insert id="insertSelective" parameterType="com.example.demo.entity.SOrder" useGeneratedKeys="true" keyProperty="id" >
    insert into s_order
    <trim prefix="(" suffix=")" suffixOverrides="," >
      <if test="name != null" >
        name,
      </if>
      <if test="description != null" >
        description,
      </if>
      <if test="createdGuid != null" >
        created_GUID,
      </if>
      <if test="updatedGuid != null" >
        updated_GUID,
      </if>
      <if test="createdTime != null" >
        created_Time,
      </if>
      <if test="updatedTime != null" >
        updated_Time,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides="," >
      <if test="name != null" >
        #{name,jdbcType=VARCHAR},
      </if>
      <if test="description != null" >
        #{description,jdbcType=VARCHAR},
      </if>
      <if test="createdGuid != null" >
        #{createdGuid,jdbcType=VARCHAR},
      </if>
      <if test="updatedGuid != null" >
        #{updatedGuid,jdbcType=VARCHAR},
      </if>
      <if test="createdTime != null" >
        #{createdTime,jdbcType=TIMESTAMP},
      </if>
      <if test="updatedTime != null" >
        #{updatedTime,jdbcType=TIMESTAMP},
      </if>
    </trim>
  </insert>
  <update id="updateByExampleSelective" >

    update s_order
    <set >
      <if test="id != null" >
        id = #{id,jdbcType=INTEGER},
      </if>
      <if test="name != null" >
        name = #{name,jdbcType=VARCHAR},
      </if>
      <if test="description != null" >
        description = #{description,jdbcType=VARCHAR},
      </if>
      <if test="createdGuid != null" >
        created_GUID = #{createdGuid,jdbcType=VARCHAR},
      </if>
      <if test="updatedGuid != null" >
        updated_GUID = #{updatedGuid,jdbcType=VARCHAR},
      </if>
      <if test="createdTime != null" >
        created_Time = #{createdTime,jdbcType=TIMESTAMP},
      </if>
      <if test="updatedTime != null" >
        updated_Time = #{updatedTime,jdbcType=TIMESTAMP},
      </if>
    </set>
    <if test="_parameter != null" >

    </if>
  </update>
  
  <update id="updateByPrimaryKey" parameterType="com.example.demo.entity.SOrder" >
    update s_order
    set name = #{name,jdbcType=VARCHAR},
      description = #{description,jdbcType=VARCHAR}
    where id = #{id,jdbcType=INTEGER}
  </update>
</mapper>

目录结果如下:

测试controller:

package com.example.demo.controller;

import com.example.demo.entity.Region;
import com.example.demo.entity.SOrder;
import com.example.demo.service.order.OrderSOrderService;
import com.example.demo.service.salechange.OrderSaleChangeService;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import java.util.List;

/**
 * @program: springboot_01
 * @description:
 * @author: guoyiguang
 * @create: 2021-03-07 13:14
 **/

@Controller
@RequestMapping("/order")
public class SOrderController {



    @Resource
    OrderSOrderService orderSOrderService;
    @Resource
    OrderSaleChangeService orderSaleChangeService;


    @RequestMapping("/getOneFromOrderNameSpace")
    @ResponseBody
    public SOrder getOneFromOrderNameSpace(Integer id){
        return  orderSOrderService.selectByPrimaryKey(id);
    }

    @RequestMapping(value = "/updateOneToOrderNameSpace",  method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
    @ResponseBody
    public int updateOneToOrderNameSpace(@RequestBody SOrder sOrder){
        return  orderSOrderService.updateByPrimaryKey(sOrder);
    }


    @RequestMapping("/getOneFromSaleChangeNameSpace")
    @ResponseBody
    public SOrder getOneFromSaleChangeNameSpace(Integer id){
        return  orderSaleChangeService.selectByPrimaryKey(id);
    }



    @RequestMapping(value = "/updateOneToSaleChangeNameSpace",  method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
    @ResponseBody
    public int updateOneToSaleChangeNameSpace(@RequestBody SOrder sOrder){
        return  orderSaleChangeService.updateByPrimaryKey(sOrder);
    }



}

 

订单模块开始修改数据:

订单模块查看数据:

 

但是这个时候销售模块拿id 为16的数据:

说明销售模块没有感知到 订单模块数据修改,因为二级缓存是以 namespace 为粒度的(也是个map),这个namespace 涉及到的增加,删除修改,都会触发清掉本namespace先关的缓存,订单模块的namespace 清空掉自己缓存可以拿到最新数据,但是这个update的操作,销售模块无法感知到,所以他还是从自己的缓存里拿的数据,所以分包操作同一张表可能会产生脏读问题

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值