diff --git a/src/main/asciidoc/reference/aerospike-object-mapping.adoc b/src/main/asciidoc/reference/aerospike-object-mapping.adoc index 2c4038154..1142bd7dd 100644 --- a/src/main/asciidoc/reference/aerospike-object-mapping.adoc +++ b/src/main/asciidoc/reference/aerospike-object-mapping.adoc @@ -58,10 +58,18 @@ NOTE: AbstractAerospikeConfiguration will create an AerospikeTemplate instance a [[mapping-usage-annotations]] === Mapping Annotation Overview -The MappingAerospikeConverter can use metadata to drive the mapping of objects to documents. An overview of the annotations is provided below +The MappingAerospikeConverter can use metadata to drive the mapping of objects to documents using annotations. An overview of the annotations is provided below * `@Id` - applied at the field level to mark the field used for identity purposes. * `@Field` - applied at the field level, describes the name of the field as it will be represented in the AerospikeDB BSON document thus allowing the name to be different from the field name of the class. +* `@Version` - applied at the field level to mark record modification count. The value must be effectively integer. +In Spring Data Aerospike, documents come in two forms – non-versioned and versioned. +Documents with an `@Version` annotation have a version field populated by the corresponding record’s generation count. +Version can be passed to a constructor or not (in that case it stays equal to zero). +* `@Expiration` - applied at the field level to mark a property to be used as expiration field. +Expiration can be specified in two flavors: as an offset in seconds from the current time (then field value must be +effectively integer) or as an absolute Unix timestamp. Client system time must be synchronized +with Aerospike server system time, otherwise expiration behaviour will be unpredictable. The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology-agnostic. Specific subclasses are used in the AerospikeDB support to support annotation-based metadata. Other strategies are also possible to put in place if there is demand. @@ -89,17 +97,20 @@ public class Person { private T address; + @Version + private int id; // must be integer public Person(Integer ssn) { this.ssn = ssn; } - public Person(Integer ssn, String firstName, String lastName, Integer age, T address) { + public Person(Integer ssn, String firstName, String lastName, Integer age, T address, int version) { this.ssn = ssn; this.firstName = firstName; this.lastName = lastName; this.age = age; this.address = address; + this.version = version; } public String getId() { diff --git a/src/main/java/org/springframework/data/aerospike/annotation/Expiration.java b/src/main/java/org/springframework/data/aerospike/annotation/Expiration.java index 967989783..c4a5f7bc7 100644 --- a/src/main/java/org/springframework/data/aerospike/annotation/Expiration.java +++ b/src/main/java/org/springframework/data/aerospike/annotation/Expiration.java @@ -23,8 +23,8 @@ /** * Demarcates a property to be used as expiration field. Expiration can be specified in two flavors: as an offset in - * seconds from the current time or as an absolute Unix time stamp.

Client system time must be synchronized - * with aerospike server system time, otherwise expiration behaviour will be unpredictable. + * seconds from the current time or as an absolute Unix timestamp.

Client system time must be synchronized + * with Aerospike server system time, otherwise expiration behaviour will be unpredictable. */ @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/org/springframework/data/aerospike/convert/MappingAerospikeWriteConverter.java b/src/main/java/org/springframework/data/aerospike/convert/MappingAerospikeWriteConverter.java index 838ee7a6c..f97075b4b 100644 --- a/src/main/java/org/springframework/data/aerospike/convert/MappingAerospikeWriteConverter.java +++ b/src/main/java/org/springframework/data/aerospike/convert/MappingAerospikeWriteConverter.java @@ -95,6 +95,7 @@ public void write(Object source, final AerospikeWriteData data) { AerospikePersistentProperty versionProperty = entity.getVersionProperty(); if (versionProperty != null) { + // version is read as Integer because Java client's Record.generation is integer Integer version = accessor.getProperty(versionProperty, Integer.class); data.setVersion(version); } diff --git a/src/test/java/org/springframework/data/aerospike/convert/MappingAerospikeConverterTests.java b/src/test/java/org/springframework/data/aerospike/convert/MappingAerospikeConverterTests.java index 99f89c514..f38cc630a 100644 --- a/src/test/java/org/springframework/data/aerospike/convert/MappingAerospikeConverterTests.java +++ b/src/test/java/org/springframework/data/aerospike/convert/MappingAerospikeConverterTests.java @@ -376,7 +376,7 @@ public void shouldReadExpirationForDocumentWithPersistenceConstructor(int conver DocumentWithExpirationAnnotationAndPersistenceConstructor document = aerospikeConverter.read(DocumentWithExpirationAnnotationAndPersistenceConstructor.class, forRead); - assertThat(document.getExpiration()).isCloseTo(TimeUnit.MINUTES.toSeconds(1), Offset.offset(100L)); + assertThat(document.getExpiration()).isCloseTo((int) TimeUnit.MINUTES.toSeconds(1), Offset.offset(100)); } @ParameterizedTest() @@ -384,7 +384,7 @@ public void shouldReadExpirationForDocumentWithPersistenceConstructor(int conver public void shouldNotWriteVersionToBins(int converterOption) { MappingAerospikeConverter aerospikeConverter = getAerospikeMappingConverterByOption(converterOption); AerospikeWriteData forWrite = AerospikeWriteData.forWrite(NAMESPACE); - aerospikeConverter.write(new VersionedClass("id", "data", 42L), forWrite); + aerospikeConverter.write(new VersionedClass("id", "data", 42), forWrite); assertThat(forWrite.getBins()).containsOnly( new Bin("@_class", VersionedClass.class.getName()), diff --git a/src/test/java/org/springframework/data/aerospike/convert/MappingAerospikeConverterTypesTests.java b/src/test/java/org/springframework/data/aerospike/convert/MappingAerospikeConverterTypesTests.java index 67bd43c44..c35bbd391 100644 --- a/src/test/java/org/springframework/data/aerospike/convert/MappingAerospikeConverterTypesTests.java +++ b/src/test/java/org/springframework/data/aerospike/convert/MappingAerospikeConverterTypesTests.java @@ -763,7 +763,7 @@ void shouldWriteAndReadNestedPOJOs() { new Address(new Street("Street1", 1), 1), new Address(new Street("Street2", 2), 2) ); - SampleClasses.idAndAddressesList testObj = new idAndAddressesList("testId", addressesList); + IdAndAddressesList testObj = new IdAndAddressesList("testId", addressesList); converter.write(testObj, forWrite); assertThat(forWrite.getBins()).containsOnly( @@ -774,9 +774,9 @@ void shouldWriteAndReadNestedPOJOs() { ); AerospikeReadData forRead = AerospikeReadData.forRead(forWrite.getKey(), aeroRecord(forWrite.getBins())); - SampleClasses.idAndAddressesList actual = converter.read(idAndAddressesList.class, forRead); + IdAndAddressesList actual = converter.read(IdAndAddressesList.class, forRead); - assertThat(actual).isEqualTo(new idAndAddressesList("testId", addressesList)); + assertThat(actual).isEqualTo(new IdAndAddressesList("testId", addressesList)); } private void assertWriteAndRead(int converterOption, diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeExpirationTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeExpirationTests.java index f53122b86..5cbfd5a3b 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeExpirationTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeExpirationTests.java @@ -153,12 +153,12 @@ public void shouldExpire() { @Test public void shouldSaveAndGetDocumentWithImmutableExpiration() { - template.insert(new DocumentWithExpirationAnnotationAndPersistenceConstructor(id, 60L)); + template.insert(new DocumentWithExpirationAnnotationAndPersistenceConstructor(id, 60)); DocumentWithExpirationAnnotationAndPersistenceConstructor doc = template.findById(id, DocumentWithExpirationAnnotationAndPersistenceConstructor.class); assertThat(doc).isNotNull(); - assertThat(doc.getExpiration()).isCloseTo(60L, Offset.offset(10L)); + assertThat(doc.getExpiration()).isCloseTo(60, Offset.offset(10)); } @Test diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByIdTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByIdTests.java index 5fd524318..6c4b2d59d 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByIdTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateFindByIdTests.java @@ -43,7 +43,7 @@ public class AerospikeTemplateFindByIdTests extends BaseBlockingIntegrationTests @Test public void findById_shouldReadVersionedClassWithAllArgsConstructor() { - VersionedClassWithAllArgsConstructor inserted = new VersionedClassWithAllArgsConstructor(id, "foobar", 0L); + VersionedClassWithAllArgsConstructor inserted = new VersionedClassWithAllArgsConstructor(id, "foobar", 0); template.insert(inserted); assertThat(template.findById(id, VersionedClassWithAllArgsConstructor.class).getVersion()).isEqualTo(1L); template.update(new VersionedClassWithAllArgsConstructor(id, "foobar1", inserted.getVersion())); @@ -54,7 +54,7 @@ public void findById_shouldReadVersionedClassWithAllArgsConstructor() { @Test public void findById_shouldReadVersionedClassWithAllArgsConstructorAndSetName() { - VersionedClassWithAllArgsConstructor inserted = new VersionedClassWithAllArgsConstructor(id, "foobar", 0L); + VersionedClassWithAllArgsConstructor inserted = new VersionedClassWithAllArgsConstructor(id, "foobar", 0); template.insert(inserted, OVERRIDE_SET_NAME); assertThat(template.findById(id, VersionedClassWithAllArgsConstructor.class, OVERRIDE_SET_NAME) .getVersion()).isEqualTo(1L); diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateInsertTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateInsertTests.java index c2279c906..59245cd94 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateInsertTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateInsertTests.java @@ -18,18 +18,23 @@ import com.aerospike.client.Key; import com.aerospike.client.Record; import com.aerospike.client.policy.Policy; +import lombok.Data; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.springframework.core.convert.ConversionFailedException; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.aerospike.BaseBlockingIntegrationTests; +import org.springframework.data.aerospike.mapping.Document; import org.springframework.data.aerospike.sample.Person; import org.springframework.data.aerospike.sample.SampleClasses.CustomCollectionClass; import org.springframework.data.aerospike.sample.SampleClasses.DocumentWithByteArray; import org.springframework.data.aerospike.util.AsyncUtils; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Version; import java.util.Arrays; import java.util.Collections; @@ -142,7 +147,7 @@ public void insertsDocumentWithZeroVersionIfThereIsNoDocumentWithSameKey() { @Test public void insertsDocumentWithVersionGreaterThanZeroIfThereIsNoDocumentWithSameKey() { - VersionedClass document = new VersionedClass(id, "any", 5L); + VersionedClass document = new VersionedClass(id, "any", 5); // initially given versions are ignored // RecordExistsAction.CREATE_ONLY is used template.insert(document); @@ -150,6 +155,32 @@ public void insertsDocumentWithVersionGreaterThanZeroIfThereIsNoDocumentWithSame assertThat(document.getVersion()).isEqualTo(1); } + + @Data + @Document(collection = "versioned-set") + public static class ClassWithLongVersion { + + @Version + private long version; // must be integer + private String field; + @Id + private String id; + + public ClassWithLongVersion(String id, String field, long version) { + this.id = id; + this.field = field; + this.version = version; + } + } + + @Test + public void mustUseIntegerVersion() { + ClassWithLongVersion document = new ClassWithLongVersion(id, "any", Long.MAX_VALUE); + assertThatThrownBy(() -> template.insert(document)) + .isInstanceOf(ConversionFailedException.class) + .hasMessageContaining("Failed to convert from type [java.lang.Long] to type [java.lang.Integer]"); + } + @Test public void throwsExceptionForDuplicateId() { Person person = new Person(id, "Amol", 28); @@ -161,7 +192,7 @@ public void throwsExceptionForDuplicateId() { @Test public void throwsExceptionForDuplicateIdForVersionedDocument() { - VersionedClass document = new VersionedClass(id, "any", 5L); + VersionedClass document = new VersionedClass(id, "any", 5); template.insert(document); assertThatThrownBy(() -> template.insert(document)) @@ -275,8 +306,8 @@ public void shouldInsertAllVersionedDocuments() { // batch write operations are supported starting with Server version 6.0+ if (serverVersionSupport.isBatchWriteSupported()) { VersionedClass first = new VersionedClass(id, "foo"); - VersionedClass second = new VersionedClass(nextId(), "foo", 1L); - VersionedClass third = new VersionedClass(nextId(), "foo", 2L); + VersionedClass second = new VersionedClass(nextId(), "foo", 1); + VersionedClass third = new VersionedClass(nextId(), "foo", 2); // initially given versions are ignored // RecordExistsAction.CREATE_ONLY is used diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateSaveTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateSaveTests.java index 90489a0a7..19b2fc39f 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateSaveTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateSaveTests.java @@ -62,7 +62,7 @@ public void shouldReplaceAllBinsPresentInAerospikeWhenSavingDocument() { Key key = new Key(getNameSpace(), template.getSetName(VersionedClass.class), id); additionalAerospikeTestOperations.addNewFieldToSavedDataInAerospike(key); - template.save(new VersionedClass(id, "foo2", 2L)); + template.save(new VersionedClass(id, "foo2", 2)); Record aeroRecord = client.get(new Policy(), key); assertThat(aeroRecord.bins.get("notPresent")).isNull(); @@ -126,9 +126,9 @@ public void shouldUpdateNotVersionedDocumentIfItAlreadyExists() { @Test public void shouldSaveDocumentWithEqualVersion() { // if an object has version property, GenerationPolicy.EXPECT_GEN_EQUAL is used - VersionedClass first = new VersionedClass(id, "foo", 0L); - VersionedClass second = new VersionedClass(id, "foo", 1L); - VersionedClass third = new VersionedClass(id, "foo", 2L); + VersionedClass first = new VersionedClass(id, "foo", 0); + VersionedClass second = new VersionedClass(id, "foo", 1); + VersionedClass third = new VersionedClass(id, "foo", 2); template.save(first); template.save(second); @@ -141,7 +141,7 @@ public void shouldSaveDocumentWithEqualVersion() { @Test public void shouldFailSaveNewDocumentWithVersionGreaterThanZero() { - assertThatThrownBy(() -> template.save(new VersionedClass(nextId(), "foo", 5L))) + assertThatThrownBy(() -> template.save(new VersionedClass(nextId(), "foo", 5))) .isInstanceOf(OptimisticLockingFailureException.class); } diff --git a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateUpdateTests.java b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateUpdateTests.java index 863b026ae..c17f6d776 100644 --- a/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateUpdateTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/AerospikeTemplateUpdateTests.java @@ -496,8 +496,8 @@ public void updateAllIfDocumentsChanged() { VersionedClass first = new VersionedClass("id1", "foo"); // In this case non-zero versions get explicitly passed to the constructor - VersionedClass second = new VersionedClass("id2", "foo", 1L); - VersionedClass third = new VersionedClass("id3", "foo", 2L); + VersionedClass second = new VersionedClass("id2", "foo", 1); + VersionedClass third = new VersionedClass("id3", "foo", 2); // Insert multiple versioned documents to create new DB records template.insertAll(List.of(first, second, third)); diff --git a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateInsertTests.java b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateInsertTests.java index 3e5e3e67e..445c6856d 100644 --- a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateInsertTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateInsertTests.java @@ -140,7 +140,7 @@ public void insertsDocumentWithZeroVersionIfThereIsNoDocumentWithSameKey() { @Test public void insertsDocumentWithVersionGreaterThanZeroIfThereIsNoDocumentWithSameKey() { - VersionedClass document = new VersionedClass(id, "any", 5L); + VersionedClass document = new VersionedClass(id, "any", 5); reactiveTemplate.insert(document).subscribeOn(Schedulers.parallel()).block(); assertThat(document.getVersion()).isEqualTo(1); @@ -148,7 +148,7 @@ public void insertsDocumentWithVersionGreaterThanZeroIfThereIsNoDocumentWithSame @Test public void insertsDocumentWithVersionGreaterThanZeroIfThereIsNoDocumentWithSameKeyAndSetName() { - VersionedClass document = new VersionedClass(id, "any", 5L); + VersionedClass document = new VersionedClass(id, "any", 5); reactiveTemplate.insert(document, OVERRIDE_SET_NAME).subscribeOn(Schedulers.parallel()).block(); assertThat(document.getVersion()).isEqualTo(1); @@ -177,7 +177,7 @@ public void throwsExceptionForDuplicateIdAndSetName() { @Test public void throwsExceptionForDuplicateIdForVersionedDocument() { - VersionedClass document = new VersionedClass(id, "any", 5L); + VersionedClass document = new VersionedClass(id, "any", 5); reactiveTemplate.insert(document).subscribeOn(Schedulers.parallel()).block(); StepVerifier.create(reactiveTemplate.insert(document).subscribeOn(Schedulers.parallel())) diff --git a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateSaveRelatedTests.java b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateSaveRelatedTests.java index bf0b4f36e..6a6040179 100644 --- a/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateSaveRelatedTests.java +++ b/src/test/java/org/springframework/data/aerospike/core/reactive/ReactiveAerospikeTemplateSaveRelatedTests.java @@ -48,10 +48,10 @@ public void saveWithSetName_shouldSaveAndSetVersion() { @Test public void save_shouldNotSaveDocumentIfItAlreadyExistsWithZeroVersion() { - reactiveTemplate.save(new VersionedClass(id, "foo", 0L)) + reactiveTemplate.save(new VersionedClass(id, "foo", 0)) .subscribeOn(Schedulers.parallel()).block(); - StepVerifier.create(reactiveTemplate.save(new VersionedClass(id, "foo", 0L)) + StepVerifier.create(reactiveTemplate.save(new VersionedClass(id, "foo", 0)) .subscribeOn(Schedulers.parallel())) .expectError(OptimisticLockingFailureException.class) .verify(); @@ -61,13 +61,13 @@ public void save_shouldNotSaveDocumentIfItAlreadyExistsWithZeroVersion() { public void save_shouldSaveDocumentWithEqualVersion() { reactiveTemplate.save(new VersionedClass(id, "foo")).subscribeOn(Schedulers.parallel()).block(); - reactiveTemplate.save(new VersionedClass(id, "foo", 1L)).subscribeOn(Schedulers.parallel()).block(); - reactiveTemplate.save(new VersionedClass(id, "foo", 2L)).subscribeOn(Schedulers.parallel()).block(); + reactiveTemplate.save(new VersionedClass(id, "foo", 1)).subscribeOn(Schedulers.parallel()).block(); + reactiveTemplate.save(new VersionedClass(id, "foo", 2)).subscribeOn(Schedulers.parallel()).block(); } @Test public void save_shouldFailSaveNewDocumentWithVersionGreaterThanZero() { - StepVerifier.create(reactiveTemplate.save(new VersionedClass(id, "foo", 5L)) + StepVerifier.create(reactiveTemplate.save(new VersionedClass(id, "foo", 5)) .subscribeOn(Schedulers.parallel())) .expectError(OptimisticLockingFailureException.class) .verify(); @@ -234,7 +234,7 @@ public void save_shouldReplaceAllBinsPresentInAerospikeWhenSavingDocument() { reactiveTemplate.save(first).subscribeOn(Schedulers.parallel()).block(); additionalAerospikeTestOperations.addNewFieldToSavedDataInAerospike(key); - reactiveTemplate.save(new VersionedClass(id, "foo2", 2L)) + reactiveTemplate.save(new VersionedClass(id, "foo2", 2)) .subscribeOn(Schedulers.parallel()).block(); StepVerifier.create(reactorClient.get(new Policy(), key)) diff --git a/src/test/java/org/springframework/data/aerospike/sample/SampleClasses.java b/src/test/java/org/springframework/data/aerospike/sample/SampleClasses.java index f9f5c499c..dd19f00df 100644 --- a/src/test/java/org/springframework/data/aerospike/sample/SampleClasses.java +++ b/src/test/java/org/springframework/data/aerospike/sample/SampleClasses.java @@ -196,7 +196,7 @@ public static class DocumentExampleIdClass { DocumentExample id; long counter; @Version - long version; + int version; // the value must be effectively integer @Field("update") Long timestamp; } @@ -552,7 +552,7 @@ private Person(Set
addresses) { } @Data - public static class idAndAddressesList { + public static class IdAndAddressesList { @Id final String id; @@ -564,12 +564,12 @@ public static class idAndAddressesList { public static class VersionedClass { @Version - private long version; + private int version; // the value must be effectively integer private String field; @Id private String id; - public VersionedClass(String id, String field, long version) { + public VersionedClass(String id, String field, int version) { this.id = id; this.field = field; this.version = version; @@ -590,10 +590,10 @@ public static class VersionedClassWithAllArgsConstructor { @Id private String id; @Version - private long version; + private int version; // the value must be effectively integer @PersistenceCreator - public VersionedClassWithAllArgsConstructor(String id, String field, long version) { + public VersionedClassWithAllArgsConstructor(String id, String field, int version) { this.id = id; this.field = field; this.version = version; @@ -722,7 +722,7 @@ public static class DocumentWithTouchOnRead { private int field; @Version - private long version; + private int version; // the value must be effectively integer public DocumentWithTouchOnRead(String id) { this(id, 0); @@ -744,7 +744,7 @@ public static class DocumentWithExpirationAnnotation { private String id; @Expiration - private Integer expiration; + private Integer expiration; // the value must be effectively integer } @Value @@ -755,10 +755,10 @@ public static class DocumentWithExpirationAnnotationAndPersistenceConstructor { String id; @Expiration - Long expiration; + int expiration; // the value must be effectively integer @PersistenceCreator - public DocumentWithExpirationAnnotationAndPersistenceConstructor(String id, Long expiration) { + public DocumentWithExpirationAnnotationAndPersistenceConstructor(String id, int expiration) { this.id = id; this.expiration = expiration; } @@ -831,7 +831,7 @@ public static class DocumentWithExpirationOneDay { @Id private String id; @Version - private long version; + private long version; // the value must be effectively integer @PersistenceCreator public DocumentWithExpirationOneDay(String id) { @@ -847,7 +847,7 @@ public static class DocumentWithTouchOnReadAndExpirationProperty { @Id private String id; @Expiration - private long expiration; + private int expiration; // the value must be effectively integer } @Document(collection = DocumentWithExpressionInCollection.COLLECTION_PREFIX + "${setSuffix}") @@ -901,7 +901,7 @@ public static class DocumentWithBigIntegerAndNestedArray { public static class ObjectWithIntegerArray { @Version - private Long version; + private Integer version; // the value must be effectively integer Integer[] array; public ObjectWithIntegerArray(Integer[] array) {