Swamy
Swamy

Reputation: 21

Javers not creating snapshot for changes in object attributes inside a collection

I am evaluating javers to use it for auditing entities. I have an Entity with nested collection of ValueObjects.I expect each attribute change on the valueobject to generate a snapshot of the Entity.Snapshot is created only when a valueobject is added to the collection.In my case i added two valueobjects to the collection which created two snapshots of the entity. On third occasion i just changed an attribute on value object, and javers didn't recognize that as a change on the entity but created a snapshot for inner value objects.

My question is whether my assumption is valid or what is the best way to track the changes to the value objects in a collection

Below is the code from a simple test i have created using spring boot.

I am using javers version 3.2.0

My entity is as below

package com.example.javersdemo;

import com.fasterxml.jackson.annotation.JsonUnwrapped;
import lombok.Data;
import org.javers.core.metamodel.annotation.TypeName;

import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import java.util.List;

@Data
@Entity
@TypeName("User")
class User{
    @Id
    private String name ;


    @JsonUnwrapped
    @ElementCollection(targetClass = Hobby.class)
    @CollectionTable(name = "USER_HOBBIES")
    @JoinColumn(name = "NAME")
    private List<Hobby> hobbies;

    private User(){

    }

    public User(String name, List<Hobby> hobbies) {
        this.name = name;
        this.hobbies = hobbies;
    }
}

Value object is as below

package com.example.javersdemo;

import lombok.Data;

import javax.persistence.Embeddable;

@Data
@Embeddable
public class Hobby {

    private String hobby;

    private boolean active;

    private Hobby() {

    }

    public Hobby(String hobby, boolean active) {

        this.hobby = hobby;
        this.active = active;
    }
}

My spring data repository is as below

package com.example.javersdemo;

import org.javers.spring.annotation.JaversSpringDataAuditable;
import org.springframework.data.repository.CrudRepository;


@JaversSpringDataAuditable
interface TestUserRepository extends CrudRepository<User,String> {

}

Below is a spock integration test i have created to verify the changes to the object attributes inside a collection creates a new snapshot.

package com.example.javersdemo

import org.javers.core.Javers
import org.javers.repository.jql.QueryBuilder
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class JaversInnerValueObjectsTest extends Specification {

    @Autowired
    TestUserRepository userRepository

    @Autowired
    Javers javers


    def 'should create 6 snapshots'() {

        given:
        def hobbies = [new Hobby('Reading books', true)]
        def user = new User('John', hobbies)

        when:
        userRepository.save(user)

        hobbies = [new Hobby('Reading books', true), new Hobby('Watching Soccer', true)]

        user.hobbies = hobbies

        userRepository.save(user)

        hobbies = [new Hobby('Reading books', true), new Hobby('Watching Soccer', false)]

        user.hobbies = hobbies

        userRepository.save(user)



        then:
        QueryBuilder jqlQuery = QueryBuilder.byInstanceId('John', User)
        def snapshots = javers.findSnapshots(jqlQuery.withChildValueObjects().build())
        snapshots.size() == 6


    }

}

and the test fails with below error

Condition not satisfied:

snapshots.size() == 6 | | | | 5 false [Snapshot{commit:3.0, id:User/John#hobbies/1, version:2, (hobby:Watching Soccer)}, Snapshot{commit:2.0, id:User/John#hobbies/1, version:1, (active:true, hobby:Watching Soccer)}, Snapshot{commit:2.0, id:User/John, version:2, (hobbies:[User/John#hobbies/0, User/John#hobbies/1], name:John)}, Snapshot{commit:1.0, id:User/John#hobbies/0, version:1, (active:true, hobby:Reading books)}, Snapshot{commit:1.0, id:User/John, version:1, (hobbies:[User/John#hobbies/0], name:John)}]

Expected :6

Actual :5

Upvotes: 2

Views: 2036

Answers (1)

Bartek Walacik
Bartek Walacik

Reputation: 3496

In the log there are JaVers commit statistis:

13:27:44.315 [main] INFO  org.javers.core.Javers - Commit(id:1.0, snapshots:2, author:author, changes - NewObject:2), done in 71 millis (diff:71, persist:0)
13:27:44.333 [main] INFO  org.javers.core.Javers - Commit(id:2.0, snapshots:2, author:author, changes - ListChange:1 NewObject:1), done in 17 millis (diff:17, persist:0)
13:27:44.336 [main] INFO  org.javers.core.Javers - Commit(id:3.0, snapshots:1, author:author, changes - ValueChange:1), done in 3 millis (diff:3, persist:0)

In the firts commit, 2 snapshots are created because we have 2 new objects here.

In the second commit another 2 snapshots are created:

  • User (entity) snapshot, because user's field hobbies is changed - new item added to the list so ListChange
  • Hobby (value Object) snapshot, because it's a new object

In the third commit only 1 snapshot is created, because user's field hobbies isn't changed. JaVers treats collections of Value Objects and collections of Entities as collections of references (GlobalId's). In this case, references are not changed. The collection's state is captured as follows:

["User/John#hobbies/0",
 "User/John#hobbies/1"]

The only change in the third commit is the Hobby's field change (true->false) so ValueChange in ValueObject with GlobalId User/John#hobbies/1

In other words, when you change Value Objects owned by some Entity, and there is no change in Entity state, Entity snapshot will not be created, because it would be exactly the same as the previous one.

Upvotes: 0

Related Questions