mybatisplus 解决唯一索引和逻辑删除冲突

1.应用场景:

        当我们注册用户时,一般username存储到数据库要求只能存在一条,这时候可以使用唯一索引来约束新增多个相同名称用户只能存在一个username。如今,各个公司存储数据做删除时,一般是逻辑删除,即使用一个字段做标记是否被删除,而mybatisPlus也提供了逻辑删除的配置,比如全局配置逻辑删除,一般在配置文件做:

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
    call-setters-on-nulls: true
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

还有提供注解@TableLogic,在逻辑删除的字段上添加此注解即可。但是,在唯一索引的基础上新增了逻辑删除会出现冲突,使得删除失败。那要如何解决唯一索引和逻辑删除冲突呢?

2.解决方案:

        网上有几种处理方式,但是觉得不大友好,不能一劳永逸。比如不做唯一索引,新增redis缓存做控制,这种方式无疑增加了redis的缓存,耦合度大。所以,我想到的是直接在mybatisPlus的基础上做改造,主要是自定义一个处理逻辑删除的方法,逻辑删除的标记是'0'未删除,已删除的删除标记填值为被删除的id。

2.1. 自定义CustomMapper继承于BaseMapper:

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import java.io.Serializable;

public interface CustomMapper<T> extends BaseMapper<T> {

    int omitById(Serializable id);
}

2.2.自定义CustomService继承于IService:

import com.baomidou.mybatisplus.extension.service.IService;

import java.io.Serializable;

public interface CustomService<T> extends IService<T> {

    boolean omitById(Serializable id);
}

2.3.自定义CustomServiceImpl继承于ServiceImpl:

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import java.io.Serializable;


public class CustomServiceImpl<M extends CustomMapper<T>, T> extends ServiceImpl<CustomMapper<T>, T> implements CustomService<T> {

    @Override
    public boolean omitById(Serializable id) {
        return baseMapper.omitById(id) > 0;
    }
}

2.3.自定义逻辑删除注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface LogicDelete {
}

2.4.自定义逻辑删除方法:基于MybatisPlus的DeleteById方法上做修改,主要是拼接语句,大致如下:

UPDATE #{table_name} SET #{logic_delete} = #{id} WHERE id = #{id} AND #{logic_delete} = '0'
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.wf.mybatisplus.annotations.LogicDelete;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;

import java.util.Objects;


@Slf4j
public class OmitById extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sql;
        SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE_BY_ID;
        String methodName = sqlMethod.getMethod();
        if (tableInfo.isWithLogicDelete()) {
            sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), sqlLogicSet(tableInfo),
                    tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),
                    tableInfo.getLogicDeleteSql(true, true));
            for (TableFieldInfo fieldInfo : tableInfo.getFieldList()) {
                LogicDelete logicDelete = fieldInfo.getField().getAnnotation(LogicDelete.class);
                if (Objects.nonNull(logicDelete)) {
                    sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(),
                            "SET " + fieldInfo.getColumn() + " = #{" + tableInfo.getKeyProperty() +"}",
                            tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),
                            "AND "+fieldInfo.getColumn() + " = 0");
                    methodName = "omitById";
                    break;
                }
            }
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Object.class);
            return addUpdateMappedStatement(mapperClass, modelClass, methodName, sqlSource);
        } else {
            sqlMethod = SqlMethod.DELETE_BY_ID;
            sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), tableInfo.getKeyColumn(),
                    tableInfo.getKeyProperty());
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Object.class);
            return this.addDeleteMappedStatement(mapperClass, getMethod(sqlMethod), sqlSource);
        }
    }
}

2.5.自定义sql注入器:

package com.wf.mybatisplus.base;

import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;

import java.util.List;

public class CustomSqlInjector extends DefaultSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        methodList.add(new OmitById());
        return methodList;
    }
}

2.6.逻辑删除自动填充:

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;

import java.util.Date;
import java.util.Map;

public class SystemMetaObjectHandler implements MetaObjectHandler {

    @Override
    //mybatis-plus版本2.0.9+
    public void insertFill(MetaObject metaObject) {
        Map<String, Object> map = BeanUtil.beanToMap(metaObject.getOriginalObject());
        if (map.containsKey("deleted")) {
            Object deleted = getFieldValByName("deleted", metaObject);
            if(deleted == null){
                setFieldValByName("deleted", false, metaObject);
            }
        }
        if (map.containsKey("logicDelete")) {
            Object logicDelete = getFieldValByName("logicDelete", metaObject);
            if(logicDelete == null){
                setFieldValByName("logicDelete", "0", metaObject);
            }
        }
        Object createBy = getFieldValByName("createBy", metaObject);
        Object createTime = getFieldValByName("createTime", metaObject);
        Object updateBy = getFieldValByName("updateBy", metaObject);
        Object updateTime = getFieldValByName("updateTime", metaObject);

        if (createBy == null) {
            setFieldValByName("createBy", "0", metaObject);
        }
        if (createTime == null) {
            setFieldValByName("createTime", new Date(), metaObject);
        }
        if (updateBy == null) {
            setFieldValByName("updateBy", "0", metaObject);
        }
        if (updateTime == null) {
            setFieldValByName("updateTime", new Date(), metaObject);
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        Object updateBy = getFieldValByName("updateBy", metaObject);
        Object updateTime = getFieldValByName("updateTime", metaObject);
        if (updateBy == null) {
            setFieldValByName("updateBy", "0", metaObject);
        }
        if (updateTime == null) {
            setFieldValByName("updateTime", new Date(), metaObject);
        }
    }
}

自此,改造完毕。

3.使用方式:

继承自己自定义的mapper和service:


@Mapper
public interface AuthUserMapper extends CustomMapper<AuthUser> {
}
public interface AuthUserService extends CustomService<AuthUser> {
}
@Service
public class AuthUserServiceImpl extends CustomServiceImpl<AuthUserMapper, AuthUser> implements AuthUserService {
}

实体类:在字段上标记即可

    @TableLogic
    @LogicDelete
    @TableField(updateStrategy = FieldStrategy.NOT_NULL, fill = FieldFill.INSERT)
    protected String logicDelete;

亲测可用。

猜你喜欢

转载自blog.csdn.net/Json_Marz/article/details/132364564
今日推荐