1.使用反射机制获取Bean字段
2.使用自定义注解获取字段说明;
3.动态多种数据类型都可支持,数据类型可自行添加
上代码
调用方式:
List<String> compareLog = SerializableFieldCompare.compare(Test.class, newTest, oldTest);
import org.apache.commons.lang3.StringUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SerializableFieldCompare {
public static <T> List<String> compare (Class<T> type, T newObject, T oldObject ) throws Exception {
List<String> logList = new ArrayList<>();
Class<?> newObj = newObject.getClass();
Class<?> oldObj = oldObject.getClass();
Field[] newFields = type.getDeclaredFields();
for (int i = 0; i < newFields.length; i++) {
FieldCompare newAnnotation = newFields[i].getAnnotation(FieldCompare.class);
if(null != newAnnotation){
PropertyDescriptor newPd = new PropertyDescriptor(newFields[i].getName(), newObj);
Method getMethodNew = newPd.getReadMethod();
Object oldVal = getMethodNew.invoke(oldObject);
Object newVal = getMethodNew.invoke(newObject);
boolean eq = false;
if (oldVal instanceof String){
String s1 = String.valueOf(oldVal).trim();
String s2 = String.valueOf(newVal).trim();
eq = !s1.equals(s2);
}else if(oldVal instanceof Integer){
int i1 = (Integer) oldVal;
int i2 = (Integer) newVal;
eq = i1 != i2;
}else if(oldVal instanceof Double){
double d1 = (Double) oldVal;
double d2 = (Double) newVal;
eq = d1 != d2;
}else if(oldVal instanceof BigDecimal){
BigDecimal b1 = (BigDecimal) oldVal;
BigDecimal b2 = (BigDecimal) newVal;
eq = b1.compareTo(b2) != 0;
}else if(oldVal instanceof Long){
long l1 = (Long) oldVal;
long l2 = (Long) newVal;
eq = l1 != l2;
String s1 = oldVal == null ? "" : oldVal.toString().trim();
String s2 = newVal == null ? "" : newVal.toString().trim();
if (eq) {
Map<String, String> map = codeToNameMap(newAnnotation);
if (map.size() > 0) {
logList.add(newAnnotation.chineseName() + "由:[" + map.get(s1) + "]更改为了:[" + map.get(s2) + "]");
}else {
logList.add(newAnnotation.chineseName() + "由:[" + s1 + "]更改为了:[" + s2 + "]");
return logList;
private static Map<String,String> codeToNameMap(FieldCompare newAnnotation){
Map<String,String> map = new HashMap<>();
String properties = newAnnotation.properties();
if(StringUtils.isNotBlank(properties)){
String[] propertiesArr = properties.split(",");
for (String propertie : propertiesArr) {
String[] split = propertie.split(":");
map.put(split[0],split[1]);
return map;
自定义注解类:
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface FieldCompare {
String chineseName();
String properties() default "";
测试Bean:
注解说明:
chineseName属性为字段名称
properties属性 是用来映射数字对应的类型名称
public class Test implements Serializable {
private Integer id;
private Integer serviceId;
@FieldCompare(chineseName = "服务费收取方式", properties = "1:固定金额,2:百分比")
private Integer serviceFeeCollectWay;
@FieldCompare(chineseName = "线上")
private BigDecimal servicePriceOnline;
@FieldCompare(chineseName = "线下")
private BigDecimal servicePriceOffline;
private Integer type;
private Date createTime;
public Integer getId() {
return id;
public void setId(Integer id) {
this.id = id;
public Integer getServiceId() {
return serviceId;
public void setServiceId(Integer serviceId) {
this.serviceId = serviceId;
public Integer getServiceFeeCollectWay() {
return serviceFeeCollectWay;
public void setServiceFeeCollectWay(Integer serviceFeeCollectWay) {
this.serviceFeeCollectWay = serviceFeeCollectWay;
public BigDecimal getServicePriceOnline() {
return servicePriceOnline;
public void setServicePriceOnline(BigDecimal servicePriceOnline) {
this.servicePriceOnline = servicePriceOnline;
public BigDecimal getServicePriceOffline() {
return servicePriceOffline;
public void setServicePriceOffline(BigDecimal servicePriceOffline) {
this.servicePriceOffline = servicePriceOffline;
public Integer getType() {
return type;
public void setType(Integer type) {
this.type = type;
public Date getCreateTime() {
return createTime;
public void setCreateTime(Date createTime) {
this.createTime = createTime;
有时候业务需要,需记录一条记录的修改历史,但是不能为完成任务而硬编码,不靠谱
这种情况可以使用java反射来完成
对对象属性的描述可以通过自定义注解来完成,读取里面的属性进而记录修改历史。
在对象的属性上面加上注解,value设置为属性的中文描述
工具了代码如下
util类(BeanChange......
分别比较修改前后两个Bean实例的所有成员变量,当值不一致时,记录变量名称,以及修改前后的值。 对于该方案,可以解决特定类型的Bean。 如果有其它类型的Bean也有这种需求,则需要新写一套逻辑,处理相应的需求。
上述方案不能复用,如果有多个这样的Bean需要比较,则每个Bean都需要新写一套逻辑。然而,利用泛型和反射技术,则可以达到一次编码,多处复用的效果
Java 记录类中所有
字段改变前,改变后的
数据
需求:
记录类中所有
字段的改动,包含改动
字段、改动
字段路径、改动前
数据值、改动后
数据值、包含类中List和枚举的改动。
import
java.lang.reflect.Field;
import
java.lang.reflect.Method;
import
java.util.ArrayList;
import
java.util.Arrays;
import
java.util.Collection;
import
java.util.List;
web/springboot数据变更历史记录设计
在一些领域,记录数据的变更历史是非常重要的。比如人力资源系统…
需要记录个人的成长历史。再比如一些非常注重安全的系统,希望在必要时可以对所有的历史操作追根溯源,有据可查。
比如,修改一个人的姓名从“张三”变为了“李四”,那么在进行记录的时候,记录的信息可能如下:
姓名:(张三)=>(李四);
这样就很好的体现出了修改了哪个字段,修改前后的数据分别是什么。关键的信息无论怎么修改都会有据可查,时间、人物、修改数据前后信息等。