需求背景
比如有张用户表,在插入或者更新数据的时候,我们需要 用户名称
(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做逻辑删除,那就会导致之前的删除失败甚至是报错,这一点一定要注意。
本人有踩过坑!