我有一个班级客户。我希望能够审计这个类的属性的变化(不是整个类 - 只是它的属性)。
public class Client {
private Long id;
private String firstName;
private String lastName;
private String email;
private String mobileNumber;
private Branch companyBranch;
实际上,使用@Audited注释审核整个实体非常容易。
但我想要的是使用我的类结构审核这些更改。
这是我想要的结果类:
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 +"已从"更改+ oldValue +" to" + newValue +" for" clientID +" by" modifiedBy;
我这样做的原因是我需要将这些更改存储到Action表下的DB中 - 因为我将审核来自不同实体的属性,我想将它们存储在一起,然后才有能力获取它们当我需要的时候
我该怎么做?
由于
答案 0 :(得分:8)
Aop是正确的方法。您可以将AspectJ与字段set()
切入点一起使用以满足您的需求。使用before
方面,您可以提取必要的信息来填充Action对象。
您还可以使用自定义类注释@AopAudit
来检测要审核的类。您必须在类路径中定义此类注释,并将其放在要审核的目标类下。
这种方法看起来像这样:
<强> 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());
}
}
这是AspectJ方面,它定义auditField
切入点来捕获字段集操作,并before
逻辑来创建Audit
对象。
要启用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编译器能够对您的类进行字节码后处理。
<强>的applicationContext.xml 强>
<bean class="AuditAnnotationAspect" factory-method="aspectOf"/>
此外,您可能需要将方面实例添加到Spring Application Context以进行依赖项注入。
<强> UPD:强> Here是此类AspectJ项目配置的示例
答案 1 :(得分:3)
如果你正在使用Hibernate,你可以使用Hibernate Envers,并定义你自己的RevisionEntity
(如果你想使用java.time
,你将需要Hibernate 5.x.在早期版本中即使是定制的JSR-310实用程序也不能用于审计目的)
如果您没有使用Hibernate或想要使用纯JPA解决方案,那么您需要使用JPA EntityListeners
机制编写自定义解决方案。
答案 2 :(得分:1)
我不确切知道“modifiedBy”属性是什么(应用程序的用户或其他客户端?),但是忽略了这个属性,你可以捕获setter中所有属性的修改
(注意:更改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
答案 3 :(得分:1)
AOP绝对是您案例的解决方案,我使用Spring AOP实现了类似的案例来保持实体修订。此解决方案需要使用 切入点。
另一个解决方案是使用org.hibernate.Interceptor
,org.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);
}
}
注册接收器,我使用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是用于审核数据更改的轻量级Java库。
你可以看看这个项目。
答案 4 :(得分:1)
我希望您应该使用Audit属性覆盖实体的equals方法。 在DAO中,您只需使用在实体内部创建的equals方法将旧的instanceof实体与新实例进行比较。
您将能够识别这是否可审核。