Java Pojos - Setter-Call (Field Touched) Detection

57 Views Asked by At

Pojos are often used as Models/DTOs for inserting/updating data into a database.

They need to by validated (with a Validator) to make sure they only contain the expected fields for the use-case:

  • insert (all fields must be validated and inserted)
  • update (only changed/modified fields must be validated and updated into the database)

To make this possible, it would be helpful if the Java-Language already contains a mechanism to track 'modified' in pojos. A 'modified' for a field would be:

  • a setter for a field has been called.

Can someone help me solve such a setter-call detection, with as less boilerplate as possible?


Here an example of how i solved it with boilerplate, by letting the Pojos extend a common class:

public abstract class AbstractDTO {

    private Map<String, Object> modifiedFields = new HashMap<>();

    public AbstractDTO() {
    }

    @JsonIgnore
    @XmlTransient
    protected void setAt(String key, Object value) {
        this.modifiedFields.put(key, value);
    }

    @JsonIgnore
    @XmlTransient
    public void resetModifiedFields() {
        this.modifiedFields.clear();
    }

    @JsonIgnore
    @XmlTransient
    public Map<String, Object> getModifiedFields() {
        return this.modifiedFields;
    }
}

Impl:

public class Product extends AbstractDTO {

    private Long productId;

    public Long getProductId() {
        return this.productId;
    }

    public void setProductId(Long productId) {
        this.productId = productId;
        this.setAt("productId", productId);
    }

That way i can now check for modified-fields:

boolean isModified = pojo.getModifiedFields().containsKey(field.getName());

I also have found an additional way with help of reflection (which helps that my setters only need to call a touch() method in the parent:

 public void touch () {
        StackWalker walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
        StackWalker.StackFrame frame = walker.walk(frames -> {
            Iterator<StackWalker.StackFrame> it = frames.iterator();
            StackWalker.StackFrame a = it.next();
            return it.next();
        });
        String methodName = frame.getMethodName();
        try {
            String withoutSet = methodName.substring(3);
            String fieldname = Character.toLowerCase(withoutSet.charAt(0)) + withoutSet.substring(1);
            Class<?> declaringClass = frame.getDeclaringClass();
            Field field = frame.getDeclaringClass().getDeclaredField(fieldname);

            String getterName = "get" + withoutSet;
            Method getter = declaringClass.getMethod(getterName);
            Object value = getter.invoke(this);
            this.modifiedFields.put(field.getName(), value);
        } catch (Exception e) {
            throw new RuntimeException("unable to find field!");
        }
    }

if someone has any idea how i could call this "touch()" function automatically (behind-the-scenes) when the setter is called, this could also help.

2

There are 2 best solutions below

1
nd. On

I think that you have something like Javers in mind. Note that change tracking libraries typically require that you have access to the the original version of the object, so make sure that you keep that around without making changes.

0
funkrusher On

With AspectJ an Interceptor can be written, that intercepts any "set*" function calls on Subclasses of AbstractDTO "behind-the-scenes".

public aspect TouchAspect {
    pointcut setterMethodCall(AbstractDTO dto):
        execution(* org.fk.codegen.dto.AbstractDTO+.set*(..)) && target(dto);

    before(AbstractDTO dto): setterMethodCall(dto) {
        dto.touch();
    }
}