原因

SpringBoot 项目中,我们通过对应的 JavaBean 来接收前端传来的 Json 数据,然后执行我们自己的业务逻辑,特殊一点的比如更新接口,我们一般是判断 JavaBean 对象的属性来决定对应的更新SQL,得益于各种框架,我们不用再手动做这种麻烦的事情。 我司项目在下一版本中提出了一个要求:当用户只修改了某一个字段,那么执行不同的逻辑,否则执行更新逻辑。看到这里,我想各位都想的是反射,毕竟一个个的判断属性太low了。 在这里先不论如何实现这个,我突然发现一个问题,如果前端调用更新接口,传过来一个空的json对象,那么会怎么样?

复现

我司使用的 MyBatis(Plus) 来处理数据库相关的操作,因为是更新接口,为了以后方便记录用户的更新日志,所以前端都是用户修改了什么就只传什么过来,自然没办法在参数上添加校验器,由此引申出问题:如果用户传的是 null 或者 {} ,那么会发生什么。 整个service中都没有处理这种情况,最后进入到了 MyBatis 部分,然后执行失败,生成的SQL语句变为了:

update xxx  where id = ?

思考

原因很简单,更新的 Entity 中,因为之后 id 不为空(更新接口id是放在url中的),其他的都为 null ,所以生成了这样子的SQL,这自然是不对的,所以我们需要在 Service 或者 Controller 中进行处理。

So. How to detect this situation?

思路1

反射应该是我们第一个想到的,毕竟一个个的 if 判断太low了。 于是,有了如下代码:

     /**
     * 检测对象的属性是否全部为空
     *
     * @param object 对象
     * @return true: 属性全部为空 false: 属性有的不为空
     */
    private <T> boolean isAllFieldsNull(@Nullable T object) throws IllegalAccessException {
        if (object == null) {
            return true;
        }
        for (Field declaredField : object.getClass().getDeclaredFields()) {
            declaredField.setAccessible(true);
            if (declaredField.get(object) != null) {
                return false;
            }
        }
        return true;
    }

简单,明了。 但是,是存在问题的!!! 如果 object 中存在默认值呢?那么就校验通过了(当然,某些情况下这个也不属于错误,具体看各位的需求)。 还有就是 serialVersionUID 或者说只考虑了引用类型,基本类型没考虑。

思路2

既然想到了上面的问题,所以就自然有对应的解决方法。

得益于 LarryZeal文章,我完美的跳过了这个步骤,所以在这里,我就只贴上 LarryZeal 的代码。

/**
     * 目的,判断是否有赋值。
     * 不能传入null对象!!!
     * 
     * @param obj
     * @return
     * @throws Exception
     */
    public <T> boolean isAllFieldStill(T obj) throws Exception {
        Class cls = obj.getClass();
        T t2 = (T) cls.getConstructor().newInstance();// 创建一个新的对象,用于对比数据
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true); // 访问所有字段
            // if (field.getName().equals("serialVersionUID")) {// 无视掉序列化ID
            // continue;
            // }
            if (field.get(obj) != null) { // 卧槽,基本类型做不到啊
                if (field.getType().getName().equals("byte") || field.getType().getName().equals("short")
                        || field.getType().getName().equals("int") || field.getType().getName().equals("long")) {
                    // 判断
                    if ((int) field.get(obj) != (int) field.get(t2)) {
                        return false;
                    }
                } else if (field.getType().getName().equals("boolean")) {
                    // 判断
                    if ((boolean) field.get(obj) != (boolean) field.get(t2)) {
                        return false;
                    }
                } else if (field.getType().getName().equals("float")) {
                    // 判断
                    if ((float) field.get(obj) != (float) field.get(t2)) {
                        return false;
                    }
                } else if (field.getType().getName().equals("double")) {
                    // 判断
                    if ((double) field.get(obj) != (double) field.get(t2)) {
                        return false;
                    }
                } else {
                    // 都是引用类型了(引用类型需要有equals方法)
                    // 判断....
                    if (!field.get(obj).equals(field.get(t2))) {
                        return false;
                    }
                }
                return false;
            }

        }

        return true;
    }

思路3

直接使用新的对象然后使用 equals<