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

@@ -34,6 +34,8 @@ import com.couchbase.client.java.document.Document;
import com.couchbase.client.java.document.RawJsonDocument;
import com.couchbase.client.java.document.json.JsonObject;
import com.couchbase.client.java.error.CASMismatchException;
import com.couchbase.client.java.error.DocumentAlreadyExistsException;
import com.couchbase.client.java.error.DocumentDoesNotExistException;
import com.couchbase.client.java.error.TranscodingException;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.query.N1qlQueryResult;
@@ -220,7 +222,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
@Override
public void save(Object objectToSave, PersistTo persistTo, ReplicateTo replicateTo) {
doPersist(objectToSave, persistTo, replicateTo, false, false);
doPersist(objectToSave, persistTo, replicateTo, PersistType.SAVE);
}
@Override
@@ -231,7 +233,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
@Override
public void save(Collection<?> batchToSave, PersistTo persistTo, ReplicateTo replicateTo) {
for (Object o : batchToSave) {
doPersist(o, persistTo, replicateTo, false, false);
doPersist(o, persistTo, replicateTo, PersistType.SAVE);
}
}
@@ -242,7 +244,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
@Override
public void insert(Object objectToInsert, PersistTo persistTo, ReplicateTo replicateTo) {
doPersist(objectToInsert, persistTo, replicateTo, true, false);
doPersist(objectToInsert, persistTo, replicateTo, PersistType.INSERT);
}
@Override
@@ -253,7 +255,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
@Override
public void insert(Collection<?> batchToInsert, PersistTo persistTo, ReplicateTo replicateTo) {
for (Object o : batchToInsert) {
doPersist(o, persistTo, replicateTo, true, false);
doPersist(o, persistTo, replicateTo, PersistType.INSERT);
}
}
@@ -264,7 +266,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
@Override
public void update(Object objectToUpdate, PersistTo persistTo, ReplicateTo replicateTo) {
doPersist(objectToUpdate, persistTo, replicateTo, false, true);
doPersist(objectToUpdate, persistTo, replicateTo, PersistType.UPDATE);
}
@Override
@@ -275,7 +277,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
@Override
public void update(Collection<?> batchToUpdate, PersistTo persistTo, ReplicateTo replicateTo) {
for (Object o : batchToUpdate) {
doPersist(o, persistTo, replicateTo, false, true);
doPersist(o, persistTo, replicateTo, PersistType.UPDATE);
}
}
@@ -509,11 +511,9 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
}
private void doPersist(Object objectToPersist, final PersistTo persistTo, final ReplicateTo replicateTo,
final boolean failOnExist, final boolean failOnMissing) {
final PersistType persistType) {
ensureNotIterable(objectToPersist);
final String operationDesc = failOnExist ? "Insert" : failOnMissing ? "Update" : "Upsert";
final ConvertingPropertyAccessor accessor = getPropertyAccessor(objectToPersist);
final CouchbasePersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(objectToPersist.getClass());
final CouchbasePersistentProperty versionProperty = persistentEntity.getVersionProperty();
@@ -529,15 +529,23 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
public Boolean doInBucket() throws InterruptedException, ExecutionException {
Document<String> doc = encodeAndWrap(converted, version);
Document<String> storedDoc;
boolean checkVersion = version != null && version > 0L;
try {
if (!failOnExist && !failOnMissing) {
storedDoc = client.upsert(doc, persistTo, replicateTo);
}
else if (failOnMissing) {
storedDoc = client.replace(doc, persistTo, replicateTo);
}
else {
storedDoc = client.insert(doc, persistTo, replicateTo);
switch (persistType) {
case SAVE:
if (checkVersion) {
storedDoc = client.replace(doc, persistTo, replicateTo);
} else {
storedDoc = client.upsert(doc, persistTo, replicateTo);
}
break;
case UPDATE:
storedDoc = client.replace(doc, persistTo, replicateTo);
break;
case INSERT:
default:
storedDoc = client.insert(doc, persistTo, replicateTo);
break;
}
if (persistentEntity.hasVersionProperty() && storedDoc != null && storedDoc.cas() != 0) {
@@ -546,13 +554,11 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
return true;
}
return false;
}
catch (CASMismatchException e) {
throw new OptimisticLockingFailureException(operationDesc +
" document with version value failed: " + version);
}
catch (Exception e) {
handleWriteResultError(operationDesc + " document failed: " + e.getMessage(), e);
} catch (CASMismatchException e) {
throw new OptimisticLockingFailureException(persistType.getSpringDataOperationName() +
" document with version value failed: " + version, e);
} catch (Exception e) {
handleWriteResultError(persistType.getSpringDataOperationName() + " document failed: " + e.getMessage(), e);
return false; //this could be skipped if WriteResultChecking.EXCEPTION
}
}
@@ -645,4 +651,26 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
public void setDefaultConsistency(Consistency consistency) {
this.configuredConsistency = consistency;
}
private enum PersistType {
SAVE("Save", "Upsert"),
INSERT("Insert", "Insert"),
UPDATE("Update", "Replace");
private final String sdkOperationName;
private final String springDataOperationName;
PersistType(String sdkOperationName, String springDataOperationName) {
this.sdkOperationName = sdkOperationName;
this.springDataOperationName = springDataOperationName;
}
public String getSdkOperationName() {
return sdkOperationName;
}
public String getSpringDataOperationName() {
return springDataOperationName;
}
}
}

View File

@@ -82,7 +82,6 @@ public class SimpleCouchbaseRepository<T, ID extends Serializable> implements Co
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null!");
couchbaseOperations.save(entity);
return entity;
}