DATACOUCH-212 - Take Version/CAS into account on save

This change takes the `Version` annotated field into account when performing a `save` (either in the `CouchbaseTemplate` or transitively in a `CouchbaseRepository`).

Having such a field with a non-zero value means that a CAS is available, activating optimistic locking. Version 1.4 and below of Spring Data Couchbase was taking this CAS into account, whereas in 2.0 we switched to using `upsert` internally, which ignores the CAS.

This change brings the behavior of `save` (when there's a non-zero CAS) closer to what was observed in 1.4 versions: an `OptimisticLockingFailureException` can now be raised in case of CAS mismatch.
This commit is contained in:
Simon Baslé
2016-03-10 22:06:05 +01:00
parent 39fb52d2a6
commit 4aa823722a
3 changed files with 141 additions and 25 deletions

View File

@@ -22,6 +22,8 @@ import java.util.Arrays;
import java.util.List;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.document.JsonDocument;
import com.couchbase.client.java.error.CASMismatchException;
import com.couchbase.client.java.view.Stale;
import com.couchbase.client.java.view.ViewQuery;
import org.junit.Before;
@@ -30,8 +32,12 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.couchbase.repository.support.IndexManager;
@@ -58,11 +64,13 @@ public class SimpleCouchbaseRepositoryTests {
private IndexManager indexManager;
private UserRepository repository;
private VersionedDataRepository versionedDataRepository;
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
repository = factory.getRepository(UserRepository.class);
versionedDataRepository = factory.getRepository(VersionedDataRepository.class);
}
@Test
@@ -180,4 +188,85 @@ public class SimpleCouchbaseRepositoryTests {
}
}
}
@Test
public void shouldTakeVersionIntoAccountWhenDoingMultipleUpdates() {
final String key = "versionedUserTest";
VersionedData initial = new VersionedData(key, "ABCD");
versionedDataRepository.save(initial);
assertNotEquals(0L, initial.version);
VersionedData fetch1 = versionedDataRepository.findOne(key);
assertNotSame(initial, fetch1);
assertEquals(fetch1.version, initial.version);
JsonDocument bypass = client.get(key);
bypass.content().put("data", "BBBB");
JsonDocument bypassed = client.upsert(bypass);
assertNotEquals(bypassed.cas(), fetch1.version);
System.out.println(bypassed.cas());
try {
fetch1.setData("ZZZZ");
versionedDataRepository.save(fetch1);
fail("Expected CAS failure");
} catch (OptimisticLockingFailureException e) {
//success
assertTrue("optimistic locking should have CASMismatchException as cause, got " + e.getCause(),
e.getCause() instanceof CASMismatchException);
} finally {
client.remove(key);
}
}
public interface VersionedDataRepository extends CouchbaseRepository<VersionedData, String> { }
@Document
public static class VersionedData {
@Id
private final String key;
@Version
public long version = 0L;
private String data;
public VersionedData(String key, String data) {
this.key = key;
this.data = data;
}
public String getKey() {
return key;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public String toString() {
return this.key + " " + this.data;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VersionedData vd = (VersionedData) o;
return key.equals(vd.key);
}
@Override
public int hashCode() {
return key.hashCode();
}
}
}