DATACASS-576 - Add support for Optimistic Locking.

We now support Optimistic Locking for insert, update and delete operations leveraging Cassandra's lightweight transaction support. Modifying statements are enhanced with IF conditions to conditionally insert and modify rows and to prevent concurrent modifications by throwing OptimisticLockingFailureException.

@Table
class Person {

  @Id String id;
  String firstname;
  String lastname;
  @Version Long version;
}

Person daenerys = template.insert(new Person("Daenerys"));

Person tmp = template.findOne(query(where("id").is(daenerys.getId())), Person.class);

daenerys.setLastname("Targaryen");
template.save(daenerys);

template.save(tmp); // throws OptimisticLockingFailureException
This commit is contained in:
Mark Paluch
2019-01-30 21:11:01 +01:00
committed by John Blum
parent b01e1fc144
commit 7a8f91ade3
18 changed files with 1414 additions and 81 deletions

View File

@@ -10,6 +10,7 @@ This chapter summarizes changes and new features for each release.
* Kotlin Coroutine extensions for `ReactiveFluentCassandraOperations`.
* Lightweight transaction support via `DeleteOptions` using the Template API.
* Filter conditions for lightweight transaction update and delete (`UPDATE … IF <condition>`, `DELETE … IF <condition>`).
* Optimistic Locking support.
[[new-features.2-1-0]]
== What's new in Spring Data for Apache Cassandra 2.1

View File

@@ -1167,6 +1167,45 @@ You can use the following overloaded methods to remove an object from the databa
* `T` *delete* `(T entity, QueryOptions queryOptions)`: Deletes the given object applying `QueryOptions`.
* `boolean` *deleteById* `(Object id, Class<?> entityClass)`: Deletes the object using the given Id.
[[cassandra.template.optimistic-locking]]
=== Optimistic Locking
The `@Version` annotation provides syntax similar to that of JPA in the context of Cassandra and makes sure updates are only applied to rows with a matching version.
Optimistic Locking leverages Cassandra's lightweight transactions to conditionally insert, update and delete rows.
Therefore, `INSERT` statements are executed with the `IF NOT EXISTS` condition.
For updates and deletes, the actual value of the version property is added to the `UPDATE` condition in such a way that the modification does not have any effect if another operation altered the row in the meantime.
In that case, an `OptimisticLockingFailureException` is thrown.
The following example shows these features:
====
[source,java]
----
@Table
class Person {
@Id String id;
String firstname;
String lastname;
@Version Long version;
}
Person daenerys = template.insert(new Person("Daenerys")); <1>
Person tmp = template.findOne(query(where("id").is(daenerys.getId())), Person.class); <2>
daenerys.setLastname("Targaryen");
template.save(daenerys); <3>
template.save(tmp); // throws OptimisticLockingFailureException <4>
----
<1> Intially insert document. `version` is set to `0`.
<2> Load the just inserted document. `version` is still `0`.
<3> Update the document with `version = 0`. Set the `lastname` and bump `version` to `1`.
<4> Try to update the previously loaded document that still has `version = 0`. The operation fails with an `OptimisticLockingFailureException`, as the current `version` is `1`.
====
NOTE: Optimistic Locking is only supported with single-entity operations and not for batch operations.
[[cassandra.template.query]]
== Querying Rows

View File

@@ -427,6 +427,7 @@ Types are derived from the declaration by default.
* `@Tuple`: Applied at the type level to use a type as a mapped tuple.
* `@Element`: Applied at the field level to specify element or field ordinals within a mapped tuple.
Types are derived from the property declaration by default.
* `@Version`: Applied at field level is used for optimistic locking and checked for modification on save operations. The initial value is `zero` which is bumped automatically on every update.
The mapping metadata infrastructure is defined in the separate, spring-data-commons project that is both
technology- and data store-agnostic.