DATACASS-250 - Support lightweight transactions.

We now support lightweight transactions via InsertOptions and UpdateOptions. Options can be used with the imperative, asynchronous and reactive templates using insert(…) and update(…) write methods. Write methods do not return the entity if the operation is not applied because of a lightweight transaction. Specifically, the resulting entity is null using the imperative and asynchronous Cassandra templates.

The reactive Cassandra template suppresses particular entities (inserted/updated through a entity stream) if its write operation was not applied due to a lightweight transaction.

InsertOptions lwtInsertOptions = InsertOptions.builder().withIfNotExists().build();

User user = new User("heisenberg", "Walter", "White");
User inserted = template.insert(user, lwtInsertOptions);

UpdateOptions lwtUpdateOptions = UpdateOptions.builder().withIfExists().build();

User user = new User("heisenberg", "Walter", "White");
User updated = template.update(user, lwtUpdateOptions);
This commit is contained in:
Mark Paluch
2017-06-09 14:33:15 +02:00
committed by John Blum
parent 4e9fd9ae0e
commit 8d64401aa8
20 changed files with 756 additions and 59 deletions

View File

@@ -37,7 +37,10 @@ import com.datastax.driver.core.Statement;
* @author John Blum
* @since 2.0
* @see AsyncCassandraTemplate
* @see CassandraOperations
* @see AsyncCqlOperations
* @see Statement
* @see InsertOptions
* @see UpdateOptions
*/
public interface AsyncCassandraOperations {
@@ -234,10 +237,11 @@ public interface AsyncCassandraOperations {
*
* @param entity The entity to insert, must not be {@literal null}.
* @param options may be {@literal null}.
* @return the inserted entity.
* @return the inserted entity or a {@literal null} inside of {@link ListenableFuture} if the {@code INSERT} operation
* was not applied.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> insert(T entity, WriteOptions options) throws DataAccessException;
<T> ListenableFuture<T> insert(T entity, InsertOptions options) throws DataAccessException;
/**
* Update the given entity and return the entity if the update was applied.
@@ -253,10 +257,11 @@ public interface AsyncCassandraOperations {
*
* @param entity The entity to update, must not be {@literal null}.
* @param options may be {@literal null}.
* @return the updated entity.
* @return the updated entityor a {@literal null} inside of {@link ListenableFuture} if the {@code UPDATE} operation
* was not applied.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> update(T entity, WriteOptions options) throws DataAccessException;
<T> ListenableFuture<T> update(T entity, UpdateOptions options) throws DataAccessException;
/**
* Delete the given entity and return the entity if the delete was applied.

View File

@@ -36,7 +36,6 @@ import org.springframework.data.cql.core.CqlIdentifier;
import org.springframework.data.cql.core.CqlProvider;
import org.springframework.data.cql.core.GuavaListenableFutureAdapter;
import org.springframework.data.cql.core.QueryOptions;
import org.springframework.data.cql.core.WriteOptions;
import org.springframework.data.cql.core.session.DefaultSessionFactory;
import org.springframework.data.cql.core.session.SessionFactory;
import org.springframework.data.cql.support.CqlExceptionTranslator;
@@ -424,10 +423,10 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations {
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#insert(java.lang.Object, org.springframework.data.cql.core.WriteOptions)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#insert(java.lang.Object, org.springframework.data.cassandra.core.InsertOptions)
*/
@Override
public <T> ListenableFuture<T> insert(T entity, WriteOptions options) {
public <T> ListenableFuture<T> insert(T entity, InsertOptions options) {
Assert.notNull(entity, "Entity must not be null");
@@ -448,10 +447,10 @@ public class AsyncCassandraTemplate implements AsyncCassandraOperations {
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#update(java.lang.Object, org.springframework.data.cql.core.WriteOptions)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#update(java.lang.Object, org.springframework.data.cassandra.core.UpdateOptions)
*/
@Override
public <T> ListenableFuture<T> update(T entity, WriteOptions options) {
public <T> ListenableFuture<T> update(T entity, UpdateOptions options) {
Assert.notNull(entity, "Entity must not be null");

View File

@@ -41,6 +41,8 @@ import com.datastax.driver.core.Statement;
* @see CassandraTemplate
* @see CqlOperations
* @see Statement
* @see InsertOptions
* @see UpdateOptions
*/
public interface CassandraOperations {
@@ -266,10 +268,10 @@ public interface CassandraOperations {
*
* @param entity The entity to insert, must not be {@literal null}.
* @param options may be {@literal null}.
* @return the inserted entity.
* @return the inserted entity or {@literal null} if the {@code INSERT} operation was not applied.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> T insert(T entity, WriteOptions options) throws DataAccessException;
<T> T insert(T entity, InsertOptions options) throws DataAccessException;
/**
* Update the given entity and return the entity if the update was applied.
@@ -285,10 +287,10 @@ public interface CassandraOperations {
*
* @param entity The entity to update, must not be {@literal null}.
* @param options may be {@literal null}.
* @return the updated entity.
* @return the updated entity or {@literal null} if the {@code UPDATE} operation was not applied.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> T update(T entity, WriteOptions options) throws DataAccessException;
<T> T update(T entity, UpdateOptions options) throws DataAccessException;
/**
* Delete the given entity and return the entity if the delete was applied.

View File

@@ -37,7 +37,6 @@ import org.springframework.data.cql.core.CqlProvider;
import org.springframework.data.cql.core.CqlTemplate;
import org.springframework.data.cql.core.QueryOptions;
import org.springframework.data.cql.core.SessionCallback;
import org.springframework.data.cql.core.WriteOptions;
import org.springframework.data.cql.core.session.DefaultSessionFactory;
import org.springframework.data.cql.core.session.SessionFactory;
import org.springframework.data.mapping.context.MappingContext;
@@ -433,10 +432,10 @@ public class CassandraTemplate implements CassandraOperations {
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object, org.springframework.data.cql.core.WriteOptions)
* @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object, org.springframework.data.cassandra.core.InsertOptions)
*/
@Override
public <T> T insert(T entity, WriteOptions options) {
public <T> T insert(T entity, InsertOptions options) {
Assert.notNull(entity, "Entity must not be null");
@@ -456,10 +455,10 @@ public class CassandraTemplate implements CassandraOperations {
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object, org.springframework.data.cql.core.WriteOptions)
* @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object, org.springframework.data.cassandra.core.UpdateOptions)
*/
@Override
public <T> T update(T entity, WriteOptions options) {
public <T> T update(T entity, UpdateOptions options) {
Assert.notNull(entity, "Entity must not be null");

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core;
import java.util.concurrent.TimeUnit;
import org.springframework.data.cql.core.WriteOptions;
/**
* Extension to {@link WriteOptions} for use with {@code INSERT} operations.
*
* @author Mark Paluch
* @since 2.0
*/
public class InsertOptions extends WriteOptions {
private boolean ifNotExists;
/**
* Creates new {@link InsertOptions}.
*/
InsertOptions() {}
/**
* Create a new {@link InsertOptionsBuilder}.
*
* @return a new {@link InsertOptionsBuilder}.
*/
public static InsertOptionsBuilder builder() {
return new InsertOptionsBuilder();
}
/**
* @return {@literal true} to apply {@code IF NOT EXISTS} to {@code INSERT} operations.
*/
public boolean isIfNotExists() {
return ifNotExists;
}
/**
* Builder for {@link InsertOptions}.
*
* @author Mark Paluch
* @since 2.0
*/
public static class InsertOptionsBuilder extends WriteOptionsBuilder {
private boolean ifNotExists;
private InsertOptionsBuilder() {}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#consistencyLevel(com.datastax.driver.core.ConsistencyLevel)
*/
@Override
public InsertOptionsBuilder consistencyLevel(com.datastax.driver.core.ConsistencyLevel consistencyLevel) {
return (InsertOptionsBuilder) super.consistencyLevel(consistencyLevel);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#retryPolicy(org.springframework.data.cql.core.RetryPolicy)
*/
@Override
public InsertOptionsBuilder retryPolicy(com.datastax.driver.core.policies.RetryPolicy driverRetryPolicy) {
return (InsertOptionsBuilder) super.retryPolicy(driverRetryPolicy);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#fetchSize(int)
*/
@Override
public InsertOptionsBuilder fetchSize(int fetchSize) {
return (InsertOptionsBuilder) super.fetchSize(fetchSize);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#readTimeout(long)
*/
@Override
public InsertOptionsBuilder readTimeout(long readTimeout) {
return (InsertOptionsBuilder) super.readTimeout(readTimeout);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#readTimeout(long, java.util.concurrent.TimeUnit)
*/
@Override
public InsertOptionsBuilder readTimeout(long readTimeout, TimeUnit timeUnit) {
return (InsertOptionsBuilder) super.readTimeout(readTimeout, timeUnit);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#tracing(boolean)
*/
@Override
public InsertOptionsBuilder tracing(boolean tracing) {
return (InsertOptionsBuilder) super.tracing(tracing);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#withTracing()
*/
@Override
public InsertOptionsBuilder withTracing() {
return (InsertOptionsBuilder) super.withTracing();
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.WriteOptions.WriteOptionsBuilder#ttl(int)
*/
public InsertOptionsBuilder ttl(int ttl) {
return (InsertOptionsBuilder) super.ttl(ttl);
}
/**
* Use light-weight transactions by applying {@code IF NOT EXISTS}.
*
* @return {@code this} {@link InsertOptionsBuilder}
*/
public InsertOptionsBuilder withIfNotExists() {
return ifNotExists(true);
}
/**
* Use light-weight transactions by applying {@code IF NOT EXISTS}.
*
* @param ifNotExists {@literal true} to enable {@code IF NOT EXISTS}.
* @return {@code this} {@link InsertOptionsBuilder}
*/
public InsertOptionsBuilder ifNotExists(boolean ifNotExists) {
this.ifNotExists = ifNotExists;
return this;
}
/**
* Builds a new {@link InsertOptions} with the configured values.
*
* @return a new {@link InsertOptions} with the configured values
*/
public InsertOptions build() {
InsertOptions insertOptions = applyOptions(new InsertOptions());
insertOptions.ifNotExists = ifNotExists;
return insertOptions;
}
}
}

View File

@@ -55,6 +55,15 @@ class QueryUtils {
Insert insert = QueryOptionsUtil.addWriteOptions(QueryBuilder.insertInto(tableName), options);
if (options instanceof InsertOptions) {
InsertOptions insertOptions = (InsertOptions) options;
if (insertOptions.isIfNotExists()) {
insert = insert.ifNotExists();
}
}
entityWriter.write(objectToUpdate, insert);
return insert;
@@ -79,6 +88,15 @@ class QueryUtils {
Update update = QueryOptionsUtil.addWriteOptions(QueryBuilder.update(tableName), options);
if (options instanceof UpdateOptions) {
UpdateOptions updateOptions = (UpdateOptions) options;
if (updateOptions.isIfExists()) {
update.where().ifExists();
}
}
entityWriter.write(objectToUpdate, update);
return update;

View File

@@ -36,6 +36,10 @@ import com.datastax.driver.core.Statement;
* @author Mark Paluch
* @since 2.0
* @see ReactiveCassandraTemplate
* @see ReactiveCqlOperations
* @see Statement
* @see InsertOptions
* @see UpdateOptions
* @see Flux
* @see Mono
*/
@@ -180,10 +184,10 @@ public interface ReactiveCassandraOperations {
*
* @param entity The entity to insert, must not be {@literal null}.
* @param options may be {@literal null}.
* @@return the inserted entity.
* @return the inserted entity or {@link Mono#empty()} if the {@code INSERT} operation was not applied.
* @throws DataAccessException if there is any problem issuing the execution.
*/
<T> Mono<T> insert(T entity, WriteOptions options) throws DataAccessException;
<T> Mono<T> insert(T entity, InsertOptions options) throws DataAccessException;
/**
* Insert the given entities and emit the entity if the insert was applied.
@@ -199,10 +203,10 @@ public interface ReactiveCassandraOperations {
*
* @param entities The entities to insert, must not be {@literal null}.
* @param options may be {@literal null}.
* @return the inserted entities.
* @return the inserted entities. Does not emit items for which the {@code INSERT} operation was not applied.
* @throws DataAccessException if there is any problem issuing the execution.
*/
<T> Flux<T> insert(Publisher<? extends T> entities, WriteOptions options) throws DataAccessException;
<T> Flux<T> insert(Publisher<? extends T> entities, InsertOptions options) throws DataAccessException;
/**
* Update the given entity and emit the entity if the update was applied.
@@ -218,10 +222,10 @@ public interface ReactiveCassandraOperations {
*
* @param entity The entity to update, must not be {@literal null}.
* @param options may be {@literal null}.
* @return the updated entity.
* @return the updated entity or {@link Mono#empty()} if the {@code UPDATE} operation was not applied.
* @throws DataAccessException if there is any problem issuing the execution.
*/
<T> Mono<T> update(T entity, WriteOptions options) throws DataAccessException;
<T> Mono<T> update(T entity, UpdateOptions options) throws DataAccessException;
/**
* Update the given entities and emit the entity if the update was applied.
@@ -237,10 +241,10 @@ public interface ReactiveCassandraOperations {
*
* @param entities The entities to update.
* @param options may be {@literal null}.
* @return the updated entities.
* @return the updated entities. Does not emit items for which the {@code UPDATE} operation was not applied.
* @throws DataAccessException if there is any problem issuing the execution.
*/
<T> Flux<T> update(Publisher<? extends T> entities, WriteOptions options) throws DataAccessException;
<T> Flux<T> update(Publisher<? extends T> entities, UpdateOptions options) throws DataAccessException;
/**
* Remove the given object from the table by id.

View File

@@ -34,7 +34,6 @@ import org.springframework.data.cql.core.QueryOptions;
import org.springframework.data.cql.core.ReactiveCqlOperations;
import org.springframework.data.cql.core.ReactiveCqlTemplate;
import org.springframework.data.cql.core.ReactiveSessionCallback;
import org.springframework.data.cql.core.WriteOptions;
import org.springframework.data.cql.core.session.DefaultReactiveSessionFactory;
import org.springframework.data.cql.core.session.ReactiveResultSet;
import org.springframework.data.cql.core.session.ReactiveSession;
@@ -380,10 +379,10 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations {
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#insert(java.lang.Object, org.springframework.data.cql.core.WriteOptions)
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#insert(java.lang.Object, org.springframework.data.cassandra.core.InsertOptions)
*/
@Override
public <T> Mono<T> insert(T entity, WriteOptions options) {
public <T> Mono<T> insert(T entity, InsertOptions options) {
Assert.notNull(entity, "Entity must not be null");
@@ -417,10 +416,10 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations {
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#insert(org.reactivestreams.Publisher, org.springframework.data.cql.core.WriteOptions)
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#insert(org.reactivestreams.Publisher, org.springframework.data.cassandra.core.InsertOptions)
*/
@Override
public <T> Flux<T> insert(Publisher<? extends T> entities, WriteOptions options) {
public <T> Flux<T> insert(Publisher<? extends T> entities, InsertOptions options) {
Assert.notNull(entities, "Entity publisher must not be null");
@@ -438,10 +437,10 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations {
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#update(java.lang.Object, org.springframework.data.cql.core.WriteOptions)
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#update(java.lang.Object, org.springframework.data.cassandra.core.UpdateOptions)
*/
@Override
public <T> Mono<T> update(T entity, WriteOptions options) {
public <T> Mono<T> update(T entity, UpdateOptions options) {
Assert.notNull(entity, "Entity must not be null");
@@ -475,10 +474,10 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations {
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#update(org.reactivestreams.Publisher, org.springframework.data.cql.core.WriteOptions)
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations#update(org.reactivestreams.Publisher, org.springframework.data.cassandra.core.UpdateOptions)
*/
@Override
public <T> Flux<T> update(Publisher<? extends T> entities, WriteOptions options) {
public <T> Flux<T> update(Publisher<? extends T> entities, UpdateOptions options) {
Assert.notNull(entities, "Entity publisher must not be null");

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core;
import java.util.concurrent.TimeUnit;
import org.springframework.data.cql.core.WriteOptions;
/**
* Extension to {@link WriteOptions} for use with {@code UPDATE} operations.
*
* @author Mark Paluch
* @since 2.0
*/
public class UpdateOptions extends WriteOptions {
private boolean ifExists;
/**
* Creates new {@link UpdateOptions}.
*/
UpdateOptions() {}
/**
* Create a new {@link UpdateOptionsBuilder}.
*
* @return a new {@link UpdateOptionsBuilder}.
*/
public static UpdateOptionsBuilder builder() {
return new UpdateOptionsBuilder();
}
/**
* @return {@literal true} to apply {@code IF EXISTS} to {@code UPDATE} operations.
*/
public boolean isIfExists() {
return ifExists;
}
/**
* Builder for {@link UpdateOptions}.
*
* @author Mark Paluch
* @since 2.0
*/
public static class UpdateOptionsBuilder extends WriteOptionsBuilder {
private boolean ifExists;
private UpdateOptionsBuilder() {}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#consistencyLevel(com.datastax.driver.core.ConsistencyLevel)
*/
@Override
public UpdateOptionsBuilder consistencyLevel(com.datastax.driver.core.ConsistencyLevel consistencyLevel) {
return (UpdateOptionsBuilder) super.consistencyLevel(consistencyLevel);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#retryPolicy(org.springframework.data.cql.core.RetryPolicy)
*/
@Override
public UpdateOptionsBuilder retryPolicy(com.datastax.driver.core.policies.RetryPolicy driverRetryPolicy) {
return (UpdateOptionsBuilder) super.retryPolicy(driverRetryPolicy);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#fetchSize(int)
*/
@Override
public UpdateOptionsBuilder fetchSize(int fetchSize) {
return (UpdateOptionsBuilder) super.fetchSize(fetchSize);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#readTimeout(long)
*/
@Override
public UpdateOptionsBuilder readTimeout(long readTimeout) {
return (UpdateOptionsBuilder) super.readTimeout(readTimeout);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#readTimeout(long, java.util.concurrent.TimeUnit)
*/
@Override
public UpdateOptionsBuilder readTimeout(long readTimeout, TimeUnit timeUnit) {
return (UpdateOptionsBuilder) super.readTimeout(readTimeout, timeUnit);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#tracing(boolean)
*/
@Override
public UpdateOptionsBuilder tracing(boolean tracing) {
return (UpdateOptionsBuilder) super.tracing(tracing);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.QueryOptions.QueryOptionsBuilder#withTracing()
*/
@Override
public UpdateOptionsBuilder withTracing() {
return (UpdateOptionsBuilder) super.withTracing();
}
/*
* (non-Javadoc)
* @see org.springframework.data.cql.core.WriteOptions.WriteOptionsBuilder#ttl(int)
*/
public UpdateOptionsBuilder ttl(int ttl) {
return (UpdateOptionsBuilder) super.ttl(ttl);
}
/**
* Use light-weight transactions by applying {@code IF EXISTS}.
*
* @return {@code this} {@link UpdateOptionsBuilder}
*/
public UpdateOptionsBuilder withIfExists() {
return ifExists(true);
}
/**
* Use light-weight transactions by applying {@code IF EXISTS}.
*
* @param ifNotExists {@literal true} to enable {@code IF EXISTS}.
* @return {@code this} {@link UpdateOptionsBuilder}
*/
public UpdateOptionsBuilder ifExists(boolean ifNotExists) {
this.ifExists = ifNotExists;
return this;
}
/**
* Builds a new {@link UpdateOptions} with the configured values.
*
* @return a new {@link UpdateOptions} with the configured values
*/
public UpdateOptions build() {
UpdateOptions insertOptions = applyOptions(new UpdateOptions());
insertOptions.ifExists = ifExists;
return insertOptions;
}
}
}

View File

@@ -313,7 +313,9 @@ public class QueryOptions {
return applyOptions(new QueryOptions());
}
<T extends QueryOptions> T applyOptions(T queryOptions) {
protected <T> T applyOptions(T options) {
QueryOptions queryOptions = (QueryOptions) options;
queryOptions.setConsistencyLevel(consistencyLevel);
queryOptions.setRetryPolicy(retryPolicy);
@@ -330,7 +332,7 @@ public class QueryOptions {
queryOptions.setTracing(tracing);
}
return queryOptions;
return options;
}
}
}

View File

@@ -95,7 +95,7 @@ public class WriteOptions extends QueryOptions {
private Integer ttl;
private WriteOptionsBuilder() {}
protected WriteOptionsBuilder() {}
/*
* (non-Javadoc)
@@ -177,12 +177,16 @@ public class WriteOptions extends QueryOptions {
* @return a new {@link WriteOptions} with the configured values
*/
public WriteOptions build() {
return applyOptions(new WriteOptions());
}
WriteOptions queryOptions = applyOptions(new WriteOptions());
@Override
protected <T> T applyOptions(T queryOptions) {
queryOptions.setTtl(ttl);
WriteOptions writeOptions = (WriteOptions) queryOptions;
writeOptions.setTtl(ttl);
return queryOptions;
return super.applyOptions(queryOptions);
}
}
}

View File

@@ -99,12 +99,41 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
User user = new User("heisenberg", "Walter", "White");
assertThat(getUninterruptibly(template.selectOneById(user.getId(), User.class))).isNull();
assertThat(getUser(user.getId())).isNull();
ListenableFuture<User> insert = template.insert(user);
assertThat(getUninterruptibly(insert)).isEqualTo(user);
assertThat(getUninterruptibly(template.selectOneById(user.getId(), User.class))).isEqualTo(user);
assertThat(getUser(user.getId())).isEqualTo(user);
}
@Test // DATACASS-250
public void insertShouldCreateEntityWithLwt() {
InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build();
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<User> inserted = template.insert(user, lwtOptions);
assertThat(getUninterruptibly(inserted)).isEqualTo(user);
}
@Test // DATACASS-250
public void insertShouldNotUpdateEntityWithLwt() {
InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build();
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user, lwtOptions));
user.setFirstname("Walter Hartwell");
ListenableFuture<User> lwt = template.insert(user, lwtOptions);
assertThat(getUninterruptibly(lwt)).isNull();
assertThat(getUser(user.getId()).getFirstname()).isEqualTo("Walter");
}
@Test // DATACASS-292
@@ -125,10 +154,40 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
getUninterruptibly(template.insert(user));
user.setFirstname("Walter Hartwell");
User updated = getUninterruptibly(template.update(user));
assertThat(updated).isNotNull();
assertThat(getUninterruptibly(template.selectOneById(user.getId(), User.class))).isEqualTo(user);
assertThat(getUser(user.getId())).isEqualTo(user);
}
@Test // DATACASS-292
public void updateShouldNotCreateEntityWithLwt() {
UpdateOptions lwtOptions = UpdateOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
ListenableFuture<User> lwt = template.update(user, lwtOptions);
assertThat(getUninterruptibly(lwt)).isNull();
assertThat(getUser(user.getId())).isNull();
}
@Test // DATACASS-292
public void updateShouldUpdateEntityWithLwt() {
UpdateOptions lwtOptions = UpdateOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
getUninterruptibly(template.insert(user));
user.setFirstname("Walter Hartwell");
ListenableFuture<User> updated = template.update(user, lwtOptions);
assertThat(getUninterruptibly(updated)).isNotNull();
assertThat(getUser(user.getId()).getFirstname()).isEqualTo("Walter Hartwell");
}
@Test // DATACASS-343
@@ -142,8 +201,7 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
template.update(query, Update.empty().set("firstname", "Walter Hartwell"), User.class));
assertThat(result).isTrue();
assertThat(getUninterruptibly(template.selectOneById(user.getId(), User.class)).getFirstname())
.isEqualTo("Walter Hartwell");
assertThat(getUser(user.getId()).getFirstname()).isEqualTo("Walter Hartwell");
}
@Test // DATACASS-343
@@ -155,7 +213,7 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
Query query = Query.query(Criteria.where("id").is("heisenberg"));
assertThat(getUninterruptibly(template.delete(query, User.class))).isTrue();
assertThat(getUninterruptibly(template.selectOneById(user.getId(), User.class))).isNull();
assertThat(getUser(user.getId())).isNull();
}
@Test // DATACASS-343
@@ -168,7 +226,7 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
assertThat(getUninterruptibly(template.delete(query, User.class))).isTrue();
User loaded = getUninterruptibly(template.selectOneById(user.getId(), User.class));
User loaded = getUser(user.getId());
assertThat(loaded.getFirstname()).isEqualTo("Walter");
assertThat(loaded.getLastname()).isNull();
}
@@ -182,7 +240,7 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
User deleted = getUninterruptibly(template.delete(user));
assertThat(deleted).isNotNull();
assertThat(getUninterruptibly(template.selectOneById(user.getId(), User.class))).isNull();
assertThat(getUser(user.getId())).isNull();
}
@Test // DATACASS-292
@@ -194,7 +252,11 @@ public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCrea
Boolean deleted = getUninterruptibly(template.deleteById(user.getId(), User.class));
assertThat(deleted).isTrue();
assertThat(getUninterruptibly(template.selectOneById(user.getId(), User.class))).isNull();
assertThat(getUser(user.getId())).isNull();
}
private User getUser(String id) {
return getUninterruptibly(template.selectOneById(id, User.class));
}
private static <T> T getUninterruptibly(Future<T> future) {

View File

@@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
@@ -72,6 +73,29 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea
assertThat(loaded.getId().getUsername()).isEqualTo(walter.getId().getUsername());
}
@Test // DATACASS-288
public void shouldInsertEntitiesWithLwt() {
InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build();
Group previousWalter = new Group(new GroupKey("users", "0x1", "walter"));
previousWalter.setAge(42);
template.insert(previousWalter);
walter.setAge(100);
CassandraBatchOperations batchOperations = new CassandraBatchTemplate(template);
batchOperations.insert(Collections.singleton(walter), lwtOptions).insert(mike).execute();
Group loadedWalter = template.selectOneById(walter.getId(), Group.class);
Group loadedMike = template.selectOneById(mike.getId(), Group.class);
assertThat(loadedWalter.getId().getUsername()).isEqualTo(walter.getId().getUsername());
assertThat(loadedWalter.getAge()).isEqualTo(42);
assertThat(loadedMike).isNotNull();
}
@Test // DATACASS-288
public void shouldInsertCollectionOfEntities() {

View File

@@ -146,6 +146,35 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
assertThat(template.selectOneById(user.getId(), User.class)).isEqualTo(user);
}
@Test // DATACASS-250
public void insertShouldCreateEntityWithLwt() {
InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build();
User user = new User("heisenberg", "Walter", "White");
User inserted = template.insert(user, lwtOptions);
assertThat(inserted).isEqualTo(user);
}
@Test // DATACASS-250
public void insertShouldNotUpdateEntityWithLwt() {
InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build();
User user = new User("heisenberg", "Walter", "White");
template.insert(user, lwtOptions);
user.setFirstname("Walter Hartwell");
User lwt = template.insert(user, lwtOptions);
assertThat(lwt).isNull();
assertThat(template.selectOneById(user.getId(), User.class).getFirstname()).isEqualTo("Walter");
}
@Test // DATACASS-292
public void shouldInsertAndCountEntities() {
@@ -171,6 +200,35 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
assertThat(template.selectOneById(user.getId(), User.class)).isEqualTo(user);
}
@Test // DATACASS-292
public void updateShouldNotCreateEntityWithLwt() {
UpdateOptions lwtOptions = UpdateOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
User lwt = template.update(user, lwtOptions);
assertThat(lwt).isNull();
assertThat(template.selectOneById(user.getId(), User.class)).isNull();
}
@Test // DATACASS-292
public void updateShouldUpdateEntityWithLwt() {
UpdateOptions lwtOptions = UpdateOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
template.insert(user);
user.setFirstname("Walter Hartwell");
User updated = template.update(user, lwtOptions);
assertThat(updated).isNotNull();
assertThat(template.selectOneById(user.getId(), User.class).getFirstname()).isEqualTo("Walter Hartwell");
}
@Test // DATACASS-343
public void updateShouldUpdateEntityByQuery() {

View File

@@ -204,6 +204,23 @@ public class CassandraTemplateUnitTests {
.isEqualTo("INSERT INTO users (firstname,id,lastname) VALUES ('Walter','heisenberg','White');");
}
@Test // DATACASS-250
public void insertShouldInsertWithOptionsEntity() {
InsertOptions insertOptions = InsertOptions.builder().withIfNotExists().build();
when(resultSet.wasApplied()).thenReturn(true);
User user = new User("heisenberg", "Walter", "White");
User inserted = template.insert(user, insertOptions);
assertThat(inserted).isEqualTo(user);
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString())
.isEqualTo("INSERT INTO users (firstname,id,lastname) VALUES ('Walter','heisenberg','White') IF NOT EXISTS;");
}
@Test // DATACASS-292
public void insertShouldTranslateException() throws Exception {
@@ -246,6 +263,23 @@ public class CassandraTemplateUnitTests {
.isEqualTo("UPDATE users SET firstname='Walter',lastname='White' WHERE id='heisenberg';");
}
@Test // DATACASS-250
public void updateShouldUpdateEntityWithOptions() {
when(resultSet.wasApplied()).thenReturn(true);
UpdateOptions updateOptions = UpdateOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
User updated = template.update(user, updateOptions);
assertThat(updated).isEqualTo(user);
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString())
.isEqualTo("UPDATE users SET firstname='Walter',lastname='White' WHERE id='heisenberg' IF EXISTS;");
}
@Test // DATACASS-292
public void updateShouldTranslateException() throws Exception {

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core;
import static org.assertj.core.api.Assertions.*;
import org.junit.Test;
/**
* Unit tests for {@link InsertOptions}.
*
* @author Mark Paluch
*/
public class InsertOptionsUnitTests {
@Test // DATACASS-250
public void shouldConfigureInsertOptions() {
InsertOptions insertOptions = InsertOptions.builder() //
.ttl(10) //
.withIfNotExists() //
.build();
assertThat(insertOptions.getTtl()).isEqualTo(10);
assertThat(insertOptions.isIfNotExists()).isTrue();
}
}

View File

@@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.*;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.test.StepVerifier;
import reactor.test.StepVerifier.FirstStep;
import org.junit.Before;
import org.junit.Test;
@@ -68,11 +69,39 @@ public class ReactiveCassandraTemplateIntegrationTests extends AbstractKeyspaceC
User user = new User("heisenberg", "Walter", "White");
Mono<User> insert = template.insert(user);
StepVerifier.create(template.selectOneById(user.getId(), User.class)).verifyComplete();
verifyUser(user.getId()).verifyComplete();
StepVerifier.create(insert).expectNext(user).verifyComplete();
StepVerifier.create(template.selectOneById(user.getId(), User.class)).expectNext(user).verifyComplete();
verifyUser(user.getId()).expectNext(user).verifyComplete();
}
@Test // DATACASS-250
public void insertShouldCreateEntityWithLwt() {
InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build();
User user = new User("heisenberg", "Walter", "White");
Mono<User> inserted = template.insert(user, lwtOptions);
StepVerifier.create(inserted).expectNext(user).verifyComplete();
}
@Test // DATACASS-250
public void insertShouldNotUpdateEntityWithLwt() {
InsertOptions lwtOptions = InsertOptions.builder().withIfNotExists().build();
User user = new User("heisenberg", "Walter", "White");
StepVerifier.create(template.insert(user, lwtOptions)).expectNext(user).verifyComplete();
user.setFirstname("Walter Hartwell");
StepVerifier.create(template.insert(user, lwtOptions)).verifyComplete();
verifyUser(user.getId()).consumeNextWith(it -> assertThat(it.getFirstname()).isEqualTo("Walter")).verifyComplete();
}
@Test // DATACASS-335
@@ -96,7 +125,35 @@ public class ReactiveCassandraTemplateIntegrationTests extends AbstractKeyspaceC
StepVerifier.create(template.insert(user)).expectNextCount(1).verifyComplete();
StepVerifier.create(template.selectOneById(user.getId(), User.class)).expectNext(user).verifyComplete();
verifyUser(user.getId()).expectNext(user).verifyComplete();
}
@Test // DATACASS-292
public void updateShouldNotCreateEntityWithLwt() {
UpdateOptions lwtOptions = UpdateOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
StepVerifier.create(template.update(user, lwtOptions)).verifyComplete();
verifyUser(user.getId()).verifyComplete();
}
@Test // DATACASS-292
public void updateShouldUpdateEntityWithLwt() {
UpdateOptions lwtOptions = UpdateOptions.builder().withIfExists().build();
User user = new User("heisenberg", "Walter", "White");
StepVerifier.create(template.insert(user)).expectNextCount(1).verifyComplete();
user.setFirstname("Walter Hartwell");
StepVerifier.create(template.update(user, lwtOptions)).expectNextCount(1).verifyComplete();
verifyUser(user.getId()).consumeNextWith(it -> assertThat(it.getFirstname()).isEqualTo("Walter Hartwell"))
.verifyComplete();
}
@Test // DATACASS-343
@@ -149,7 +206,7 @@ public class ReactiveCassandraTemplateIntegrationTests extends AbstractKeyspaceC
StepVerifier.create(template.delete(user)).expectNext(user).verifyComplete();
StepVerifier.create(template.selectOneById(user.getId(), User.class)).verifyComplete();
verifyUser(user.getId()).verifyComplete();
}
@Test // DATACASS-335
@@ -161,7 +218,7 @@ public class ReactiveCassandraTemplateIntegrationTests extends AbstractKeyspaceC
StepVerifier.create(template.deleteById(user.getId(), User.class)).expectNext(true).verifyComplete();
StepVerifier.create(template.selectOneById(user.getId(), User.class)).verifyComplete();
verifyUser(user.getId()).verifyComplete();
}
@Test // DATACASS-343
@@ -199,4 +256,8 @@ public class ReactiveCassandraTemplateIntegrationTests extends AbstractKeyspaceC
assertThat(template.selectOne(query, UserToken.class).block()).isEqualTo(token1);
}
private FirstStep<User> verifyUser(String userId) {
return StepVerifier.create(template.selectOneById(userId, User.class));
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core;
import static org.assertj.core.api.Assertions.*;
import org.junit.Test;
/**
* Unit tests for {@link UpdateOptions}.
*
* @author Mark Paluch
*/
public class UpdateOptionsUnitTests {
@Test // DATACASS-250
public void shouldConfigureInsertOptions() {
UpdateOptions insertOptions = UpdateOptions.builder() //
.ttl(10) //
.withIfExists() //
.build();
assertThat(insertOptions.getTtl()).isEqualTo(10);
assertThat(insertOptions.isIfExists()).isTrue();
}
}

View File

@@ -5,6 +5,7 @@
== What's new in Spring Data for Apache Cassandra 2.0
* `Update` and `Query` objects.
* CRUD repository interface renaming: `CassandraRepository` using `MapId` is now renamed to `MapIdCassandraRepository`. `TypedIdCassandraRepository` is renamed to `CassandraRepository`.
* Lightweight transactions via `InsertOptions` and `UpdateOptions` using the Template API.
[[new-features.1-5-0]]
== What's new in Spring Data for Apache Cassandra 1.5

View File

@@ -1160,14 +1160,15 @@ Person qp = cassandraTemplate.selectOne(query(where("age").is(33)), Person.class
The insert/save operations available to you are listed below.
* `T` *insert* `(T objectToSave)` Insert the object in an Apache Cassandra table.
* `T` *insert* `(T objectToSave, WriteOptions writeOptions)` Insert the object in an Apache Cassandra table applying `WriteOptions`.
* `T` *insert* `(T objectToSave, InsertOptions options)` Insert the object in an Apache Cassandra table applying `InsertOptions`.
A similar set of update operations is listed below
* `T` *update* `(T objectToSave)` Update the object in an Apache Cassandra table.
* `T` *update* `(T objectToSave, WriteOptions writeOptions)` Update the object in an Apache Cassandra table applying `WriteOptions`.
* `T` *update* `(T objectToSave, UpdateOptions options)` Update the object in an Apache Cassandra table applying `UpdateOptions`.
Then, there is always the old fashioned way. You can write your own CQL statements.
Then, there is always the old fashioned way. You can write your own CQL statements. You can configure with `InsertOptions` and `UpdateOptions`
additional options such as TTL, consistency level and lightweight transactions.
[source,java]
----