保存业务的修改记录(对象字段差异对比)

如何记录Java对象的修改历史和实现数据修改日志 在大多数业务场景中,修改历史是非常重要的信息。例如,当我们对某个对象进行数据修改时,可能需要记录修改之前和修改之后的值,以便于我们对修改进行审核和追溯,以及诊断和解决潜在的问题。在 Java 开发中,我们可以使用观察者模式来实现记录Java对象的修改历史和实现数据修改日志。本文将介绍如何实现这一功能。 阅读详情
AI摘要

本文介绍了一种利用Java反射机制实现的对象属性差异比对方法,通过自定义注解标记需要比对的字段,并将差异结果以JSON形式保存于数据库中,便于后续查询与展示。

一 概述

我们在做一些修改业务时候,可能需要对修改记录留痕迹,包括修改人、修改时间、修改前后的值。今天抛砖引玉,告诉大家适用于这种场景如何处理。

二 原理

使用Java反射机制获取新旧对象的值,并获取字段的中文字段名称,组装成一个差异对象的集合。 

三 实践

需求:修改学生记录时并保存修改记录到数据库,并可以通过学生ID查询所有修改内容。

1. 自定义注解(字段比较)【在实体类字段上使用: 字段中文名称,字段字典映射】

package com.wtao;

import java.lang.annotation.*;

/**
 * @author wtao
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface FieldCompare {

    /** 字段对应的中文 */
    String chineseName();

    /**
     * 类型映射
     * 使用方式 1:男,0:女
     */
    String properties() default "";

}

2. 创建字段差异实体类(修改的字段名称,修改前值,修改后值)

package com.wtao;

/**
 * ChangeLog 对比差异
 *
 * @auto wtao wangtao
 */
public class FieldDiffent {

    private String fieldName;
    private String before;
    private String after;

    public FieldDiffent() {
    }

    public FieldDiffent(String fieldName, String before, String after) {
        this.fieldName = fieldName;
        this.before = before;
        this.after = after;
    }

    public String getFieldName() {
        return fieldName;
    }

    public void setFieldName(String fieldName) {
        this.fieldName = fieldName;
    }

    public String getBefore() {
        return before;
    }

    public void setBefore(String before) {
        this.before = before;
    }

    public String getAfter() {
        return after;
    }

    public void setAfter(String after) {
        this.after = after;
    }

    @Override
    public String toString() {
        return "FieldDiffent{" +
                "fieldName='" + fieldName + '\'' +
                ", before='" + before + '\'' +
                ", after='" + after + '\'' +
                '}';
    }
}

3.(核心)字段对比工具类(反射比较对象字段差异, 封装成差异对象集合)

package com.wtao;

/**
 * SerializableFieldCompare TODO
 *
 * @author wangtao
 * @date 2022/9/29 11:36
 */

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 FieldCompareUtil {

    public static <T> List<FieldDiffent> compare(Class<T> type, T newObject, T oldObject) throws Exception {
        // 变更的记录列表
        List<FieldDiffent> changeLogFields = new ArrayList<>();

        Class<?> newObj = newObject.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;
                } else {
                    eq = !oldVal.equals(newVal);
                }
                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) {
                        changeLogFields.add(new FieldDiffent(newAnnotation.chineseName(), map.get(s1), map.get(s2)));
                    } else {
                        changeLogFields.add(new FieldDiffent(newAnnotation.chineseName(), s1, s2));
                    }
                }
            }
        }
        return changeLogFields;
    }

    /**
     * 字典映射 使用方式:
     * @param newAnnotation
     * @return
     */
    private static Map<String, String> codeToNameMap(FieldCompare newAnnotation) {
        Map<String, String> map = new HashMap<>();
        String properties = newAnnotation.properties();
        if (properties != null && properties.length() > 0) {
            String[] propertiesArr = properties.split(",");
            for (String propertie : propertiesArr) {
                String[] split = propertie.split(":");
                map.put(split[0], split[1]);
            }
        }
        return map;
    }
}

4. 学生实体类(业务)(在需要对比的字段上添加注解,不加注解不对比)

package com.wtao;

import java.util.Date;

/**
 * Student TODO
 *
 * @author wangtao
 * @date 2022/11/4 14:33
 */
public class Student {

    @FieldCompare(chineseName = "姓名")
    private String name;
    @FieldCompare(chineseName = "年龄")
    private Integer age;
    @FieldCompare(chineseName = "性别", properties = "1:男,0:女")
    private Integer sex;
    @FieldCompare(chineseName = "时间")
    private Date time;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public Date getTime() {
        return time;
    }

    public void setTime(Date time) {
        this.time = time;
    }
}

5. 创建学生修改记录表(主要字段:主键ID,学生ID,修改差异Json数组,修改人,修改时间)大家自己创建,这里不做数据库操作

6. 测试

public class Test {

    public static void main(String[] args) throws Exception {
        Student student = new Student();
        student.setName("张三");
        student.setAge(18);
        student.setSex(1);
        student.setTime(new SimpleDateFormat("yyyy-MM-dd").parse("2022-10-11"));

        Student newStudent = new Student();
        newStudent.setName("李四");
        newStudent.setAge(33);
        newStudent.setSex(0);
        newStudent.setTime(new SimpleDateFormat("yyyy-MM-dd").parse("2022-10-11"));

        List<FieldDiffent> compare = FieldCompareUtil.compare(Student.class, student, newStudent);

        System.out.println(compare);

    }
}

输出是差异字段的列表(ps: 因为当时测试时新旧对象传反了,导致before和after反了)

[

ChangeLogField{fieldName='姓名', before='李四', after='张三'},

ChangeLogField{fieldName='年龄', before='33', after='18'},

ChangeLogField{fieldName='性别', before='女', after='男'}

]

数据库保存大致样子(数据表里answer_id相当于业务的id)diff字段差异保存本次修改的修改内容,根据业务ID查询出所有修改的记录,前端根据每次修改记录,解析diff 的json数组来做页面展示。

 页面可以渲染成这样

 整体效果如下

 (完~)

金九银十面试季—19道精选Dubbo面试问题(含答案分析)! 前言 现在很多大中型公司 (研发团队达到60人或以上) 的系统都在采用 Dubbo 做服务管 理,一个系统会分成支付、用户、产品、活动、网关等子系统,每个子系统之间的服务依赖和调用都是通过 Dubbo 等服务中间件做管理和监控的,所以会使用被广泛运用的 Dubbo 进行开发成为了核心的技能。在这分享一些dubbo学习中及面试中的相关问题分享给大家,希望对大家有所帮助! 标题Dubbo面试问题如下: 1.Dubbo 是什么? Dubbo 是一款高性能、轻量级的开源 RPC 框架,提供服务自动注册、自动发现 阅读详情

相关推荐

JavaWeb开发:历史变更记录(基于SSM框架)

目录一、效果展示二、实现1、表单提交2、Controller层接收数据3、Service层4、ServiceImplement层(1)记录下修改前的表单内容(2)记录修改后的数据(3)记录下修改时间(4)比较新旧数据 一、效果展示 后端为SSM框架,前端采用Layui,相关简介可参考之前的博客:JavaWeb后端开发框架SSM+前端框架Layui简介 1、初始数据表格 2、修改内容: 将“现在地址”一栏中的“南京”修改为“北京”: 3、修改后: 历史变更记录的表格中包含的字段为:(护理机构)名称、

weixin_44429033的博客 5126

java字段修改记录保存实现

java字段修改记录保存实现

weixin_40133594的博客 6245

SAP 底表设计规范:配置表与业务表 5 大核心差异与 MANDT 字段实践

本文深入解析SAP系统中配置表与业务表在底表设计中的5大核心差异,重点探讨MANDT字段在多租户架构中的实践应用。通过对比两类表在字段设计、维护机制、日志策略等方面的规范要求,为SAP顾问和开发人员提供可落地的设计指南,帮助提升系统数据架构的合理性和维护效率。

weixin_42638178的博客 222

java保存实现变更记录

基于注解和反射实现保存变更记录的功能

偏执即为魔的博客 2741

7.1 SAP MASS批量修改实战指南:从对象类型选择到字段更新的全流程解析

本文详细解析了SAP MASS批量修改功能的实战应用,从对象类型选择到字段更新的全流程操作指南。通过MASS事务码,用户可以高效完成物料主数据、采购订单等20多种主数据和单据的批量修改,显著提升工作效率。文章还提供了权限检查、数据筛选、字段选择等实用技巧,帮助用户避免常见错误。

weixin_42562079的博客 380

从 CDS 字段别名到可写持久化,破解 Fiori 业务对象中的字段映射陷阱

在 SAP Fiori Elements 应用中,当 CDS 元素名称与底层数据库字段名存在语义差异时,可能导致保存后字段值丢失的问题。这种现象常见于销售订单等事务性应用,表面上是前端数据显示异常,实则是 CDS 模型与持久化层之间的映射断裂。 核心问题在于: 读取路径(数据库→CDS→UI)正常工作,但写入路径(UI→CDS→数据库)因名称不匹配而中断 设计期仅出现黄色警告,运行时才暴露数据持久化失败 草稿数据能正常显示,但激活后的活动记录出现空值 解决方案要点: 创建可写的 DDIC Database

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 33

对象比较工具类:实现对业务的修改记录保存(对象字段差异对比)

对象比较工具类:实现对业务的修改记录保存(对象字段差异对比)

林夕 514

ARM CMSIS DAP源码分析(二)

今天分析的是RDDI_DAP_GetARMRegs(DWORD *regs, DWORD *rfpu, ULONG64 mask)和RDDI_DAP_SetARMRegs(DWORD *regs, DWORD *rfpu, ULONG64 mask)这两个函数。 以RDDI_DAP_GetARMRegs(DWORD *regs, DWORD *rfpu, ULONG64 mask)为例,代码如下:

pengxiao618的专栏 7976

反射功能,进行比较实体

保存业务的修改记录(对象字段差异对比)-优快云博客

qq_21288755的博客 86

[Linux外设驱动详解]RK3588 U-Boot网络功能深度分析

本文深入分析了RK3588平台上U-Boot的网络功能实现,主要内容包括: 网络架构:详细剖析了从硬件层到协议层的完整网络栈架构,包括GMAC驱动、TFTP/NFS启动、网络命令等关键组件。 驱动实现:重点介绍了RK3588 GMAC驱动结构,包含关键数据结构、专用操作函数集、设备树兼容性配置和GRF寄存器设置。 功能实现:详细说明了驱动初始化流程、RGMII速度适配机制以及PHY接口配置过程。 高级特性:涵盖了网络启动功能、镜像更新和MAC地址传递到Linux内核的机制。 文档通过代码片段和流程图清晰展示

Just Linux! 1111

Web端百度离线地图3.0实战:从零搭建到私有化部署

本文详细介绍了Web端百度离线地图3.0的完整实战流程,从核心SDK本地化、功能模块下载到地图瓦片部署。通过逐步修改代码与配置,实现在无外网环境下的私有化部署,解决了内网或网络不稳定场景下的地图应用需求,并提供了关键的避坑指南。

n7o8p的博客 714

信息修改记录 java代码,JeeSite | 保存信息修改记录封装,批量修改封装

JeeSite | 保存信息修改记录封装,批量修改封装前面写过两篇关于“保存信息修改记录”的内容,分别如下:JeeSite | 保存信息修改记录JeeSite | 保存信息修改记录续回顾第一篇文章通过类字段的比较返回一个有字段值不同的 List,然后自己构造表字段和字典的 Map 来生成修改前和修改后的差异字符串。从而实现“信息”修改“前”和修改“后”的对比记录。第二篇文章不再自己构造表字段和字典...

weixin_28755331的博客 416

C# 调用halcon中visualize_object_model_3d显示点云

3.1 判断线程是否为空,如果为空,则新创建,如果不为空,则中断线程,清空窗口后再次执行创建操作3.2 创建:调用show()方法实例化显示类的对象,传入显示参数,将线程testvisual 绑定 在 类对象的action方法上(此处action方法内部,只调用了visualize_object_model_3d算子)3.3 启动线程。

weixin_42362219的博客 1402

java修改前后差异_java 记录对象前后修改的内容 (工具类)

java 记录对象前后修改的内容 (工具类)有时候业务需要, 需记录一条记录的修改历史, 但是不能为完成任务而硬编码, 不靠谱这种情况可以使用 java 反射来完成对对象属性的描述可以通过自定义注解来完成, 读取里面的属性进而记录修改历史.在对象的属性上面加上注解, value 设置为属性的中文描述工具了代码如下util 类(BeanChangeUtil)importjava.beans.Prop...

weixin_39890332的博客 2344

Android13配置selinux让system应用可读sys,proc,SN号

Android13预置的system应用,需要读/sys, /proc目录,读(SN)serial number号, 需要修改selinux配置,否则会报avc错.其修改方法会比Android11复杂一些.。

zmlovelx(帅得不敢出门 程序员群31843264) 2654

Python 爬虫项目:爬取百度贴吧帖子,保存标题与评论内容

在 Python 爬虫技术的学习与实战体系中,贴吧类动态内容爬取是典型的入门进阶案例,百度贴吧作为国内主流的社区论坛平台,其页面结构规范、数据载体清晰,非常适合用于巩固爬虫核心技术栈。本次实战项目基于requests请求库与解析库实现完整的爬取流程,从网页请求、数据解析、内容提取到本地持久化存储全链路落地,既能掌握基础爬虫的核心逻辑,也能理解网页结构化数据解析的通用思路,同时规避爬虫常见的反爬基础策略,具备极高的实战参考价值。本文核心价值。

2503_91057718的博客 2542

java 记录对象前后修改的内容(工具类)

有时候业务需要,需记录一条记录的修改历史,但是不能为完成任务而硬编码,不靠谱 这种情况可以使用java反射来完成 对对象属性的描述可以通过自定义注解来完成,读取里面的属性进而记录修改历史。 在对象的属性上面加上注解,value设置为属性的中文描述 工具了代码如下 util类(BeanChangeUtil) import java.beans.PropertyDescriptor; ...

weixin_34352005的博客 1632

Python爬虫实战:实时监控美国SEC官网文件更新,助你第一时间掌握市场动态

在资本市场中,信息就是财富。美国证券交易委员会(SEC)官网公开了大量公司披露文件,涵盖财报、招股说明书、内幕交易等重要信息。对投资者、分析师及法律合规人员来说,实时监控这些公开文件具有重要价值。

2201_76125261的博客 594

如何优雅利用反射实现对象属性对比

实现一个通用、可复用的对象属性对比工具,只需一个方法,就能自动识别两个对象之间的差异。支持忽略指定字段(如 id、时间戳),并可扩展为变更日志记录、操作审计等实用功能。附完整可运行代码,适合 Java 开发者直接集成到项目中,提升开发效率,告别冗余代码!

weixin_45992535的博客 410

渗透测试工具之Sliver

攻击者需要将生成的植入物传输到目标系统,并执行它。C2 框架的架构与 Metasploit(如 Meterpreter)类似,但更专注于。(Red Teaming)中常见的攻击工具之一,攻击者利用 C2 服务器。组成,二者共同实现 C2 服务器与受控设备的通信。植入物(Implants)是 Sliver 生成的。抓包分析时,可以看到所有数据都是加密传输的。和**监听器(Listeners)**功能。Sliver 支持多种通信协议,如。随着安全防御的不断提升,企业应加强。进行通信,数据仍然是加密的。

idlecloud0105的博客 1934

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

java中修改对象类的数据_java 记录对象前后修改的内容(工具类)

有时候业务需要,需记录一条记录的修改历史,但是不能为完成任务而硬编码,不靠谱这种情况可以使用java反射来完成对对象属性的描述可以通过自定义注解来完成,读取里面的属性进而记录修改历史。在对象的属性上面加上注解,value设置为属性的中文描述工具了代码如下util类(BeanChangeUtil)importjava.beans.PropertyDescriptor;importjava.lang....

weixin_29783407的博客 1591

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Java通用比较模块实战:对象差异、集合比较与多字段排序

在Java后端开发中,对象比较是绕不开的基础操作。很多开发者习惯用equals判断数据是否变化,但equals只能回答“是否相同”,无法指出具体差异字段。面对二三十个字段的实体,人眼比对几乎不可行。此时需要一个能输出“字段路径+旧值+新值”的比较模块。本文从相等性比较、顺序比较、差异比较三类概念入手,解析反射机制在字段级对比中的原理,并给出不依赖第三方库的JDK 8+实现。该模块覆盖对象字段差异检测、集合交集差集并集计算、多字段排序比较三大场景,可应用于数据审计、配置对比、接口入参校验和报表排序。通过反射兜

weixin_34208283的博客 311

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

存储过程与业务类实现业务的差异比较

以下比较不太全面,纯粹是个人的理解。可能是针对前一篇文章的补充与说明 1、批量数据的处理比较 业务逻辑:单位A部门划转到B部门,业务规则是把A部门的100人的关联单位改为B部门,同时在人员岗位变化子表里增加一条变动记录。 业务实现: 1)存储过程实现(SP实现)(两个SQL语句) insert into 岗位变化子表(变化前部门、变化前岗位、变化后部门、变化后岗位、生效时间、操作人、操作时间) select A,岗位,B,岗位,sysdate,当前登录用户,sysdate from 员工表 whe

Jack的私人博客 831

SAP中使用BDC创建或修改采购信息记录

&nbsp; &nbsp; &nbsp;工作中的需求是这样子的: &nbsp; &nbsp; &nbsp;在某天,我们公司的采购提了一个需求,说是在从OA创建采购信息记录的时候,价格上限应该自动默认为是价格字段,但是这个在系统里面没有做,采购在创建玩之后需要一个个手工去修改,因此想让我看看能不能通过开发解决。 &nbsp; &nbsp; &...

乐期无许的博客 4350

还在手动改数据库?Laravel 10自动迁移修改字段的3步高效方案

轻松掌握Laravel 10数据库迁移修改字段技巧,三步实现字段自动更新。适用于表结构调整、字段类型变更等场景,通过生成迁移文件、使用Schema门面修改字段、运行迁移命令完成操作。高效安全,避免手动改库风险,值得收藏。

LogicWander的博客 1048
上一篇: 普通Maven项目打成可执行Jar包插件
下一篇: SpringBoot项目服务器启动 / 关闭脚本
小流至江河
博客等级 码龄9年 223粉丝 53原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值