Interface Javers


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

    For example, to deeply compare two objects or two arbitrary complex object graphs, 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 seamlessly 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 a JaversRepository for Shadows.
        Shadows are historical version of domain objects which are restored from persisted snapshots.

        For example, to get latest 5 Shadows of "bob" Person, call:

         List<Shadow> 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;
         

        Paging & limit

        Use QueryBuilder.skip(int) and QueryBuilder.limit(int) for paging Shadows.
        An underlying Snapshots query uses its own limit — QueryBuilder.snapshotQueryLimit(Integer).
        Since one Shadow might be reconstructed from many Snapshots, when snapshotQueryLimit() is hit,
        Javers repeats a given Shadow query to load a next frame of Shadows until required limit is reached.

        Returned list of Shadow graphs is always complete (according to the selected ShadowScope)
        but the whole operation can trigger a few DB queries.

        Query scopes

        By default, Shadow 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 (see ShadowScope).

        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, let's say 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 as in the first example,
        but 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 DB queries (Snapshot queries).
        The number of executed DB queries depends on: selected ShadowScope,
        complexity of your object graphs, and obviously on Stream.limit(long).

        If you are having performance issues, start from checking execution statistics of your query, available in JqlQuery.streamStats(). Also, the stats are printed in JqlQuery.toString(), for example:

         def query = QueryBuilder.byInstanceId(1, Entity).withScopeDeepPlus(1).build()
         def shadows = javers.findShadows(query)
         println 'executed query: ' + query
         
        Output:
         executed query: JqlQuery {
           IdFilterDefinition{ globalId: 'org.javers.core.examples.JqlExample$Entity/1' }
           QueryParams{ aggregate: 'true', limit: '100' }
           shadowScope: DEEP_PLUS
           ShadowStreamStats{
             executed in millis: '7'
             DB queries: '2'
             snapshots loaded: '2'
             SHALLOW snapshots: '1'
             DEEP_PLUS snapshots: '1'
             gaps filled: '1'
             gaps left!: '1'
             Shadow stream frame queries: '1'
           }
         }
         
        For quick win — try to reduce the ShadowScope.

        More detailed stats can be obtained by setting the org.javers.JQL logger to DEBUG:

        <logger name="org.javers.JQL" level="DEBUG"/>
        
        Type Parameters:
        T - type of a domain object
        Returns:
        Returns a list of latest Shadows ordered in reverse chronological order The size of the list is limited by QueryBuilder.limit(int).
        See Also:
        ShadowScope, findShadows(JqlQuery)
      • findShadowsAndStream

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

        The main difference is that the returned stream is lazy loaded and subsequent frame queries are executed gradually, during the stream consumption.
        Type Parameters:
        T - type of a domain object
        Returns:
        A lazy loaded stream of latest Shadows ordered in reverse chronological order. Terminated stream if nothing found. The size of the stream is limited by QueryBuilder.limit(int).
        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