EntityUtil
该工具类为作者突发奇想写的,难免有不足之处(bug),欢迎指正;
目前只有一个entity()
方法,后续可能会继续添加新的方法。
entityEq
说明
这个方法用于两个实体类对象是否 “逻辑相等”。
满足下列两个条件之一,就认为两个实体类对象 “逻辑相等”:
- 被 Objects.equals() 判断相等。
- 同时满足下列条件:
- 两个对象存在共同的属性;
- 两个对象共同属性的值对应相等(忽略
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
属性,这样除了 id
、username
属性的值不为 null
外,其他属性的值都是 null
。但是原来的数据除了 id
、username
以外的属性值未必是 null
,这样哪怕更新的 username
和原来一样,通过 BeanUtil
将 User
对象转成 UserRequest
对象后,比较的结果仍然是 false
,还是会进行不必要的数据库更新操作。
这里提到的 BeanUtil 工具类,Spring 框架有提供,将
User
转成UserRequest
的用法:BeanUtils.copyProperties(user, userRequest);
因此,在执行数据库更新操作时,一个能够灵活比较不同类型的实体类对象之间是否逻辑相等的工具是有必要存在。
实现
思路
为了就有通用性,这里使用了反射机制,假设旧数据对应 oldObject
,新数据对应 newObject
,思路如下:
- 避免不必要的反射,先使用
Objects.equals()
比较oldObject
和newObject
是否相等; - 如果
Objects.equals()
判断结果为false
,再判断oldObject
和newObject
中是否有null
,如果有,直接返回fasle
;否则继续下面的步骤; - 通过反射机制分别获取
oldObject
和newObject
的所有属性; - 判断
newObject
的当前属性是否存在与oldObject
,如果存在,比较二者对应属性值是否相等:!Objects.equals(newValue, oldValue) && newValue != null
,如果结果为true
,直接返回false
。
代码
/**
* 实体类工具类
*
* @author 孤诣
*/
public class EntityUtil {
/**
* <p>
* 传入两个对象, 比较它们在逻辑上是否相等.<br>
* 对于传入的两个对象, 即使它们引用类型可以不同, 仍然有可能被判定为逻辑相等.
* </p>
* <p>
* 在这里, 传入的两个对象在逻辑上相等需要满足的条件: <br>
* 1. 被 Objects.equals() 判断相等. <br>
* 2. 或者同时满足下列条件: <br>
* 1) 两个对象存在共同的属性. <br>
* 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;
}
}