Interface Javers


  • public interface Javers
    Facade to JaVers instance.
    Should be constructed by JaversBuilder provided with your domain model configuration.

    For example, to deeply compare two objects or two arbitrary complex graphs of objects, call:
     Javers javers = JaversBuilder.javers().build();
     Diff diff = javers.compare(oldVersion, currentVersion);
     
    See Also:
    http://javers.org/documentation
    • Method Detail

      • commit

        Commit commit​(java.lang.String author,
                      java.lang.Object currentVersion)
        Persists a current state of a given domain object graph in JaVers repository.

        JaVers applies commit() to given object and all objects navigable from it. You can capture a state of an arbitrary complex object graph with a single commit() call.
        Parameters:
        author - current user
        currentVersion - standalone object or handle to an object graph
        See Also:
        http://javers.org/documentation/repository-examples
      • commit

        Commit commit​(java.lang.String author,
                      java.lang.Object currentVersion,
                      java.util.Map<java.lang.String,​java.lang.String> commitProperties)
        Variant of commit(String, Object) with commitProperties.
        You can pass arbitrary commit properties and use them in JQL to search for snapshots or changes.
        Parameters:
        commitProperties - for example ["channel":"web", "locale":"pl-PL"]
        See Also:
        QueryBuilder.withCommitProperty(String, String)
      • commitAsync

        java.util.concurrent.CompletableFuture<Commit> commitAsync​(java.lang.String author,
                                                                   java.lang.Object currentVersion,
                                                                   java.util.concurrent.Executor executor)
        Async version of commit(String, Object)

        Important! Async Javers commits work seamless with MongoDB. If you are using SQL repository — take extra care about transaction management.
        Parameters:
        executor - ExecutorService to be used to process commit() asynchronously.
      • commitAsync

        java.util.concurrent.CompletableFuture<Commit> commitAsync​(java.lang.String author,
                                                                   java.lang.Object currentVersion,
                                                                   java.util.Map<java.lang.String,​java.lang.String> commitProperties,
                                                                   java.util.concurrent.Executor executor)
        Async version of commit(String, Object, Map)

        Parameters:
        executor - ExecutorService to be used to process commit() asynchronously
      • commitShallowDelete

        Commit commitShallowDelete​(java.lang.String author,
                                   java.lang.Object deleted)
        Marks given object as deleted.

        Unlike commit(String, Object), this method is shallow and affects only given object.

        This method doesn't delete anything from JaVers repository. It just persists 'terminal snapshot' of a given object.
        Parameters:
        deleted - object to be marked as deleted (Entity or Value Object)
      • compare

        Diff compare​(java.lang.Object oldVersion,
                     java.lang.Object currentVersion)

        Deep compare

        JaVers core function, deeply compares two arbitrary complex object graphs.

        To calculate a diff, just provide two versions of the same domain object.
        The domain object can be a root of an Aggregate, tree root or any node in a domain object graph from where all other nodes are navigable.

        Both oldVersion and currentVersion should be mapped to EntityType or ValueObjectType, see Domain model mapping.

        Flat collection compare

        You can also pass object collections here (List, Sets or Maps), but in this case, JaVers calculates flat collection diff only. Because it's impossible to determine type of raw collection items, JaVers maps them as Values and compares using Object.equals(Object).
        So if you need to deep compare, wrap collections in some Value Objects.

        Misc

        compare() function is used for ad-hoc objects comparing. In order to use data auditing feature, call commit(String, Object).

        Diffs can be converted to JSON with JsonConverter.toJson(Object) or pretty-printed with Diff.prettyPrint()

        Parameters:
        oldVersion - Old version of a domain object, an instance of EntityType or ValueObjectType , nullable
        currentVersion - Current version of a domain object, nullable
        See Also:
        http://javers.org/documentation/diff-examples
      • compareCollections

        <T> Diff compareCollections​(java.util.Collection<T> oldVersion,
                                    java.util.Collection<T> currentVersion,
                                    java.lang.Class<T> itemClass)
        Deeply compares two top-level collections.

        Introduced due to the lack of possibility to statically determine type of collection items when two top-level collections are passed as references to compare(Object, Object).

        Usage example:
         List<Person> oldList = ...
         List<Person> newList = ...
         Diff diff = javers.compareCollections(oldList, newList, Person.class);
         
        See Also:
        Compare top-level collections example
      • initial

        @Deprecated
        Diff initial​(java.lang.Object newDomainObject)
        Deprecated.
        Use compare(Object, Object) passing null as the first parameter.
        Initial diff is a kind of snapshot of a given object graph.
      • findShadows

        <T> java.util.List<Shadow<T>> findShadows​(JqlQuery query)
        Queries JaversRepository for object Shadows.
        Shadow is a historical version of a domain object restored from a snapshot.

        For example, to get latest Shadows of "bob" Person, call:
         List shadows = javers.findShadows( QueryBuilder.byInstanceId("bob", Person.class)
              .limit(5).build() );
         
        Since Shadows are instances of your domain classes, you can use them directly in your application:
         assert shadows.get(0).get() instanceof Person.class;
         

        Query scopes

        By default, queries are run in the Shallow scope, which is the fastest one.
        To eagerly load all referenced objects use one of the wider scopes, Commit-deep or Deep+ : We recommend the Deep+ scope as a good start.

        Query scopes example

        To understand Shadow query scopes, you need to understand how JaVers commit works.
        Remember that JaVers reuses existing snapshots and creates a fresh one only if a given object is changed.
        The way how objects are committed affects shadow query results.

        For example, we have four Entities in the object graph, joined by references:

         // E1 -> E2 -> E3 -> E4
         def e4 = new Entity(id:4)
         def e3 = new Entity(id:3, ref:e4)
         def e2 = new Entity(id:2, ref:e3)
         def e1 = new Entity(id:1, ref:e2)
         

        In the first scenario, our four entities are committed in three commits:

        In Shallow scope, referenced entities are not loaded. But they all can be loaded using Deep+3 scope.
        given:
          javers.commit("author", e4) // commit 1.0 with e4 snapshot
          javers.commit("author", e3) // commit 2.0 with e3 snapshot
          javers.commit("author", e1) // commit 3.0 with snapshots of e1 and e2
        
        when: 'shallow scope query'
          def shadows = javers.findShadows(QueryBuilder.byInstanceId(1, Entity)
                              .build())
          def shadowE1 = shadows.get(0).get()
        
        then: 'only e1 is loaded'
          shadowE1 instanceof Entity
          shadowE1.id == 1
          shadowE1.ref == null
        
        when: 'commit-deep scope query'
          shadows = javers.findShadows(QueryBuilder.byInstanceId(1, Entity)
                          .withScopeCommitDeep().build())
          shadowE1 = shadows.get(0).get()
        
        then: 'only e1 and e2 are loaded, both was committed in commit 3.0'
          shadowE1.id == 1
          shadowE1.ref.id == 2
          shadowE1.ref.ref == null
        
        when: 'deep+1 scope query'
          shadows = javers.findShadows(QueryBuilder.byInstanceId(1, Entity)
                          .withScopeDeepPlus(1).build())
          shadowE1 = shadows.get(0).get()
        
        then: 'only e1 + e2 are loaded'
          shadowE1.id == 1
          shadowE1.ref.id == 2
          shadowE1.ref.ref == null
        
        when: 'deep+3 scope query'
          shadows = javers.findShadows(QueryBuilder.byInstanceId(1, Entity)
                          .withScopeDeepPlus(3).build())
          shadowE1 = shadows.get(0).get()
        
        then: 'all object are loaded'
          shadowE1.id == 1
          shadowE1.ref.id == 2
          shadowE1.ref.ref.id == 3
          shadowE1.ref.ref.ref.id == 4
        

        In the second scenario, our four entities are committed in the single commit:

        Shallow scope works in the same way. Commit-deep scope is enough to load the full graph.
        given:
          javers.commit("author", e1) //commit 1.0 with snapshots of e1, e2, e3 and e4
        
        when: 'commit-deep scope query'
          shadows = javers.findShadows(QueryBuilder.byInstanceId(1, Entity)
                          .withScopeCommitDeep().build())
          shadowE1 = shadows.get(0).get()
        
        then: 'all object are loaded'
          shadowE1.id == 1
          shadowE1.ref.id == 2
          shadowE1.ref.ref.id == 3
          shadowE1.ref.ref.ref.id == 4
        

        Performance & Profiling

        Each Shadow query executes one or more Snapshot queries (depending on the scope) and complexity of an object graph. If you are having performance issues, check with debug mode how your Shadow query is executed and then, try to reduce the scope.
        <logger name="org.javers.JQL" level="DEBUG"/>
        
        Example debug for a Shadow query execution:
        DEBUG org.javers.JQL - SHALLOW query: 1 snapshots loaded (entities: 1, valueObjects: 0)
        DEBUG org.javers.JQL - DEEP_PLUS query for '...SnapshotEntity/2' at commitId 3.0, 1 snapshot(s) loaded, gaps filled so far: 1
        DEBUG org.javers.JQL - warning: object '...SnapshotEntity/3' is outside of the DEEP_PLUS+1 scope, references to this object will be nulled. Increase maxGapsToFill and fill all gaps in your object graph.
        DEBUG org.javers.JQL - queryForShadows executed:
        JqlQuery {
          IdFilter{ globalId: ...SnapshotEntity/1 }
          QueryParams{ aggregate: true, limit: 100 }
          ShadowScopeDefinition{ shadowScope: DEEP_PLUS, maxGapsToFill: 1 }
          Stats{
            executed in millis: 12
            DB queries: 2
            all snapshots: 2
            SHALLOW snapshots: 1
            DEEP_PLUS snapshots: 1
            gaps filled: 1
            gaps left!: 1
          }
        }
        
        Execution stats are also available in JqlQuery.stats().
        Type Parameters:
        T - type of a domain object
        Returns:
        A list of Shadows, ordered in reverse chronological order. Empty if nothing found. Remember that QueryBuilder.limit(int) limits the number of Snapshots loaded in the base query and not the number of returned Shadows (one Shadow can be reconstructed from many Snapshots). The only correct way for paging Shadows is findShadowsAndStream(JqlQuery) with Stream.skip(long) and Stream.limit(long).
        Since:
        3.2
        See Also:
        ShadowScope, http://javers.org/documentation/jql-examples
      • findShadowsAndStream

        <T> java.util.stream.Stream<Shadow<T>> findShadowsAndStream​(JqlQuery query)
        Streamed version of findShadows(JqlQuery).

        Using Stream.skip(long) and Stream.limit(long) is the only correct way for paging Shadows.
        Type Parameters:
        T - type of a domain object
        Returns:
        A stream of Shadows, ordered in reverse chronological order. Terminated stream if nothing found.
        Since:
        3.10
        See Also:
        findShadows(JqlQuery)
      • findChanges

        Changes findChanges​(JqlQuery query)
        Queries a JaversRepository for change history (diff sequence) of a given class, object or property.
        Returns the list of Changes.
        There are various types of changes. See Change class hierarchy.
        Changes can be easily traversed using Changes.groupByCommit() and Changes.groupByObject().

        Querying for Entity changes by instance Id

        For example, to get change history of last 5 versions of "bob" Person, call:
         javers.findChanges( QueryBuilder.byInstanceId("bob", Person.class).limit(5).build() );
         
        Last "salary" changes of "bob" Person:
         javers.findChanges( QueryBuilder.byInstanceId("bob", Person.class).withChangedProperty("salary").build() );
         
        Querying for ValueObject changes

        Last changes on Address ValueObject owned by "bob" Person:
         javers.findChanges( QueryBuilder.byValueObjectId("bob", Person.class, "address").build() );
         
        Last changes on Address ValueObject owned by any Person:
         javers.findChanges( QueryBuilder.byValueObject(Person.class, "address").build() );
         
        Last changes on nested ValueObject (when Address is a ValueObject nested in PersonDetails ValueObject):
         javers.findChanges( QueryBuilder.byValueObject(Person.class, "personDetails/address").build() );
         
        Querying for any object changes by its class

        Last changes on any object of MyClass.class:
         javers.findChanges( QueryBuilder.byClass(MyClass.class).build() );
         
        Last "myProperty" changes on any object of MyClass.class:
         javers.findChanges( QueryBuilder.byClass(Person.class).withChangedProperty("myProperty").build() );
         
        Returns:
        A list of Changes ordered in reverse chronological order. Empty if nothing found.
        See Also:
        http://javers.org/documentation/jql-examples
      • findSnapshots

        java.util.List<CdoSnapshot> findSnapshots​(JqlQuery query)
        Queries JaversRepository for object Snapshots.
        Snapshot is a historical state of a domain object captured as the property->value Map.

        For example, to get latest Snapshots of "bob" Person, call:
         javers.findSnapshots( QueryBuilder.byInstanceId("bob", Person.class).limit(5).build() );
         
        For more query examples, see findChanges(JqlQuery) method.
        Use the same JqlQuery to get changes, snapshots and shadows views.
        Returns:
        A list ordered in reverse chronological order. Empty if nothing found.
        See Also:
        http://javers.org/documentation/jql-examples
      • getLatestSnapshot

        java.util.Optional<CdoSnapshot> getLatestSnapshot​(java.lang.Object localId,
                                                          java.lang.Class entity)
        Latest snapshot of a given Entity instance.

        For example, to get the current state of Bob, call:
         javers.getLatestSnapshot("bob", Person.class);
         
        Returns Optional#EMPTY if an instance is not versioned.
      • getHistoricalSnapshot

        java.util.Optional<CdoSnapshot> getHistoricalSnapshot​(java.lang.Object localId,
                                                              java.lang.Class entity,
                                                              java.time.LocalDateTime effectiveDate)
        Historical snapshot of a given Entity instance.

        For example, to get the historical state of Bob at 2017-01-01, call:
         javers.getHistoricalSnapshot("bob", Person.class, LocalDateTime.of(2017,01,01));
         
        Returns Optional#EMPTY if an instance is not versioned.
        Since:
        3.4
      • getJsonConverter

        JsonConverter getJsonConverter()
        If you are serializing JaVers objects like Commit, Change, Diff or CdoSnapshot to JSON, use this JsonConverter.

        For example:
         javers.getJsonConverter().toJson(changes);
         
      • processChangeList

        <T> T processChangeList​(java.util.List<Change> changes,
                                ChangeProcessor<T> changeProcessor)
        Generic purpose method for processing a changes list. After iterating over given list, returns data computed by ChangeProcessor.result().
        It's more convenient than iterating over changes on your own. ChangeProcessor frees you from if + inctanceof boilerplate.

        Additional features:
        - when several changes in a row refers to the same Commit, ChangeProcessor.onCommit(CommitMetadata) is called only for first occurrence
        - similarly, when several changes in a row affects the same object, ChangeProcessor.onAffectedObject(GlobalId) is called only for first occurrence

        For example, to get pretty change log, call:
         List<Change> changes = javers.calculateDiffs(...);
         String changeLog = javers.processChangeList(changes, new SimpleTextChangeLog());
         System.out.println( changeLog );
         
        See Also:
        SimpleTextChangeLog
      • getTypeMapping

        <T extends JaversType> T getTypeMapping​(java.lang.reflect.Type userType)
        Use JaversTypes, if you want to:
        - describe your class in the context of JaVers domain model mapping,
        - use JaVers Reflection API to conveniently access your object properties (instead of awkward java.lang.reflect API).

        Class describe example. You can pretty-print JaversType of your class and check if mapping is correct.
         class Person {
             @Id int id;
             @Transient String notImportantField;
             String name;
         }
         
        Calling
         System.out.println( javers.getTypeMapping(Person.class).prettyPrint() );
         
        prints:
         EntityType{
           baseType: org.javers.core.examples.Person
           managedProperties:
              Field int id; //declared in: Person
              Field String name; //declared in: Person
           idProperty: login
         }
         
        Property access example. You can list object property values using Property abstraction.
         Javers javers = JaversBuilder.javers().build();
         ManagedType jType = javers.getTypeMapping(Person.class);
         Person person = new Person("bob", "Uncle Bob");
        
         System.out.println("Bob's properties:");
         for (Property p : jType.getPropertyNames()){
             Object value = p.get(person);
             System.out.println( "property:" + p.getName() + ", value:" + value );
         }
         
        prints:
         Bob's properties:
         property:login, value:bob
         property:name, value:Uncle Bob