需求

经常碰到这样的需求,在某个表里添加一条数据,但是名称不能重复,编码不能重复。其实做法无非就是去数据库里查询是否有重复数据。

V1.0

最原始的写法就是每个表都写自己的校验。类似
image.png

每个表都有一份差不多的代码,可能字段不一样。那么这个时候就可以想着怎么去把方法抽象成公共方法了。因为我们是根据字段名去判断,那么就把字段名,和要判断的值拿出来当做参数。

V2.0

所以我基于mybatis-plus 写了一个通用的方法。
给ServiceImpl再包装一层:

  1. public class StandardBaseServiceImpl<M extends BaseMapper<T>, T extends BasicEntity> extends ServiceImpl<BaseMapper<T>, T> implements IBasicService<T> {
  2. /**
  3. * 全表校验字段是否重复
  4. * @param id 数据id
  5. * @param column 字段
  6. * @param target 判断的目标值
  7. * @throws
  8. * @return boolean true 校验通过;false 未通过
  9. * @author zhy
  10. * @date 2021/1/27 10:36
  11. */
  12. protected boolean validateFieldDuplicate(@Nullable String id, String column, Object target) {
  13. //根据字段获取数据库列表
  14. QueryWrapper<T> wrapper = new QueryWrapper<>();
  15. return validateFieldDuplicate(id,wrapper,column,target);
  16. }
  17. /**
  18. * 有条件校验字段是否重复
  19. * @param id 数据id
  20. * @param wrapper 具体条件
  21. * @param column 字段名
  22. * @param target 目标值
  23. * @throws
  24. * @return boolean true 校验通过;false 未通过
  25. * @author zhy
  26. * @date 2021/1/28 11:32
  27. */
  28. protected boolean validateFieldDuplicate(String id,QueryWrapper<T> wrapper,String column,Object target){
  29. wrapper.eq(column,target);
  30. List<T> list = baseMapper.selectList(wrapper);
  31. //列表为空证明不重复,返回true
  32. if (CollectionUtils.isEmpty(list)){
  33. return true;
  34. }
  35. //id为空则为新增,如果有列表则代表重复
  36. if (StringUtils.isBlank(id) && !CollectionUtils.isEmpty(list)){
  37. return false;
  38. }
  39. //修改数据逻辑
  40. for (T t : list) {
  41. //id一样的,证明未修改属性,忽略
  42. if (t.getId().equals(id)){
  43. continue;
  44. }
  45. //属性重复
  46. return false;
  47. }
  48. return true;
  49. }
  50. }

BasicEntity 是我们表对象一定要继承的基类

@Data
public class BasicEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(
        type = IdType.ID_WORKER_STR
    )
    protected String id;
    protected Integer sort;
    @TableField(
        fill = FieldFill.INSERT
    )
    protected String createBy;
    @TableField(
        fill = FieldFill.INSERT_UPDATE
    )
    protected String updateBy;
    @TableField(
        fill = FieldFill.INSERT
    )
    protected Date createTime;
    @TableField(
        fill = FieldFill.INSERT_UPDATE
    )
    protected Date updateTime;
}

然后具体业务service写法可以是这样

@Service
public class StandardAssetsBrandModelServiceImpl extends StandardBaseServiceImpl<StandardAssetsBrandModelMapper, StandardAssetsBrandModel> implements StandardAssetsBrandModelService {

    @Override
    public boolean save(StandardAssetsBrandModel entity) {
        if (!validateFieldDuplicate(null,"model",entity.getModel())){
            throw new CecClientException(CecClientCodeMessage.error("型号已存在"));
        }
        return super.save(entity);
    }

    @Override
    public boolean updateById(StandardAssetsBrandModel entity) {
        if (!validateFieldDuplicate(entity.getId(),"model",entity.getModel())){
            throw new CecClientException(CecClientCodeMessage.error("型号已存在"));
        }
        return super.updateById(entity);
    }
}

其实这种判重,代码会一直重复写,每次写的都差不多,当你看到这种每次代码写的差不多,就证明这些代码可以抽象出来,封装成通用的,这样使得代码美观,易维护。

但是,这样的写法会有个问题代码里会出现魔数值,也就是未经定义的变量 “model”。虽然model对应的是字段名。但是我们无法保证它不被修改。一旦哪一天被修改了。编译器是不会报错的,但是代码运行却会出现异常,因为找不到这个字段。所以我为了避免魔数值的出现,就写了一个通过lambda表达式获取字段名的工具类。

V3.0

先参考
语雀内容
然后改造一下方法

/**
 * @author zhy
 * @date 2021/1/2710:28
 */
@Slf4j
public class StandardBaseServiceImpl<M extends BaseMapper<T>, T extends BasicEntity> extends BasicServiceImpl<BaseMapper<T>, T> implements IBasicService<T> {


    /**
      * 全表校验字段是否重复
      * @param id 数据id
      * @param func 字段属性的get方法lambda表达式
      * @param target 目标值
      * @throws
      * @return boolean
      * @author zhy
      * @date 2021/1/28 15:42
      */
    protected boolean validateFieldDuplicate(@Nullable String id, Func<T,Object> func, Object target) {
        String column = LambdaUtil.getSqlColumn(func);
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        return validateFieldDuplicate(id,wrapper,column,target);
    }

    /**
      * 全表校验字段是否重复
      * @param id 数据id
      * @param column 字段
      * @param target 判断的目标值
      * @throws
      * @return boolean true 校验通过;false 未通过
      * @author zhy
      * @date 2021/1/27 10:36
      */
    protected boolean validateFieldDuplicate(@Nullable String id, String column, Object target) {
        //根据字段获取数据库列表
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        return validateFieldDuplicate(id,wrapper,column,target);
    }

    /**
      * 有条件校验字段是否重复
      * @param id 数据id
      * @param wrapper 具体条件
      * @param column 字段名
      * @param target 目标值
      * @throws
      * @return boolean true 校验通过;false 未通过
      * @author zhy
      * @date 2021/1/28 11:32
      */
    protected boolean validateFieldDuplicate(String id,QueryWrapper<T> wrapper,String column,Object target){
        wrapper.eq(column,target);
        List<T> list = baseMapper.selectList(wrapper);
        //列表为空证明不重复,返回true
        if (CollectionUtils.isEmpty(list)){
            return true;
        }
        //id为空则为新增,如果有列表则代表重复
        if (StringUtils.isBlank(id) && !CollectionUtils.isEmpty(list)){
            return false;
        }
        //修改数据逻辑
        for (T t : list) {
            //id一样的,证明未修改属性,忽略
            if (t.getId().equals(id)){
                continue;
            }
            //属性重复
            return false;
        }
        return true;
    }

}

用法改为:

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateById(StandardAssetsBrandModel entity) {
        if (!validateFieldDuplicate(entity.getId(),StandardAssetsBrandModel::getModel,entity.getModel())){
            throw new CecClientException(CecClientCodeMessage.error("型号已存在"));
        }
        return super.updateById(entity);
    }