利用反射机制比较两个实体类是否 “相等”

EntityUtil

该工具类为作者突发奇想写的,难免有不足之处(bug),欢迎指正;
目前只有一个 entity() 方法,后续可能会继续添加新的方法。


entityEq

说明

这个方法用于两个实体类对象是否 “逻辑相等”。

满足下列两个条件之一,就认为两个实体类对象逻辑相等”:

  1. 被 Objects.equals() 判断相等。
  2. 同时满足下列条件:
    • 两个对象存在共同的属性;
    • 两个对象共同属性的值对应相等(忽略 null)。

使用场景

判断两个实体类对象是否 “逻辑相等” 做什么呢?

假设数据库中有一张 user 表,user 表对应的实体类是 User,有如下属性(字段):

public class User {
    /**
     * id
     */
    private Long id;

    /**
     * 账号
     */
    private String userAccount;

    /**
     * 昵称
     */
    private String username;

    /**
     * 密码
     */
    private String userPassword;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新时间
     */
    private Date updateTime;

    /**
     * 逻辑删除
     */
    @TableLogic
    private Integer isDelete;
}

现在前端需要更新当前用户的信息,但是 User 表中有些属性是前端不关心的,比如:账号、创建时间、更新时间、逻辑删除。于是需要定义一个 UserRequest 类来接收前端更新请求传递的参数:

public class UserRequest {
    /**
     * id
     */
    private Long id;

    /**
     * 昵称
     */
    private String username;

    /**
     * 密码
     */
    private String userPassword;
}

当前端更新请求传递到后端,后端需要的数据进行校验,判断新数据是否和旧数据内容一致,如果一致,直接告诉前端更新成功,避免不必要的数据库操作。

新数据从请求参数取,取到的是 UserRequest 对象;旧数据获取要么从登录态中取,要么从数据库中取,取到的都是 User 对象。两个不同类型的数据显然不能使用 equals() 比较,只能通过手动、一个一个属性比较,灵活性为 0。

那为什么不将 User 对象通过其他工具类的 BeanUtil 转为 UserRequest 对象,然后调用 equals() 比较呢?

因为更新请求中可能只更新一个 username 属性,这样除了 idusername 属性的值不为 null 外,其他属性的值都是 null。但是原来的数据除了 idusername 以外的属性值未必是 null,这样哪怕更新的 username 和原来一样,通过 BeanUtilUser 对象转成 UserRequest 对象后,比较的结果仍然是 false,还是会进行不必要的数据库更新操作。

这里提到的 BeanUtil 工具类,Spring 框架有提供,将 User 转成 UserRequest 的用法:BeanUtils.copyProperties(user, userRequest);


因此,在执行数据库更新操作时,一个能够灵活比较不同类型的实体类对象之间是否逻辑相等的工具是有必要存在。

实现

思路

为了就有通用性,这里使用了反射机制,假设旧数据对应 oldObject,新数据对应 newObject,思路如下:

  1. 避免不必要的反射,先使用 Objects.equals() 比较 oldObjectnewObject 是否相等;
  2. 如果 Objects.equals() 判断结果为 false,再判断 oldObjectnewObject 中是否有 null,如果有,直接返回 fasle;否则继续下面的步骤;
  3. 通过反射机制分别获取 oldObjectnewObject 的所有属性;
  4. 判断 newObject 的当前属性是否存在与 oldObject,如果存在,比较二者对应属性值是否相等:!Objects.equals(newValue, oldValue) && newValue != null,如果结果为 true,直接返回 false

代码

/**
 * 实体类工具类
 *
 * @author 孤诣
 */
public class EntityUtil {

    /**
     * <p>
     * 传入两个对象, 比较它们在逻辑上是否相等.<br>
     * 对于传入的两个对象, 即使它们引用类型可以不同, 仍然有可能被判定为逻辑相等.
     * </p>
     * <p>
     * 在这里, 传入的两个对象在逻辑上相等需要满足的条件: <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
     * 1. 被 Objects.equals() 判断相等. <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
     * 2. 或者同时满足下列条件: <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
     * 1) 两个对象存在共同的属性. <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
     * 2) 两个对象共同属性的值对应相等.
     * </p>
     * <p>
     * 在对数据库进行更新时, 可以用于判断 entity 和 entityVo 之间是否逻辑相等, 避免一些无意义的数据库更新操作.
     * </p>
     *
     * @param newObject - 新对象
     * @param oldObject - 原对象
     * @return Objects.equals() 判断 newObject 和 oldObject 相等或者它们之间存在共同属性且共同属性的值对应相等时, 返回 true.
     */
    public static boolean entityEq(Object newObject, Object oldObject) {
        // 两个对象实际是否相等
        if (Objects.equals(newObject, oldObject)) {
            return true;
        }

        // 到这里, 只要有一个 null, 一定不等
        if (newObject == null || oldObject == null) {
            return false;
        }

        Class<?> newClass = newObject.getClass();
        Class<?> oldClass = oldObject.getClass();

        // 收集新对象的所有属性
        Field[] newDeclaredFields = newClass.getDeclaredFields();
        List<String> newDeclaredFieldNameList = new ArrayList<>();
        for (Field newDeclaredField : newDeclaredFields) {
            newDeclaredFieldNameList.add(newDeclaredField.getName());
        }

        // 收集旧对象的所有属性
        Field[] oldDeclaredFields = oldClass.getDeclaredFields();
        List<String> oldDeclaredFieldNameList = new ArrayList<>();
        for (Field oldDeclaredField : oldDeclaredFields) {
            oldDeclaredFieldNameList.add(oldDeclaredField.getName());
        }

        // 判断两对象具体属性的值是否存在差异
        for (String oldDeclaredFieldName : oldDeclaredFieldNameList) {
            // oldObject 的当前属性是否存在与 newObject 中
            if (newDeclaredFieldNameList.contains(oldDeclaredFieldName)) {
                try {
                    // 设置为允许打破封装
                    Field newDeclaredField = newClass.getDeclaredField(oldDeclaredFieldName);
                    newDeclaredField.setAccessible(true);
                    Field oldDeclaredField = oldClass.getDeclaredField(oldDeclaredFieldName);
                    oldDeclaredField.setAccessible(true);
                    try {
                        // 获取两对象的属性值, 并判断这两个属性值是否存在差异
                        Object newValue = newDeclaredField.get(newObject);
                        Object oldValue = oldDeclaredField.get(oldObject);
                        if (!Objects.equals(newValue, oldValue) && newValue != null) {
                            return false;
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                }
            }
        }
        return true;
    }

}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值