2

I've the similar use-case as described in https://github.com/javers/javers/issues/98 .

In the code below, when I update the dependent object, and commit the Employee bob, I want the changes to dependent to retrieve from JQL query QueryBuilder.byInstanceId(1L, Employee.class)

//update dependent's name ( WANT TO TRACK THIS FROM ROOT EMPLOYEE OBJECT)
Dependent aDep = bob.getDependentById(1L);
aDep.setName("Ramsey");
javers.commit("hr.manager", bob);

Full Code:

import lombok.Getter;
import lombok.Setter;
import org.javers.core.Javers;
import org.javers.core.JaversBuilder;
import org.javers.core.changelog.SimpleTextChangeLog;
import org.javers.core.diff.Change;
import org.javers.repository.jql.QueryBuilder;
import org.junit.Test;

import javax.persistence.Id;
import java.util.*;

public class JaversTests {

    @Test
    public void shouldPrintTextChangeLog() {
        // given:
        Javers javers = JaversBuilder.javers().build();

        //initial employee
        Employee bob = new Employee(1L, "Bob", 9_000, "Junior Developer");
        javers.commit("hr.manager", bob);

        // promoted
        bob.setPosition("Senior Developer");
        bob.setSalary(11_000);
        javers.commit("hr.director", bob);

        //add dependents
        bob.addDependents(new Dependent(1L, "Ram", "Son"), new Dependent(2L, "Kabita", "Daughter"));
        javers.commit("hr.manager", bob);

        //update dependent's name ( WANT TO TRACK THIS FROM ROOT EMPLOYEE OBJECT)
        Dependent aDep = bob.getDependentById(1L);
        aDep.setName("Ramsey");
        javers.commit("hr.manager", bob);

        List<Change> employeeChanges = javers.findChanges(
            QueryBuilder.byInstanceId(1L, Employee.class).withChildValueObjects().build());

        String employeeChangeLog = javers.processChangeList(employeeChanges, new SimpleTextChangeLog());

        System.out.println(employeeChangeLog);
    }
}

@Getter
@Setter
class Employee {
    @Id
    Long id;
    String name;
    double salary;
    String position;
    Set<Dependent> dependents = new HashSet<>();

    public Employee(Long id, String name, double salary, String position) {
        this.id = id;
        this.name = name;
        this.salary = salary;
        this.position = position;
    }

    void addDependents(Dependent... dependents) {
        getDependents().addAll(new LinkedList(Arrays.asList(dependents)));
    }

    Dependent getDependentById(Long id) {
        for (Dependent dep : getDependents()) {
            if (dep.getId() == id) {
                return dep;
            }
        }
        return null;
    }
}

@Setter
@Getter
class Dependent {

    @Id
    Long id;
    String name;
    String relation;

    public Dependent(Long id, String name, String relation) {
        this.id = id;
        this.name = name;
        this.relation = relation;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Dependent dependent = (Dependent) o;
        return Objects.equals(id, dependent.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

I'm getting following output. This is missing the dependent name change ( Ram to Ramsey).

commit 3.0, author: hr.manager, Oct 3, 2016 12:45:57 PM
  changed object: us.sdata.enroll.Employee/1
    set changed on 'dependents' property: [added:'us.sdata.enroll.Dependent/2', added:'us.sdata.enroll.Dependent/1']
commit 2.0, author: hr.director, Oct 3, 2016 12:45:57 PM
  changed object: us.sdata.enroll.Employee/1
    value changed on 'salary' property: '9000.0' -> '11000.0'
    value changed on 'position' property: 'Junior Developer' -> 'Senior Developer'

Thanks!

gtiwari333
  • 24,554
  • 15
  • 75
  • 102

2 Answers2

1

withChildValueObjects() filter works only for child (dependant) ValueObjects and you have both classes mapped as Entities (@Id ann is present).

Since there is no explicit parent-child relationship between Employee and Dependent objects, JaVers treats them equally. Your test will pass if you map Dependent as ValueObject (removing @Id is enough).

Bartek Walacik
  • 3,386
  • 1
  • 9
  • 14
  • By "explicit parent-child relationship", do you mean OneToMany/ManyToOne as in java persistence? – gtiwari333 Oct 04 '16 at 05:07
  • Actually, my real use case has such OneToMany/ManyToOne relationship between Employee and Dependent. – gtiwari333 Oct 04 '16 at 05:08
  • By parent-child relationship I mean ValueObject owned by an Entity (DDD meaning). If you have two Entities, this filter won't work. – Bartek Walacik Oct 04 '16 at 05:14
  • I tried marking the Employee and Dependent class with @Entity and setup the @OneToMany/@ManyToOne relationship. But the results were same. – gtiwari333 Oct 04 '16 at 05:14
  • Okay. Is there a filter already implemented into Javers that supports multiple entities to achieve my goals? – gtiwari333 Oct 04 '16 at 05:15
  • I am thinking of a workaround for this. 1) add a field on Employee entity that gets updated every time when we save Employee ( a date field with @ LastModifiedDate or a integer field that gets incremented with @ PreUpdate, @ PrePersist) 2) fetch the changes to the Employee by QueryBuilder.byInstanceId(employeeId, Employee.class) 3) loop through the changes and find the CommitId. Use the CommitId to fetch all changes ( Employee and Dependent and other child objects) that were made along with Employee on that commit by : QueryBuilder.anyDomainObject().withCommitId(commitId) Any Suggestions ? – gtiwari333 Oct 04 '16 at 05:24
1

There is no clean solution in this case as JaVers JQL doesn't support Entity joins. So there is no simple way to find snapshots of entity X which are referenced from snapshots of entity Y. What I can suggest is to prepare DTO objects shaped in the form of DDD aggregates (Entity and its child ValueObjects) and commit these DTOs to JaVers instead of your original domain objects. I know that it is not a vary elegant solution.

Bartek Walacik
  • 3,386
  • 1
  • 9
  • 14
  • Thank you. The workaround that I proposed earlier worked well :) – gtiwari333 Oct 04 '16 at 06:51
  • Steps: 1) add a field on Employee entity that gets updated every time when we save Employee ( a date field with @ LastModifiedDate or a integer field that gets incremented with @ PreUpdate, @ PrePersist) 2) fetch the changes to the Employee by QueryBuilder.byInstanceId(employeeId, Employee.class) 3) loop through the changes and find the CommitId. Use the CommitId to fetch all changes ( Employee and Dependent and other child objects) that were made along with Employee on that commit by : QueryBuilder.anyDomainObject().withCommitId(commitId) – gtiwari333 Oct 04 '16 at 06:51