Custom auditing with spring and hibernate on specific field
There are multiple ways to audit table fields with hibernate envers @Audited annotation. If this is used at entity level it will store audit of all fields like below(Example 1)
But in above two example, it will store the audit of all(Example 1) or few fields(Example 2) on change of ANY field. in example 2 if fieldName1 is changed then also it will create row in audit table with recent value of fieldName2.
Sometimes we may encounter use case where we want to audit only when specific field is changed, we can achieve this without performance hit in following way.
Since our save method is async another thread will perform audit save operation.
If you have doubts or need any clarification, please let me know in comments.
@Audited public class SampleClass { @Id private Long id; @Column(name = "field_name_1") private String fieldName1; . . . }
Example 1
If @Audited is used at field level, it will store audit of that specific field. In below example(Example 2) it will store audit of only fieldName2 field.public class SampleClass { @Id private Long id; @Column(name = "field_name_1") private String fieldName1; @Audited @Column(name = "field_name_2") private String fieldName2; . }
Example 2
But in above two example, it will store the audit of all(Example 1) or few fields(Example 2) on change of ANY field. in example 2 if fieldName1 is changed then also it will create row in audit table with recent value of fieldName2.
Sometimes we may encounter use case where we want to audit only when specific field is changed, we can achieve this without performance hit in following way.
- add transient field.
- copy previous copy to another new object.
- async save class to store previous copy.
Since our save method is async another thread will perform audit save operation.
public class SampleClass { @Id private Long id; @Column(name = "field_name_1") private String fieldName1; @Audited @Column(name = "field_name_2") private String fieldName2; @Transient private SampleClass prevSampleClass; @PostLoad private void storePrevCopy() { org.springframework.beans.BeanUtils.BeanUtils.copyProperties(this, new SampleClass(), "prevSampleClass"); } @PostPersist @PostUpdate private void logAudit() { CustomAuditLogService service = ApplicationContextAwareConfig.getApplicationContext() .getAutowireCapableBeanFactory().getBean(CustomAuditLogService.class); service.save(prevSampleClass, this); } }
@Service public class CustomAuditLogService { @Autowired private SampleClassAuditRepository sampleClassAuditRepository; @Async public void save(SampleClass prevSample, SampleClass newSample) { ListskipFields = new ArrayList<>(); skipFields.addAll(Arrays.asList("prevSampleClass","a", "b")); boolean auditToBeSaved = false; if (null != prevSample) { List changedFields = getChangedFields(prevSample, newSample, SampleClass.class, skipFields); if (!CollectionUtils.isEmpty(changedFields)) { auditToBeSaved = true; } } else { auditToBeSaved = true; } if (auditToBeSaved) { SampleClassAudit sampleAudit = new SampleClassAudit(); BeanUtils.copyProperties(newSample, sampleAudit); vntAuditRepository.save(sampleAudit); } } private List getChangedFields(T obj1, T obj2, Class clazz, List skipFields) { List fields = getAllFields(new ArrayList<>(), clazz); List changedFields = new ArrayList<>(); for (Field field : fields) { try { if (!skipFields.contains(field.getName())) { PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), clazz); Object o1Value = propertyDescriptor.getReadMethod().invoke(obj1); Object o2Value = propertyDescriptor.getReadMethod().invoke(obj2); if (null != o1Value && null != o2Value && !o1Value.equals(o2Value)) { changedFields.add(field.getName()); } else if ((null == o1Value && null != o2Value) || (null != o1Value && null == o2Value)) { changedFields.add(field.getName()); } } } catch (Throwable t) { log.error("Field {} could not be compared", field.getName(), t); } } return changedFields; } }
If you have doubts or need any clarification, please let me know in comments.
Comments
Post a Comment