审核 属性 更改 - Spring MVC+ JPA

Audit Property change - Spring MVC+ JPA

我有一个 class 客户。我希望能够审核此 class 的 属性 的更改(不是整个 class - 只是它的属性)。

public class Client {
private Long id;
private String firstName;
private String lastName;
private String email;
private String mobileNumber;
private Branch companyBranch;

实际上,使用@Audited 注解审计整个实体非常容易。

但我想要的是使用我的 class 结构来审核此更改。

这是我想要的结果 class:

public class Action {
private String fieldName;
private String oldValue;
private String newValue;
private String action;
private Long modifiedBy;
private Date changeDate;
private Long clientID;

结果应如下所示:

fieldName + "was changed from " + oldValue + "to" + newValue + "for" clientID +"by" modifiedBy;

我这样做的原因是我需要将此更改存储到操作下的数据库中 table - 因为我将审计来自不同实体的属性并且我想将它们存储在一起然后有一个在我需要时获得它们的能力。

我该怎么做?

谢谢

如果您正在使用 Hibernate,您可以使用 Hibernate Envers,并定义您自己的 RevisionEntity(如果您想使用 java.time,您将需要 Hibernate 5.x . 在早期版本中,甚至自定义 JSR-310 实用程序也无法用于审计目的)

如果您不使用 Hibernate 或想拥有纯 JPA 解决方案,那么您将需要使用 JPA EntityListeners 机制编写自定义解决方案。

我不知道"modifiedBy"属性到底是什么(应用程序的用户还是另一个客户端?),但是忽略这个,你可以捕捉到[=20中所有属性的修改=]

(注意:更改 setter 实现或向 setter 添加其他参数是不好的做法,这项工作应使用 LOGGER 或 AOP 完成):

  public class Client {
    private Long id;
    private String firstName;
    private String lastName;
    private String email;
    private String mobileNumber;
    private Branch companyBranch;
    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn("client_ID");
    List<Action> actions = new ArrayList<String>();

    public void setFirstName(String firstName,Long modifiedBy){
    // constructor       Action(fieldName,  oldValue,      newValue ,modifiedBy)
    this.actions.add(new Action("firstName",this.firstName,firstName,modifiedBy));
    this.firstName=firstName;
    }
//the same work for lastName,email,mobileNumber,companyBranch
}

注意:最好和正确的解决方案是使用 LOGGER 或 AOP

Aop 是正确的方法。您可以根据需要将 AspectJ 与字段 set() 切入点一起使用。使用 before 方面,您可以提取必要的信息来填充 Action 对象。

您还可以使用自定义 class 注释 @AopAudit 来检测 class 您想要审核的内容。您必须在您的 class 路径中定义此类注释,并将其放置在您要审核的目标 classes 下。

这种方法看起来像这样:

AopAudit.java

@Retention(RUNTIME)
@Target(TYPE)
public @interface AopAudit {

}

Client.java

@AopAudit
public class Client {
    private Long id;
    private String firstName;
    private String lastName;
    private String email;
    private String mobileNumber;
}

AuditAnnotationAspect.aj

import org.aspectj.lang.reflect.FieldSignature;

import java.lang.reflect.Field;

public aspect FieldAuditAspect {

pointcut auditField(Object t, Object value): set(@(*.AopAudit) * *.*) && args(value) && target(t);

pointcut auditType(Object t, Object value): set(* @(*.AopAudit) *.*) && args(value) && target(t);

before(Object target, Object newValue): auditField(target, newValue) || auditType(target, newValue) {
        FieldSignature sig = (FieldSignature) thisJoinPoint.getSignature();
        Field field = sig.getField(); 
        field.setAccessible(true);

        Object oldValue;
        try
        {
            oldValue = field.get(target);
        }
        catch (IllegalAccessException e)
        {
            throw new RuntimeException("Failed to create audit Action", e);
        }

        Action a = new Action();
        a.setFieldName(sig.getName());
        a.setOldValue(oldValue == null ? null : oldValue.toString());
        a.setNewValue(newValue == null ? null : newValue.toString());
    }

}

这是定义 auditField 切入点以捕获字段集操作和 before 逻辑以创建 Audit 对象的 AspectJ 方面。

要启用 AspectJ Compile Time Weaving,您必须在 Maven 的情况下执行以下操作:

pom.xml

...

<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
    </dependency>
</dependencies>

...

<plugins>
    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.6</version>
        <configuration>
            <showWeaveInfo>true</showWeaveInfo>
            <source>${java.source}</source>
            <target>${java.target}</target>
            <complianceLevel>${java.target}</complianceLevel>
            <encoding>UTF-8</encoding>
            <verbose>false</verbose>
            <XnoInline>false</XnoInline>
        </configuration>
        <executions>
            <execution>
                <id>aspectj-compile</id>
                <goals>
                    <goal>compile</goal>
                </goals>
            </execution>
            <execution>
                <id>aspectj-compile-test</id>
                <goals>
                    <goal>test-compile</goal>
                </goals>
            </execution>
        </executions>
        <dependencies>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>${aspectj.version}</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjtools</artifactId>
                <version>${aspectj.version}</version>
            </dependency>
        </dependencies>
    </plugin>
</plugins>

Maven 配置启用 AspectJ 编译器,使字节码 post 处理您的 classes。

applicationContext.xml

<bean class="AuditAnnotationAspect" factory-method="aspectOf"/>

您可能还需要将方面实例添加到 Spring 应用程序上下文以进行依赖注入。

更新: Here就是这样的AspectJ项目配置的例子

AOP 绝对是您案例的解决方案,我用 Spring AOP 实现了类似案例以保持实体修订。此解决方案的一个要点是需要使用 around 切入点。

另一个解决方案是使用org.hibernate.Interceptororg.hibernate.EmptyInterceptor应该是合适的扩展,我写了一些简单的代码来模拟它(拿你的客户端代码):

@Entity
public class Client {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String firstName;
    private String lastName;
    private String email;
    private String mobileNumber;
    // getter and setter
}

Interceptor 实现

public class StateChangeInterceptor extends EmptyInterceptor {
    @Override
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
        if (entity instanceof Client) {
            for (int i = 0; i < propertyNames.length; i++) {
                if (currentState[i] == null && previousState[i] == null) {
                    return false;
                } else {
                    if (!currentState[i].equals(previousState[i])) {
                        System.out.println(propertyNames[i] + " was changed from " + previousState[i] + " to " + currentState[i] + " for " + id);
                    }
                }

            }
        }

        return true;
    }

    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        return super.onSave(entity, id, state, propertyNames, types);
    }
}

注册inceptor,我用的是spring boot,直接添加到application.properties

spring.jpa.properties.hibernate.ejb.interceptor=io.cloudhuang.jpa.StateChangeInterceptor

这是测试

@Test
public void testStateChange() {
    Client client = new Client();
    client.setFirstName("Liping");
    client.setLastName("Huang");

    entityManager.persist(client);
    entityManager.flush();

    client.setEmail("test@example.com");
    entityManager.persist(client);
    entityManager.flush();
}

将得到如下输出:

email was changed from null to test@example.com for 1

所以假设它可以用 Action 个对象替换。

这是一个开源项目JaVers - object auditing and diff framework for Java

JaVers is the lightweight Java library for auditing changes in your data.

你可以看看这个项目

我希望您应该使用审计 属性 覆盖实体的 equals 方法。 在 DAO 中,您只需使用在实体内部创建的 equals 方法将旧的 instanceof 实体与新实例进行比较。

您将能够识别这是否可审核。