MyBatisPlus解决逻辑删除与唯一索引的兼容问题

需求背景

比如有张用户表,在插入或者更新数据的时候,我们需要 用户名称(username),不能重复。

我们首先考虑的是给该字段创建唯一索引

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-sql"><span style="color:#f92672">create</span> <span style="color:#f92672">unique</span> index uni_username <span style="color:#f92672">on</span> <span style="color:#f92672">user</span>(username)
</code></span></span>

似乎这样就可以了,然而事情并没有那么简单。

因为我们表中的数据在删除的时候不会真的的删除,而是采用逻辑删除,会有一个 deleted 字段使用0,1标识未删除与已删除。

当然我们可以考虑将 username + deleted 组合成一个联合唯一索引。

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-sql"><span style="color:#f92672">create</span> <span style="color:#f92672">unique</span> index uni_username_deleted <span style="color:#f92672">on</span> <span style="color:#f92672">user</span>(username,deleted)
</code></span></span>

这样就ok了吗?

其实会有一个新的问题,就是如果同一个用户名如果被删除一次。

再去删除会发现系统报错了,因为该条数据已经存在了,不能在删除了。

是不是很多时候因为逻辑删除与唯一索引的冲突,你就不创建唯一索引,想着自己写的代码自己有信心不会出现脏数据的。

这么想你就太天真啦,数据库是我们最后一道防线,这道防线都不要了嘛?

阿里巴巴手册有关索引规范,第一条就是

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-undefined">【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
</code></span></span>

手册还有这么一句话:

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-undefined">即使在应用层做了非常完善的校验和控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
</code></span></span>

所以唯一索引非常有必要!!!

那该怎么做能让逻辑删除与唯一索引兼容?

现在大家比较通用的办法就是

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-bash">我们依旧可以将 username + deleted 组合成一个联合唯一索引,但是删除的时候deleted不再是固定的1,而是当前的主键ID,也就是deleted不等于0都是删除状态,如果删除了那deleted值=<span style="color:#e6db74">id</span>值
</code></span></span>

既然确立了解决方案,那就该思考怎么做?

二、MyBatisPlus逻辑删除

MyBatisPlus是支持逻辑删除的,如果确定在哪个字段是逻辑删除字段,那就在该字段上添加一个注解

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-java">  <span style="color:#75715e">/**
     * 1、删除 0、未删除
     */</span>
    <span style="color:#75715e">@TableLogic(value = "0", delval = "1")</span>
    <span style="color:#f92672">private</span> Integer deleted;
</code></span></span>

这个一来操作数据是会自动变成如下:

  • 查询时: 查询条件会自动加上 'AND deleted = 0'
  • 删除时: 自定添加 'UPDATE SET deleted = 1 … WHERE … AND deleted = 0'

如果你想删除的时候不再是固定1而是id值,那么就可以这样改

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-java">    <span style="color:#75715e">@TableLogic(value = "0", delval = "id")</span>
    <span style="color:#f92672">private</span> Integer deleted;
</code></span></span>

如果想改成全局的那么在配置文件中添加

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-yaml language-yml"><span style="color:#f92672">mybatis-plus:</span>
  <span style="color:#f92672">global-config:</span>
    <span style="color:#f92672">db-config:</span>
      <span style="color:#f92672">logic-delete-value:</span> <span style="color:#ae81ff">1</span> <span style="color:#75715e"># 逻辑已删除值(默认为 1)</span>
      <span style="color:#f92672">logic-not-delete-value:</span> <span style="color:#ae81ff">0</span> <span style="color:#75715e"># 逻辑未删除值(默认为 0)</span>
</code></span></span>

三、测试

1、用户表
<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-sql"><span style="color:#f92672">CREATE</span> <span style="color:#f92672">TABLE</span> `<span style="color:#f92672">user</span>` (
  `id` <span style="color:#e6db74">int</span> unsigned  AUTO_INCREMENT COMMENT <span style="color:#e6db74">'主键'</span>,
  `username` <span style="color:#e6db74">varchar</span>(<span style="color:#ae81ff">128</span>)  COMMENT <span style="color:#e6db74">'用户名'</span>,
  `phone` <span style="color:#e6db74">varchar</span>(<span style="color:#ae81ff">32</span>)  COMMENT <span style="color:#e6db74">'手机号'</span>,
  `sex` <span style="color:#e6db74">char</span>(<span style="color:#ae81ff">1</span>)  COMMENT <span style="color:#e6db74">'性别'</span>,
  `create_time` datetime  COMMENT <span style="color:#e6db74">'创建时间'</span>,
  `update_time` datetime  COMMENT <span style="color:#e6db74">'更新时间'</span>,
  `deleted` tinyint <span style="color:#f92672">DEFAULT</span> <span style="color:#e6db74">'0'</span> COMMENT <span style="color:#e6db74">'1、删除 0、未删除'</span>,
  <span style="color:#f92672">PRIMARY</span> KEY (`id`)
) ENGINE<span style="color:#ab5656">=</span>InnoDB AUTO_INCREMENT<span style="color:#ab5656">=</span><span style="color:#ae81ff">1</span> 
</code></span></span>
2、创建对应实体
<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#75715e">@Data</span>
<span style="color:#75715e">@Accessors(chain = true)</span>
<span style="color:#75715e">@TableName("user")</span>
<span style="color:#f92672">public</span> <span style="color:#f92672">class</span> <span style="color:#a6e22e">UserDO</span> <span style="color:#f92672">implements</span> <span style="color:#a6e22e">Serializable</span> {

    <span style="color:#f92672">private</span> <span style="color:#f92672">static</span> <span style="color:#f92672">final</span> <span style="color:#e6db74">long</span> <span style="color:#e6db74">serialVersionUID</span> <span style="color:#ab5656">=</span> <span style="color:#ae81ff">1L</span>;

    <span style="color:#75715e">@TableId(type = IdType.AUTO)</span>
    <span style="color:#f92672">private</span> Integer id;
    <span style="color:#75715e">/**
     * 用户名
     */</span>
    <span style="color:#f92672">private</span> String username;
    <span style="color:#75715e">/**
     * 手机号
     */</span>
    <span style="color:#f92672">private</span> String phone;
    <span style="color:#75715e">/**
     * 性别
     */</span>
    <span style="color:#f92672">private</span> String sex;
    <span style="color:#75715e">/**
     * 创建时间
     */</span>
    <span style="color:#f92672">private</span> LocalDateTime createTime;
    <span style="color:#75715e">/**
     * 更新时间
     */</span>
    <span style="color:#f92672">private</span> LocalDateTime updateTime;

    <span style="color:#75715e">/**
     * 1、删除 0、未删除
     */</span>
    <span style="color:#f92672">private</span> Integer deleted;
}
</code></span></span>
3、物理删除测试

注意: 目前 deleted 字段是没有添加 @TableLogic注解,同是在全局也没有定义逻辑删除

我们来看下删除示例

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-java">    <span style="color:#75715e">@Test</span>
    <span style="color:#f92672">public</span> <span style="color:#f92672">void</span> <span style="color:#a6e22e">deleteById</span><span style="color:#f8f8f2">()</span> {
        <span style="color:#75715e">//方式一:根据id删除</span>
        mapper.deleteById(<span style="color:#ae81ff">10</span>);
        <span style="color:#75715e">//方式二:根据指定字段删除</span>
        LambdaQueryWrapper<UserDO> wrapper = <span style="color:#f92672">new</span> <span style="color:#a6e22e">LambdaQueryWrapper</span><>();
        wrapper.eq(UserDO::getSex, <span style="color:#e6db74">"男"</span>);
        mapper.delete(wrapper);
        <span style="color:#75715e">//方式三:手动逻辑删除</span>
        <span style="color:#e6db74">UserDO</span> <span style="color:#e6db74">userDO</span> <span style="color:#ab5656">=</span> <span style="color:#f92672">new</span> <span style="color:#a6e22e">UserDO</span>();
        userDO.setId(<span style="color:#ae81ff">10</span>);
        userDO.setDeleted(<span style="color:#ae81ff">1</span>);
        mapper.updateById(userDO);
    }
</code></span></span>

执行结果

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-sql"><span style="color:#75715e">--方式1</span>
<span style="color:#f92672">DELETE</span> <span style="color:#f92672">FROM</span> <span style="color:#f92672">user</span> <span style="color:#f92672">WHERE</span> id<span style="color:#ab5656">=</span><span style="color:#ae81ff">10</span>
<span style="color:#75715e">--方式2</span>
<span style="color:#f92672">DELETE</span> <span style="color:#f92672">FROM</span> <span style="color:#f92672">user</span> <span style="color:#f92672">WHERE</span> (sex <span style="color:#ab5656">=</span> <span style="color:#e6db74">'男'</span>)
<span style="color:#75715e">--方式3</span>
<span style="color:#f92672">UPDATE</span> <span style="color:#f92672">user</span> <span style="color:#f92672">SET</span> deleted<span style="color:#ab5656">=</span><span style="color:#ae81ff">1</span> <span style="color:#f92672">WHERE</span> id<span style="color:#ab5656">=</span><span style="color:#ae81ff">10</span>
</code></span></span>

我们通过结果可以看出,如果不添加逻辑删除标识 那删除就是物理删除。

4、逻辑删除测试

我们在deleted属性字段 添加 逻辑删除标识

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-java"> <span style="color:#75715e">@TableLogic(value = "0", delval = "id")</span>
 <span style="color:#f92672">private</span> Integer deleted;
</code></span></span>

我们再来执行上面三个删除,看下执行结果

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-sql"><span style="color:#75715e">--方式1</span>
<span style="color:#f92672">UPDATE</span> <span style="color:#f92672">user</span> <span style="color:#f92672">SET</span> deleted<span style="color:#ab5656">=</span>id <span style="color:#f92672">WHERE</span> id<span style="color:#ab5656">=</span><span style="color:#ae81ff">10</span> <span style="color:#f92672">AND</span> deleted<span style="color:#ab5656">=</span><span style="color:#ae81ff">0</span>
<span style="color:#75715e">--方式2</span>
<span style="color:#f92672">UPDATE</span> <span style="color:#f92672">user</span> <span style="color:#f92672">SET</span> deleted<span style="color:#ab5656">=</span>id <span style="color:#f92672">WHERE</span> deleted<span style="color:#ab5656">=</span><span style="color:#ae81ff">0</span> <span style="color:#f92672">AND</span> (sex <span style="color:#ab5656">=</span> <span style="color:#e6db74">'男'</span>)
<span style="color:#75715e">--方式3</span>
报错了
</code></span></span>

从执行结果来看,方式一和方式二都从之前的物理删除变成了逻辑删除。

但为什么方式三会报错呢?我们来看下报错的结果

 

发现问题了,最终执行的SQL竟然是:

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-sql"><span style="color:#f92672">UPDATE</span> <span style="color:#f92672">user</span>  <span style="color:#f92672">WHERE</span> id<span style="color:#ab5656">=</span>?  <span style="color:#f92672">AND</span> deleted<span style="color:#ab5656">=</span><span style="color:#ae81ff">0</span>
</code></span></span>

为什么是这样,正常不应该是

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-sql"><span style="color:#f92672">UPDATE</span> <span style="color:#f92672">user</span> <span style="color:#f92672">SET</span> deleted<span style="color:#ab5656">=</span><span style="color:#ae81ff">1</span>  <span style="color:#f92672">WHERE</span> id<span style="color:#ab5656">=</span>?  <span style="color:#f92672">AND</span> deleted<span style="color:#ab5656">=</span><span style="color:#ae81ff">0</span>
</code></span></span>

这个就需要去看Mybatisplus到底做了什么操作,改变了我们的SQL

 

真相大白了

<span style="color:#4b4b4b"><span style="background-color:#ffffff"><code class="language-sql">Mybatisplus在updateById更新时,如果已经加了逻辑删除标记,那做<span style="color:#f92672">SQL</span>拼接的时候,会自动过滤掉逻辑删除的<span style="color:#f92672">Set</span>拼接
</code></span></span>

所以在实际开发中就非常注意,如果你的项目一开始是没有加Mybatisplus逻辑删除标识的,后面你在加逻辑删除标识时,不是说加了就好了。

你还需要考虑对整体项目有没有影响,如果之前是用updateById做逻辑删除,那就会导致之前的删除失败甚至是报错,这一点一定要注意。

本人有踩过坑!  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

supeerzdj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值