DATACASS-292 - Provide revised synchronous and asynchronous CQL and Cassandra templates.

We now provide revised CQL and Cassandra templates as central classes to interact with CQL and Cassandra with object mapping. Previously, synchronous and asynchronous methods were exposed inside the same interfaces that made it hard to chose the right method.

The revised Template API consists of:

* CqlTemplate
* AsyncCqlTemplate
* CassandraTemplate
* AsyncCassandraTemplate

CassandraTemplate and AsyncCassandraTemplate reuse CqlTemplate and AsyncCqlTemplate instead of extending from these. This is, to not mix methods using conversion/object mapping with lower level CQL execution methods.

AsyncCqlTemplate and AsyncCassandraTemplate are all new and benefit from ListenableFuture as synchronization aid. They no longer rely on various callback-interfaces.

CassandraTemplate and AsyncCassandraTemplate no longer provide insert/update/delete methods accepting a collection of items. Use CassandraBatchOperations for atomic batches to group operations.
This commit is contained in:
Mark Paluch
2016-10-19 14:56:11 +02:00
committed by John Blum
parent 636c49e058
commit 8af23880e9
78 changed files with 9074 additions and 8443 deletions

View File

@@ -0,0 +1,722 @@
/*
* Copyright 2016 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.cassandra.core;
import java.util.List;
import java.util.Map;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Statement;
/**
* Interface specifying a basic set of CQL asynchronously executed operations. Exposes similar methods as {@link CqlTemplate}, but returns
* result handles or accepts callbacks as opposed to concrete results. Implemented by {@link AsyncCqlTemplate}. Not
* often used directly, but a useful option to enhance testability, as it can easily be mocked or stubbed.
*
* @author Mark Paluch
* @since 2.0
* @see AsyncCqlTemplate
*/
public interface AsyncCqlOperations {
// -------------------------------------------------------------------------
// Methods dealing with a plain com.datastax.driver.core.Session
// -------------------------------------------------------------------------
/**
* Execute a CQL data access operation, implemented as callback action working on a
* {@link com.datastax.driver.core.Session}. This allows for implementing arbitrary data access operations, within
* Spring's managed CQL environment: that is, converting CQL
* {@link com.datastax.driver.core.exceptions.DriverException}s into Spring's {@link DataAccessException} hierarchy.
* <p>
* The callback action can return a result object, for example a domain object or a collection of domain objects.
*
* @param action the callback object that specifies the action.
* @return a result object returned by the action, or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> execute(AsyncSessionCallback<T> action) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with static CQL
// -------------------------------------------------------------------------
/**
* Issue a single CQL execute, typically a DDL statement, insert, update or delete statement.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> execute(String cql) throws DataAccessException;
/**
* Execute a query given static CQL, reading the {@link ResultSet} with a {@link ResultSetExtractor}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param rse object that will extract all rows of results, must not be {@literal null}.
* @return an arbitrary result object, as returned by the ResultSetExtractor.
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, ResultSetExtractor, Object...)
*/
<T> ListenableFuture<T> query(String cql, ResultSetExtractor<T> rse) throws DataAccessException;
/**
* Execute a query given static CQL, reading the {@link ResultSet} on a per-row basis with a
* {@link RowCallbackHandler}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code query} method with {@code null} as argument array.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param rch object that will extract results, one row at a time, must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query
* @see #query(String, RowCallbackHandler, Object[])
*/
ListenableFuture<Void> query(String cql, RowCallbackHandler rch) throws DataAccessException;
/**
* Execute a query given static CQL, mapping each row to a Java object via a {@link RowMapper}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the result {@link List}, containing mapped objects.
* @throws DataAccessException if there is any problem executing the query
* @see #query(String, RowMapper, Object[])
*/
<T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Execute a query given static CQL, mapping a single result row to a Java object via a {@link RowMapper}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@link #queryForObject(String, RowMapper, Object...)} method with
* {@literal null} as argument array.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the single mapped object.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, RowMapper, Object[])
*/
<T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Execute a query for a result object, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@link #queryForObject(String, Class, Object...)} method with
* {@literal null} as argument array.
* <p>
* This method is useful for running static CQL with a known outcome. The query is expected to be a single row/single
* column query; the returned result will be directly mapped to the corresponding object type.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param requiredType the type that the result object is expected to match, must not be {@literal null}.
* @return the result object of the required type, or {@link Mono#empty()} in case of CQL NULL.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row, or does not return
* exactly one column in that row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, Class, Object[])
*/
<T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType) throws DataAccessException;
/**
* Execute a query for a result Map, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@link #queryForMap(String, Object...)} method with {@literal null}
* as argument array.
* <p>
* The query is expected to be a single row query; the result row will be mapped to a Map (one entry for each column,
* using the column name as the key).
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @return the result Map (one entry for each column, using the column name as the key), must not be {@literal null}.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForMap(String, Object[])
* @see ColumnMapRowMapper
*/
ListenableFuture<Map<String, Object>> queryForMap(String cql) throws DataAccessException;
/**
* Execute a query for a result {@link List}, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code queryForList} method with {@literal null} as argument array.
* <p>
* The results will be mapped to a {@link List} (one item for each row) of result objects, each of them matching the
* specified element type.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param elementType the required type of element in the result {@link List} (for example, {@code Integer.class}),
* must not be {@literal null}.
* @return a {@link List} of objects that match the specified element type.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String, Class, Object[])
* @see SingleColumnRowMapper
*/
<T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType) throws DataAccessException;
/**
* Execute a query for a result {@link List}, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code queryForList} method with {@literal null} as argument array.
* <p>
* The results will be mapped to a {@link List} (one item for each row) of {@link Map}s (one entry for each column
* using the column name as the key). Each item in the {@link List} will be of the form returned by this interface's
* queryForMap() methods.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @return a {@link List} that contains a {@link Map} per row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String, Object[])
*/
ListenableFuture<List<Map<String, Object>>> queryForList(String cql) throws DataAccessException;
/**
* Execute a query for a ResultSet, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code queryForResultSet} method with {@literal null} as argument
* array.
* <p>
* The results will be mapped to an {@link ResultSet}.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @return a {@link ResultSet} representation.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForResultSet(String, Object[])
*/
ListenableFuture<ResultSet> queryForResultSet(String cql) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.driver.core.Statement
// -------------------------------------------------------------------------
/**
* Issue a single CQL execute, typically a DDL statement, insert, update or delete statement.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> execute(Statement statement) throws DataAccessException;
/**
* Execute a query given static CQL, reading the {@link ResultSet} with a {@link ResultSetExtractor}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @param rse object that will extract all rows of results, must not be {@literal null}.
* @return an arbitrary result object, as returned by the ResultSetExtractor.
* @throws DataAccessException if there is any problem executing the query.
* @see #query(String, ResultSetExtractor, Object...)
*/
<T> ListenableFuture<T> query(Statement statement, ResultSetExtractor<T> rse) throws DataAccessException;
/**
* Execute a query given static CQL, reading the {@link ResultSet} on a per-row basis with a
* {@link RowCallbackHandler}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code query} method with {@code null} as argument array.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @param rch object that will extract results, one row at a time, must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query
* @see #query(String, RowCallbackHandler, Object[])
*/
ListenableFuture<Void> query(Statement statement, RowCallbackHandler rch) throws DataAccessException;
/**
* Execute a query given static CQL, mapping each row to a Java object via a {@link RowMapper}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code query} method with {@literal null} as argument array.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the result {@link List}, containing mapped objects.
* @throws DataAccessException if there is any problem executing the query
* @see #query(String, RowMapper, Object[])
*/
<T> ListenableFuture<List<T>> query(Statement statement, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Execute a query given static CQL, mapping a single result row to a Java object via a {@link RowMapper}.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@link #queryForObject(String, RowMapper, Object...)} method with
* {@literal null} as argument array.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the single mapped object.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, RowMapper, Object[])
*/
<T> ListenableFuture<T> queryForObject(Statement statement, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Execute a query for a result object, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@link #queryForObject(String, Class, Object...)} method with
* {@literal null} as argument array.
* <p>
* This method is useful for running static CQL with a known outcome. The query is expected to be a single row/single
* column query; the returned result will be directly mapped to the corresponding object type.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @param requiredType the type that the result object is expected to match, must not be {@literal null}.
* @return the result object of the required type, or {@link Mono#empty()} in case of CQL NULL.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row, or does not return
* exactly one column in that row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, Class, Object[])
*/
<T> ListenableFuture<T> queryForObject(Statement statement, Class<T> requiredType) throws DataAccessException;
/**
* Execute a query for a result Map, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@link #queryForMap(String, Object...)} method with {@literal null}
* as argument array.
* <p>
* The query is expected to be a single row query; the result row will be mapped to a Map (one entry for each column,
* using the column name as the key).
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @return the result Map (one entry for each column, using the column name as the key), must not be {@literal null}.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForMap(String, Object[])
* @see ColumnMapRowMapper
*/
ListenableFuture<Map<String, Object>> queryForMap(Statement statement) throws DataAccessException;
/**
* Execute a query for a result {@link List}, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code queryForList} method with {@literal null} as argument array.
* <p>
* The results will be mapped to a {@link List} (one item for each row) of result objects, each of them matching the
* specified element type.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @param elementType the required type of element in the result {@link List} (for example, {@code Integer.class}),
* must not be {@literal null}.
* @return a {@link List} of objects that match the specified element type.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String, Class, Object[])
* @see SingleColumnRowMapper
*/
<T> ListenableFuture<List<T>> queryForList(Statement statement, Class<T> elementType) throws DataAccessException;
/**
* Execute a query for a result {@link List}, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code queryForList} method with {@literal null} as argument array.
* <p>
* The results will be mapped to a {@link List} (one item for each row) of {@link Map}s (one entry for each column
* using the column name as the key). Each item in the {@link List} will be of the form returned by this interface's
* queryForMap() methods.
*
* @param statement static CQL {@link Statement} to execute, must not be empty or {@literal null}.
* @return a {@link List} that contains a {@link Map} per row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String, Object[])
*/
ListenableFuture<List<Map<String, Object>>> queryForList(Statement statement) throws DataAccessException;
/**
* Execute a query for a ResultSet, given static CQL.
* <p>
* Uses a CQL Statement, not a {@link PreparedStatement}. If you want to execute a static query with a
* {@link PreparedStatement}, use the overloaded {@code queryForResultSet} method with {@literal null} as argument
* array.
* <p>
* The results will be mapped to an {@link ResultSet}.
*
* @param statement static CQL {@link Statement}, must not be {@literal null}.
* @return a {@link ResultSet} representation.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForResultSet(String, Object[])
*/
ListenableFuture<ResultSet> queryForResultSet(Statement statement) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with prepared statements
// -------------------------------------------------------------------------
/**
* Execute a CQL data access operation, implemented as callback action working on a CQL {@link PreparedStatement}.
* This allows for implementing arbitrary data access operations on a single {@link PreparedStatement}, within
* Spring's managed CQL environment: that is, participating in Spring-managed transactions and converting CQL
* {@link com.datastax.driver.core.exceptions.DriverException}s into Spring's {@link DataAccessException} hierarchy.
* <p>
* The callback action can return a result object, for example a domain object or a collection of domain objects.
*
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
* must not be {@literal null}.
* @param action callback object that specifies the action, must not be {@literal null}.
* @return a result object returned by the action, or {@literal null}.
* @throws DataAccessException if there is any problem
*/
<T> ListenableFuture<T> execute(AsyncPreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException;
/**
* Execute a CQL data access operation, implemented as callback action working on a CQL {@link PreparedStatement}.
* This allows for implementing arbitrary data access operations on a single Statement, within Spring's managed CQL
* environment: that is, participating in Spring-managed transactions and converting CQL
* {@link com.datastax.driver.core.exceptions.DriverException}s into Spring's {@link DataAccessException} hierarchy.
* <p>
* The callback action can return a result object, for example a domain object or a collection of domain objects.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param action callback object that specifies the action, must not be {@literal null}.
* @return a result object returned by the action, or {@literal null}
* @throws DataAccessException if there is any problem TODO: Lambda-usage clashes with execute(cql,
* PreparedStatementBinder)
*/
<T> ListenableFuture<T> execute(String cql, PreparedStatementCallback<T> action) throws DataAccessException;
/**
* Query using a prepared statement, reading the {@link ResultSet} with a {@link ResultSetExtractor}.
*
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
* must not be {@literal null}.
* @param rse object that will extract results, must not be {@literal null}.
* @return an arbitrary result object, as returned by the {@link ResultSetExtractor}
* @throws DataAccessException if there is any problem
*/
<T> ListenableFuture<T> query(AsyncPreparedStatementCreator psc, ResultSetExtractor<T> rse) throws DataAccessException;
/**
* Query using a prepared statement, reading the {@link ResultSet} with a {@link ResultSetExtractor}.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @param rse object that will extract results, must not be {@literal null}.
* @return an arbitrary result object, as returned by the {@link ResultSetExtractor}.
* @throws DataAccessException if there is any problem
*/
<T> ListenableFuture<T> query(String cql, PreparedStatementBinder psb, ResultSetExtractor<T> rse)
throws DataAccessException;
/**
* Query using a prepared statement and a {@link PreparedStatementBinder} implementation that knows how to bind values
* to the query, reading the {@link ResultSet} with a {@link ResultSetExtractor}.
*
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
* must not be {@literal null}.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @param rse object that will extract results, must not be {@literal null}.
* @return an arbitrary result object, as returned by the {@link ResultSetExtractor}.
* @throws DataAccessException if there is any problem
*/
<T> ListenableFuture<T> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb, ResultSetExtractor<T> rse)
throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, reading the
* {@link ResultSet} with a {@link ResultSetExtractor}.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param rse object that will extract results, must not be {@literal null}.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type).
* @return an arbitrary result object, as returned by the {@link ResultSetExtractor}
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> query(String cql, ResultSetExtractor<T> rse, Object... args) throws DataAccessException;
/**
* Query using a prepared statement, reading the {@link ResultSet} on a per-row basis with a
* {@link RowCallbackHandler}.
*
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
* must not be {@literal null}.
* @param rch object that will extract results, one row at a time, must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> query(AsyncPreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a {@link PreparedStatementBinder} implementation that
* knows how to bind values to the query, reading the {@link ResultSet} on a per-row basis with a
* {@link RowCallbackHandler}.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @param rch object that will extract results, one row at a time, must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch)
throws DataAccessException;
/**
* Query using a prepared statement and a {@link PreparedStatementBinder} implementation that knows how to bind values
* to the query, reading the {@link ResultSet} on a per-row basis with a {@link RowCallbackHandler}.
*
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
* must not be {@literal null}.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @param rch object that will extract results, one row at a time, must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb, RowCallbackHandler rch)
throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, reading the
* {@link ResultSet} on a per-row basis with a {@link RowCallbackHandler}.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param rch object that will extract results, one row at a time, must not be {@literal null}.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type)
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> query(String cql, RowCallbackHandler rch, Object... args) throws DataAccessException;
/**
* Query using a prepared statement, mapping each row to a Java object via a {@link RowMapper}.
*
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
* must not be {@literal null}.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the result {@link List}, containing mapped objects.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a {@link PreparedStatementBinder} implementation that
* knows how to bind values to the query, mapping each row to a Java object via a {@link RowMapper}.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the result {@link List}, containing mapped objects.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> query(String cql, PreparedStatementBinder psb, RowMapper<T> rowMapper)
throws DataAccessException;
/**
* Query using a prepared statement and a {@link PreparedStatementBinder} implementation that knows how to bind values
* to the query, mapping each row to a Java object via a {@link RowMapper}.
*
* @param psc object that can create a {@link PreparedStatement} given a {@link com.datastax.driver.core.Session},
* must not be {@literal null}.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @return the result {@link List}, containing mapped objects.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb, RowMapper<T> rowMapper)
throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, mapping each
* row to a Java object via a {@link RowMapper}.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param rowMapper object that will map one object per row
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type)
* @return the result {@link List}, containing mapped objects
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper, Object... args) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, mapping a
* single result row to a Java object via a {@link RowMapper}.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param rowMapper object that will map one object per row, must not be {@literal null}.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type)
* @return the single mapped object
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper, Object... args) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
* result object.
* <p>
* The query is expected to be a single row/single column query; the returned result will be directly mapped to the
* corresponding object type.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param requiredType the type that the result object is expected to match, must not be {@literal null}.
* @param args arguments to bind to the query (leaving it to the PreparedStatement to guess the corresponding CQL
* type)
* @return the result object of the required type, or {@link Mono#empty()} in case of CQL NULL.
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row, or does not return
* exactly one column in that row.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForObject(String, Class)
*/
<T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType, Object... args) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
* result Map. The queryForMap() methods defined by this interface are appropriate when you don't have a domain model.
* Otherwise, consider using one of the queryForObject() methods.
* <p>
* The query is expected to be a single row query; the result row will be mapped to a Map (one entry for each column,
* using the column name as the key).
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type).
* @return the result Map (one entry for each column, using the column name as the key).
* @throws IncorrectResultSizeDataAccessException if the query does not return exactly one row
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForMap(String)
* @see ColumnMapRowMapper
*/
ListenableFuture<Map<String, Object>> queryForMap(String cql, Object... args) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
* result {@link List}.
* <p>
* The results will be mapped to a {@link List} (one item for each row) of result objects, each of them matching the
* specified element type.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param elementType the required type of element in the result {@link List} (for example, {@code Integer.class}),
* must not be {@literal null}.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type).
* @return a {@link List} of objects that match the specified element type.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String, Class)
* @see SingleColumnRowMapper
*/
<T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType, Object... args)
throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
* result {@link List}.
* <p>
* The results will be mapped to a {@link List} (one item for each row) of {@link Map}s (one entry for each column,
* using the column name as the key). Each item in the {@link List} will be of the form returned by this interface's
* queryForMap() methods.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type).
* @return a {@link List} that contains a {@link Map} per row
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForList(String)
*/
ListenableFuture<List<Map<String, Object>>> queryForList(String cql, Object... args) throws DataAccessException;
/**
* Query given CQL to create a prepared statement from CQL and a list of arguments to bind to the query, expecting a
* ResultSet.
* <p>
* The results will be mapped to an {@link ResultSet}.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type).
* @return a {@link ResultSet} representation.
* @throws DataAccessException if there is any problem executing the query.
* @see #queryForResultSet(String)
*/
ListenableFuture<ResultSet> queryForResultSet(String cql, Object... args) throws DataAccessException;
/**
* Issue a single CQL execute operation (such as an insert, update or delete statement) using a
* {@link AsyncPreparedStatementCreator} to provide CQL and any required parameters.
*
* @param psc object that provides CQL and any necessary parameters, must not be {@literal null}.
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem issuing the execution.
*/
// TODO: Interferes with execute(session callback lambda)
ListenableFuture<Boolean> execute(AsyncPreparedStatementCreator psc) throws DataAccessException;
/**
* Issue an statement using a {@link PreparedStatementBinder} to set bind parameters, with given CQL. Simpler than
* using a {@link AsyncPreparedStatementCreator} as this method will create the {@link PreparedStatement}: The
* {@link PreparedStatementBinder} just needs to set parameters.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param psb object that knows how to set values on the prepared statement. If this is {@literal null}, the CQL will
* be assumed to contain no bind parameters. Even if there are no bind parameters, this object may be used to
* set fetch size and other performance options.
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem issuing the execution.
*/
ListenableFuture<Boolean> execute(String cql, PreparedStatementBinder psb) throws DataAccessException;
/**
* Issue a single CQL operation (such as an insert, update or delete statement) via a prepared statement, binding the
* given arguments.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @param args arguments to bind to the query (leaving it to the {@link PreparedStatement} to guess the corresponding
* CQL type).
* @return boolean value whether the statement was applied.
* @throws DataAccessException if there is any problem issuing the execution.
*/
ListenableFuture<Boolean> execute(String cql, Object... args) throws DataAccessException;
}

View File

@@ -0,0 +1,973 @@
/*
* Copyright 2016 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.cassandra.core;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import java.util.stream.StreamSupport;
import org.springframework.cassandra.support.CassandraAccessor;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.SettableListenableFuture;
import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.ConsistencyLevel;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.SimpleStatement;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.exceptions.DriverException;
import com.datastax.driver.core.policies.RetryPolicy;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
/**
* <b>This is the central class in the CQL core package for asynchronous Cassandra data access.</b> It simplifies the
* use of CQL and helps to avoid common errors. It executes core CQL workflow, leaving application code to provide CQL
* and extract results. This class executes CQL queries or updates, initiating iteration over {@link ResultSet}s and
* catching {@link DriverException} exceptions and translating them to the generic, more informative exception hierarchy
* defined in the {@code org.springframework.dao} package.
* <p>
* Code using this class need only implement callback interfaces, giving them a clearly defined contract. The
* {@link PreparedStatementCreator} callback interface creates a prepared statement given a Connection, providing CQL
* and any necessary parameters. The {@link ResultSetExtractor} interface extracts values from a {@link ResultSet}. See
* also {@link PreparedStatementBinder} and {@link RowMapper} for two popular alternative callback interfaces.
* <p>
* Can be used within a service implementation via direct instantiation with a {@link Session} reference, or get
* prepared in an application context and given to services as bean reference. Note: The {@link Session} should always
* be configured as a bean in the application context, in the first case given to the service directly, in the second
* case to the prepared template.
* <p>
* Because this class is parameterizable by the callback interfaces and the
* {@link org.springframework.dao.support.PersistenceExceptionTranslator} interface, there should be no need to subclass
* it.
* <p>
* All CQL operations performed by this class are logged at debug level, using
* "org.springframework.cassandra.core.CqlTemplate" as log category.
* <p>
* <b>NOTE: An instance of this class is thread-safe once configured.</b>
*
* @author Mark Paluch
* @see ListenableFuture
* @see PreparedStatementCreator
* @see PreparedStatementBinder
* @see PreparedStatementCallback
* @see ResultSetExtractor
* @see RowCallbackHandler
* @see RowMapper
* @see org.springframework.dao.support.PersistenceExceptionTranslator
*/
public class AsyncCqlTemplate extends CassandraAccessor implements AsyncCqlOperations {
/**
* Placeholder for default values.
*/
private final static Statement DEFAULTS = QueryBuilder.select().from("DEFAULT");
/**
* If this variable is set to a non-negative value, it will be used for setting the {@code fetchSize} property on
* statements used for query processing.
*/
private int fetchSize = -1;
/**
* If this variable is set to a value, it will be used for setting the {@code retryPolicy} property on statements used
* for query processing.
*/
private com.datastax.driver.core.policies.RetryPolicy retryPolicy;
/**
* If this variable is set to a value, it will be used for setting the {@code consistencyLevel} property on statements
* used for query processing.
*/
private com.datastax.driver.core.ConsistencyLevel consistencyLevel;
/**
* Construct a new {@link AsyncCqlTemplate}. Note: The {@link Session} has to be set before using the instance.
*
* @see #setSession(Session)
*/
public AsyncCqlTemplate() {}
/**
* Construct a new {@link AsyncCqlTemplate}, given a {@link Session}.
*
* @param session the active Cassandra {@link Session}.
*/
public AsyncCqlTemplate(Session session) {
Assert.notNull(session, "Session must not be null");
setSession(session);
}
/**
* Set the fetch size for this {@link AsyncCqlTemplate}. This is important for processing large result sets: Setting
* this higher than the default value will increase processing speed at the cost of memory consumption; setting this
* lower can avoid transferring row data that will never be read by the application. Default is -1, indicating to use
* the CQL driver's default configuration (i.e. to not pass a specific fetch size setting on to the driver).
*
* @see Statement#setFetchSize(int)
*/
public void setFetchSize(int fetchSize) {
this.fetchSize = fetchSize;
}
/**
* @return the fetch size specified for this {@link AsyncCqlTemplate}.
*/
public int getFetchSize() {
return this.fetchSize;
}
/**
* Set the retry policy for this {@link AsyncCqlTemplate}. This is important for defining behavior when a request
* fails.
*
* @see Statement#setRetryPolicy(com.datastax.driver.core.policies.RetryPolicy)
* @see com.datastax.driver.core.policies.RetryPolicy
*/
public void setRetryPolicy(com.datastax.driver.core.policies.RetryPolicy retryPolicy) {
this.retryPolicy = retryPolicy;
}
/**
* @return the {@link com.datastax.driver.core.policies.RetryPolicy} specified for this {@link AsyncCqlTemplate}.
*/
public com.datastax.driver.core.policies.RetryPolicy getRetryPolicy() {
return retryPolicy;
}
/**
* Set the consistency level for this {@link AsyncCqlTemplate}. Consistency level defines the number of nodes involved
* into query processing. Relaxed consistency level settings use fewer nodes but eventual consistency is more likely
* to occur while a higher consistency level involves more nodes to obtain results with a higher consistency
* guarantee.
*
* @see Statement#setConsistencyLevel(com.datastax.driver.core.ConsistencyLevel)
* @see com.datastax.driver.core.policies.RetryPolicy
*/
public void setConsistencyLevel(com.datastax.driver.core.ConsistencyLevel consistencyLevel) {
this.consistencyLevel = consistencyLevel;
}
/**
* @return the {@link com.datastax.driver.core.ConsistencyLevel} specified for this {@link AsyncCqlTemplate}.
*/
public com.datastax.driver.core.ConsistencyLevel getConsistencyLevel() {
return consistencyLevel;
}
// -------------------------------------------------------------------------
// Methods dealing with a plain com.datastax.driver.core.Session
// -------------------------------------------------------------------------
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(org.springframework.cassandra.core.AsyncSessionCallback)
*/
@Override
public <T> ListenableFuture<T> execute(AsyncSessionCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
try {
return action.doInSession(getSession());
} catch (DriverException e) {
throw translateException("SessionCallback", getCql(action), e);
}
}
// -------------------------------------------------------------------------
// Methods dealing with static CQL
// -------------------------------------------------------------------------
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(java.lang.String)
*/
@Override
public ListenableFuture<Boolean> execute(String cql) throws DataAccessException {
Assert.hasText(cql, "CQL must not be empty");
return new MappingListenableFutureAdapter<>(queryForResultSet(cql), ResultSet::wasApplied);
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.ResultSetExtractor)
*/
@Override
public <T> ListenableFuture<T> query(String cql, ResultSetExtractor<T> rse) throws DataAccessException {
Assert.hasText(cql, "CQL must not be empty");
Assert.notNull(rse, "ResultSetExtractor must not be null");
try {
if (logger.isDebugEnabled()) {
logger.debug("Executing CQL Statement [{}]", cql);
}
SimpleStatement simpleStatement = new SimpleStatement(cql);
applyStatementSettings(simpleStatement);
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(
new GuavaListenableFutureAdapter<>(getSession().executeAsync(simpleStatement),
ex -> translateExceptionIfPossible("Query", cql, ex)),
rse::extractData), getExceptionTranslator());
} catch (DriverException e) {
throw translateException("Query", cql, e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowCallbackHandler)
*/
@Override
public ListenableFuture<Void> query(String cql, RowCallbackHandler rch) throws DataAccessException {
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(query(cql, new RowCallbackHandlerResultSetExtractor(rch)), o -> null),
getExceptionTranslator());
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowMapper)
*/
@Override
public <T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper) throws DataAccessException {
return query(cql, new RowMapperResultSetExtractor<>(rowMapper));
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(java.lang.String, org.springframework.cassandra.core.RowMapper)
*/
@Override
public <T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper) throws DataAccessException {
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(query(cql, rowMapper), DataAccessUtils::requiredSingleResult),
getExceptionTranslator());
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(java.lang.String, java.lang.Class)
*/
@Override
public <T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType) throws DataAccessException {
return queryForObject(cql, getSingleColumnRowMapper(requiredType));
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForMap(java.lang.String)
*/
@Override
public ListenableFuture<Map<String, Object>> queryForMap(String cql) throws DataAccessException {
return queryForObject(cql, getColumnMapRowMapper());
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(java.lang.String, java.lang.Class)
*/
@Override
public <T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType) throws DataAccessException {
return query(cql, getSingleColumnRowMapper(elementType));
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(java.lang.String)
*/
@Override
public ListenableFuture<List<Map<String, Object>>> queryForList(String cql) throws DataAccessException {
return query(cql, getColumnMapRowMapper());
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForResultSet(java.lang.String)
*/
@Override
public ListenableFuture<ResultSet> queryForResultSet(String cql) throws DataAccessException {
return query(cql, rs -> rs);
}
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.driver.core.Statement
// -------------------------------------------------------------------------
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(com.datastax.driver.core.Statement)
*/
@Override
public ListenableFuture<Boolean> execute(Statement statement) throws DataAccessException {
Assert.notNull(statement, "CQL Statement must not be null");
return new MappingListenableFutureAdapter<>(queryForResultSet(statement), ResultSet::wasApplied);
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.ResultSetExtractor)
*/
@Override
public <T> ListenableFuture<T> query(Statement statement, ResultSetExtractor<T> rse) throws DataAccessException {
Assert.notNull(statement, "CQL Statement must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null");
try {
if (logger.isDebugEnabled()) {
logger.debug("Executing CQL Statement [{}]", statement);
}
applyStatementSettings(statement);
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(new GuavaListenableFutureAdapter<>(getSession().executeAsync(statement),
ex -> translateExceptionIfPossible("Query", statement.toString(), ex)), rse::extractData),
getExceptionTranslator());
} catch (DriverException e) {
throw translateException("Query", statement.toString(), e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowCallbackHandler)
*/
@Override
public ListenableFuture<Void> query(Statement statement, RowCallbackHandler rch) throws DataAccessException {
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(
query(statement, new RowCallbackHandlerResultSetExtractor(rch)), o -> null), getExceptionTranslator());
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowMapper)
*/
@Override
public <T> ListenableFuture<List<T>> query(Statement statement, RowMapper<T> rowMapper) throws DataAccessException {
return query(statement, new RowMapperResultSetExtractor<>(rowMapper));
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(com.datastax.driver.core.Statement, org.springframework.cassandra.core.RowMapper)
*/
@Override
public <T> ListenableFuture<T> queryForObject(Statement statement, RowMapper<T> rowMapper)
throws DataAccessException {
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(query(statement, rowMapper), DataAccessUtils::requiredSingleResult),
getExceptionTranslator());
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(com.datastax.driver.core.Statement, java.lang.Class)
*/
@Override
public <T> ListenableFuture<T> queryForObject(Statement statement, Class<T> requiredType) throws DataAccessException {
return queryForObject(statement, getSingleColumnRowMapper(requiredType));
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForMap(com.datastax.driver.core.Statement)
*/
@Override
public ListenableFuture<Map<String, Object>> queryForMap(Statement statement) throws DataAccessException {
return queryForObject(statement, getColumnMapRowMapper());
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(com.datastax.driver.core.Statement, java.lang.Class)
*/
@Override
public <T> ListenableFuture<List<T>> queryForList(Statement statement, Class<T> elementType)
throws DataAccessException {
return query(statement, getSingleColumnRowMapper(elementType));
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(com.datastax.driver.core.Statement)
*/
@Override
public ListenableFuture<List<Map<String, Object>>> queryForList(Statement statement) throws DataAccessException {
return query(statement, getColumnMapRowMapper());
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForResultSet(com.datastax.driver.core.Statement)
*/
@Override
public ListenableFuture<ResultSet> queryForResultSet(Statement statement) throws DataAccessException {
return query(statement, rs -> rs);
}
// -------------------------------------------------------------------------
// Methods dealing with prepared statements
// -------------------------------------------------------------------------
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementCallback)
*/
@Override
public <T> ListenableFuture<T> execute(AsyncPreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException {
Assert.notNull(psc, "PreparedStatementCreator must not be null");
Assert.notNull(action, "PreparedStatementCallback object must not be null");
try {
if (logger.isDebugEnabled()) {
logger.debug("Preparing statement [{}] using {}", getCql(psc), psc);
}
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(psc.createPreparedStatement(getSession()), preparedStatement -> {
try {
applyStatementSettings(preparedStatement);
return action.doInPreparedStatement(preparedStatement);
} catch (DriverException e) {
throw translateException("PreparedStatementCallback", preparedStatement.toString(), e);
}
}), getExceptionTranslator());
} catch (DriverException e) {
throw translateException("PreparedStatementCallback", getCql(psc), e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.ResultSetExtractor)
*/
@Override
public <T> ListenableFuture<T> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb,
ResultSetExtractor<T> rse) throws DataAccessException {
Assert.notNull(psc, "AsyncPreparedStatementCreator must not be null");
Assert.notNull(rse, "ResultSetExtractor object must not be null");
try {
if (logger.isDebugEnabled()) {
logger.debug("Preparing statement [{}] using {}", getCql(psc), psc);
}
Session session = getSession();
PersistenceExceptionTranslator exceptionTranslator = ex -> translateExceptionIfPossible("Query", getCql(psc), ex);
ListenableFuture<BoundStatement> psFuture = new MappingListenableFutureAdapter<>(
psc.createPreparedStatement(session), ps -> {
if (logger.isDebugEnabled()) {
logger.debug("Executing prepared statement [{}]", ps);
}
BoundStatement boundStatement = psb != null ? psb.bindValues(ps) : ps.bind();
applyStatementSettings(boundStatement);
return boundStatement;
});
SettableListenableFuture<T> settableListenableFuture = new SettableListenableFuture<T>();
psFuture.addCallback(boundStatement -> {
Futures.addCallback(session.executeAsync(boundStatement), new FutureCallback<ResultSet>() {
@Override
public void onSuccess(ResultSet result) {
try {
settableListenableFuture.set(rse.extractData(result));
} catch (DriverException e) {
settableListenableFuture.setException(exceptionTranslator.translateExceptionIfPossible(e));
}
}
@Override
public void onFailure(Throwable ex) {
if (ex instanceof DriverException) {
settableListenableFuture
.setException(exceptionTranslator.translateExceptionIfPossible((DriverException) ex));
} else {
settableListenableFuture.setException(ex);
}
}
});
}, ex -> {
if (ex instanceof DriverException) {
settableListenableFuture.setException(exceptionTranslator.translateExceptionIfPossible((DriverException) ex));
} else {
settableListenableFuture.setException(ex);
}
});
return settableListenableFuture;
} catch (DriverException e) {
throw translateException("Query", getCql(psc), e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(java.lang.String, org.springframework.cassandra.core.PreparedStatementCallback)
*/
@Override
public <T> ListenableFuture<T> execute(String cql, PreparedStatementCallback<T> action) throws DataAccessException {
return execute(newAsyncPreparedStatementCreator(cql), action);
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.ResultSetExtractor)
*/
@Override
public <T> ListenableFuture<T> query(AsyncPreparedStatementCreator psc, ResultSetExtractor<T> rse)
throws DataAccessException {
return query(psc, null, rse);
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.ResultSetExtractor)
*/
@Override
public <T> ListenableFuture<T> query(String cql, PreparedStatementBinder psb, ResultSetExtractor<T> rse)
throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), psb, rse);
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.ResultSetExtractor, java.lang.Object[])
*/
@Override
public <T> ListenableFuture<T> query(String cql, ResultSetExtractor<T> rse, Object... args)
throws DataAccessException {
return query(cql, newArgPreparedStatementBinder(args), rse);
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.RowCallbackHandler)
*/
@Override
public ListenableFuture<Void> query(AsyncPreparedStatementCreator psc, RowCallbackHandler rch)
throws DataAccessException {
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(query(psc, new RowCallbackHandlerResultSetExtractor(rch)), o -> null),
getExceptionTranslator());
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowCallbackHandler)
*/
@Override
public ListenableFuture<Void> query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch)
throws DataAccessException {
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(query(cql, psb, new RowCallbackHandlerResultSetExtractor(rch)), o -> null),
getExceptionTranslator());
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowCallbackHandler)
*/
@Override
public ListenableFuture<Void> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb,
RowCallbackHandler rch) throws DataAccessException {
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(query(psc, psb, new RowCallbackHandlerResultSetExtractor(rch)), o -> null),
getExceptionTranslator());
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowCallbackHandler, java.lang.Object[])
*/
@Override
public ListenableFuture<Void> query(String cql, RowCallbackHandler rch, Object... args) throws DataAccessException {
return new ExceptionTranslatingListenableFutureAdapter<>(
new MappingListenableFutureAdapter<>(query(newAsyncPreparedStatementCreator(cql),
newArgPreparedStatementBinder(args), new RowCallbackHandlerResultSetExtractor(rch)), o -> null),
getExceptionTranslator());
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.RowMapper)
*/
@Override
public <T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator psc, RowMapper<T> rowMapper)
throws DataAccessException {
return query(psc, new RowMapperResultSetExtractor<>(rowMapper));
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowMapper)
*/
@Override
public <T> ListenableFuture<List<T>> query(String cql, PreparedStatementBinder psb, RowMapper<T> rowMapper)
throws DataAccessException {
return query(cql, psb, new RowMapperResultSetExtractor<>(rowMapper));
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(org.springframework.cassandra.core.AsyncPreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowMapper)
*/
@Override
public <T> ListenableFuture<List<T>> query(AsyncPreparedStatementCreator psc, PreparedStatementBinder psb,
RowMapper<T> rowMapper) throws DataAccessException {
return query(psc, psb, new RowMapperResultSetExtractor<>(rowMapper));
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#query(java.lang.String, org.springframework.cassandra.core.RowMapper, java.lang.Object[])
*/
@Override
public <T> ListenableFuture<List<T>> query(String cql, RowMapper<T> rowMapper, Object... args)
throws DataAccessException {
return query(cql, newArgPreparedStatementBinder(args), new RowMapperResultSetExtractor<>(rowMapper));
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(java.lang.String, org.springframework.cassandra.core.RowMapper, java.lang.Object[])
*/
@Override
public <T> ListenableFuture<T> queryForObject(String cql, RowMapper<T> rowMapper, Object... args)
throws DataAccessException {
return new ExceptionTranslatingListenableFutureAdapter<>(new MappingListenableFutureAdapter<>(
query(cql, newArgPreparedStatementBinder(args), new RowMapperResultSetExtractor<>(rowMapper, 1)),
DataAccessUtils::requiredSingleResult), getExceptionTranslator());
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForObject(java.lang.String, java.lang.Class, java.lang.Object[])
*/
@Override
public <T> ListenableFuture<T> queryForObject(String cql, Class<T> requiredType, Object... args)
throws DataAccessException {
return queryForObject(cql, getSingleColumnRowMapper(requiredType), args);
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForMap(java.lang.String, java.lang.Object[])
*/
@Override
public ListenableFuture<Map<String, Object>> queryForMap(String cql, Object... args) throws DataAccessException {
return queryForObject(cql, getColumnMapRowMapper(), args);
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(java.lang.String, java.lang.Class, java.lang.Object[])
*/
@Override
public <T> ListenableFuture<List<T>> queryForList(String cql, Class<T> elementType, Object... args)
throws DataAccessException {
return query(cql, getSingleColumnRowMapper(elementType), args);
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForList(java.lang.String, java.lang.Object[])
*/
@Override
public ListenableFuture<List<Map<String, Object>>> queryForList(String cql, Object... args)
throws DataAccessException {
return query(cql, getColumnMapRowMapper(), args);
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#queryForResultSet(java.lang.String, java.lang.Object[])
*/
@Override
public ListenableFuture<ResultSet> queryForResultSet(String cql, Object... args) throws DataAccessException {
return query(cql, rs -> rs, args);
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(org.springframework.cassandra.core.AsyncPreparedStatementCreator)
*/
@Override
public ListenableFuture<Boolean> execute(AsyncPreparedStatementCreator psc) throws DataAccessException {
return query(psc, ResultSet::wasApplied);
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(java.lang.String, org.springframework.cassandra.core.PreparedStatementBinder)
*/
@Override
public ListenableFuture<Boolean> execute(String cql, PreparedStatementBinder psb) throws DataAccessException {
return query(newAsyncPreparedStatementCreator(cql), psb, ResultSet::wasApplied);
}
/*
* (non-Javadoc)
* @see org.springframework.cassandra.core.AsyncCqlOperations#execute(java.lang.String, java.lang.Object[])
*/
@Override
public ListenableFuture<Boolean> execute(String cql, Object... args) throws DataAccessException {
return execute(cql, newArgPreparedStatementBinder(args));
}
// -------------------------------------------------------------------------
// Implementation hooks and helper methods
// -------------------------------------------------------------------------
/**
* Translate the given {@link DriverException} into a generic {@link DataAccessException}.
*
* @param task readable text describing the task being attempted
* @param cql CQL query or update that caused the problem (may be {@code null})
* @param ex the offending {@code RuntimeException}.
* @return the translated {@link DataAccessException} or {@literal null} if translation not possible.
* @see CqlProvider
*/
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
protected DataAccessException translateExceptionIfPossible(String task, String cql, RuntimeException ex) {
if (ex instanceof DriverException) {
return translate(task, cql, (DriverException) ex);
}
return null;
}
/**
* Translate the given {@link DriverException} into a generic {@link DataAccessException}.
*
* @param task readable text describing the task being attempted
* @param cql CQL query or update that caused the problem (may be {@code null})
* @param ex the offending {@code RuntimeException}.
* @return the exception translation {@link Function}
* @see CqlProvider
*/
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
protected DataAccessException translateException(String task, String cql, DriverException ex) {
return translate(task, cql, ex);
}
/**
* Create a new RowMapper for reading columns as key-value pairs.
*
* @return the RowMapper to use
* @see ColumnMapRowMapper
*/
protected RowMapper<Map<String, Object>> getColumnMapRowMapper() {
return new ColumnMapRowMapper();
}
/**
* Create a new RowMapper for reading result objects from a single column.
*
* @param requiredType the type that each result object is expected to match
* @return the RowMapper to use
* @see SingleColumnRowMapper
*/
protected <T> RowMapper<T> getSingleColumnRowMapper(Class<T> requiredType) {
return SingleColumnRowMapper.newInstance(requiredType);
}
/**
* Prepare the given CQL Statement (or {@link com.datastax.driver.core.PreparedStatement}), applying statement
* settings such as fetch size, retry policy, and consistency level.
*
* @param stmt the CQL Statement to prepare
* @see #setFetchSize(int)
* @see #setRetryPolicy(RetryPolicy)
* @see #setConsistencyLevel(ConsistencyLevel)
*/
protected void applyStatementSettings(Statement stmt) {
int fetchSize = getFetchSize();
if (fetchSize != -1 && stmt.getFetchSize() == DEFAULTS.getFetchSize()) {
stmt.setFetchSize(fetchSize);
}
RetryPolicy retryPolicy = getRetryPolicy();
if (retryPolicy != null && stmt.getRetryPolicy() == DEFAULTS.getRetryPolicy()) {
stmt.setRetryPolicy(retryPolicy);
}
ConsistencyLevel consistencyLevel = getConsistencyLevel();
if (consistencyLevel != null && stmt.getConsistencyLevel() == DEFAULTS.getConsistencyLevel()) {
stmt.setConsistencyLevel(consistencyLevel);
}
}
/**
* Prepare the given CQL Statement (or {@link com.datastax.driver.core.PreparedStatement}), applying statement
* settings such as retry policy and consistency level.
*
* @param stmt the CQL Statement to prepare
* @see #setRetryPolicy(RetryPolicy)
* @see #setConsistencyLevel(ConsistencyLevel)
*/
protected void applyStatementSettings(PreparedStatement stmt) {
RetryPolicy retryPolicy = getRetryPolicy();
if (retryPolicy != null) {
stmt.setRetryPolicy(retryPolicy);
}
ConsistencyLevel consistencyLevel = getConsistencyLevel();
if (consistencyLevel != null) {
stmt.setConsistencyLevel(consistencyLevel);
}
}
/**
* Create a new arg-based PreparedStatementSetter using the args passed in. By default, we'll create an
* {@link ArgumentPreparedStatementBinder}. This method allows for the creation to be overridden by subclasses.
*
* @param args object array with arguments
* @return the new {@link PreparedStatementBinder} to use
*/
protected PreparedStatementBinder newArgPreparedStatementBinder(Object[] args) {
return new ArgumentPreparedStatementBinder(args);
}
/**
* Create a new CQL-based AsyncPreparedStatementCreator using the CQL passed in. By default, we'll create an
* {@link SimpleAsyncPreparedStatementCreator}. This method allows for the creation to be overridden by subclasses.
*
* @param cql static CQL to execute, must not be empty or {@literal null}.
* @return the new {@link AsyncPreparedStatementCreator} to use
*/
protected AsyncPreparedStatementCreator newAsyncPreparedStatementCreator(String cql) {
return new SimpleAsyncPreparedStatementCreator(cql,
ex -> translateExceptionIfPossible("PrepareStatement", cql, ex));
}
/**
* Determine CQL from potential provider object.
*
* @param cqlProvider object that's potentially a {@link CqlProvider}
* @return the CQL string, or {@code null}
* @see CqlProvider
*/
private static String getCql(Object cqlProvider) {
if (cqlProvider instanceof CqlProvider) {
return ((CqlProvider) cqlProvider).getCql();
} else {
return null;
}
}
private static class SimpleAsyncPreparedStatementCreator implements AsyncPreparedStatementCreator, CqlProvider {
private final String cql;
private final PersistenceExceptionTranslator persistenceExceptionTranslator;
SimpleAsyncPreparedStatementCreator(String cql, PersistenceExceptionTranslator persistenceExceptionTranslator) {
Assert.notNull(cql, "CQL must not be null");
this.cql = cql;
this.persistenceExceptionTranslator = persistenceExceptionTranslator;
}
@Override
public ListenableFuture<PreparedStatement> createPreparedStatement(Session session) throws DriverException {
return new GuavaListenableFutureAdapter<>(session.prepareAsync(cql), persistenceExceptionTranslator);
}
@Override
public String getCql() {
return cql;
}
}
private static class MappingListenableFutureAdapter<T, S>
extends org.springframework.util.concurrent.ListenableFutureAdapter<T, S> {
private final Function<S, T> mapper;
public MappingListenableFutureAdapter(ListenableFuture<S> adaptee, Function<S, T> mapper) {
super(adaptee);
this.mapper = mapper;
}
@Override
protected T adapt(S adapteeResult) throws ExecutionException {
return mapper.apply(adapteeResult);
}
}
/**
* Adapter to enable use of a {@link RowCallbackHandler} inside a {@link ResultSetExtractor}.
*/
private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor<Object> {
private final RowCallbackHandler rch;
public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) {
this.rch = rch;
}
@Override
public Object extractData(ResultSet rs) {
StreamSupport.stream(rs.spliterator(), false).forEach(rch::processRow);
return null;
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2016 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.cassandra.core;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.DriverException;
/**
* One of the two central callback interfaces used by the {@link AsyncCqlTemplate} class. This interface prepares a CQL
* statement returning a {@link org.springframework.util.concurrent.ListenableFuture} given a {@link Session}, provided
* by the {@link CqlTemplate} class.
* <p>
* Implementations may either create new prepared statements or reuse cached instances. Implementations do not need to
* concern themselves with {@link DriverException}s that may be thrown from operations they attempt. The
* {@link AsyncCqlTemplate} class will catch and handle {@link DriverException}s appropriately.
* <p>
* A {@link AsyncPreparedStatementCreator} should also implement the {@link CqlProvider} interface if it is able to
* provide the CQL it uses for {@link PreparedStatement} creation. This allows for better contextual information in case
* of exceptions.
*
* @author Mark Paluch
* @since 2.0
*/
public interface AsyncPreparedStatementCreator {
/**
* Create a statement in this session. Allows implementations to use {@link PreparedStatement}s. The
* {@link CqlTemplate} will attempt to cache the {@link PreparedStatement}s for future use without the overhead of
* re-preparing on the entire cluster.
*
* @param session Session to use to create statement, must not be {@literal null}.
* @return a prepared statement
* @throws DriverException there is no need to catch DriverException that may be thrown in the implementation of this
* method. The {@link CqlTemplate} class will handle them.
*/
ListenableFuture<PreparedStatement> createPreparedStatement(Session session) throws DriverException;
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2016 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.cassandra.core;
import org.springframework.dao.DataAccessException;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.DriverException;
import org.springframework.util.concurrent.ListenableFuture;
/**
* Generic callback interface for code that operates asynchronously on a Cassandra {@link Session}. Allows to execute any number of
* operations on a single session, using any type and number of statements.
* <p>
* This is particularly useful for delegating to existing data access code that expects a {@link Session} to work on and
* throws {@link DriverException}. For newly written code, it is strongly recommended to use {@link CqlTemplate}'s more
* specific operations, for example a {@code query} or {@code update} variant.
*
* @author David Webb
* @author Mark Paluch
* @see AsyncCqlTemplate#execute(AsyncSessionCallback)
* @see AsyncCqlTemplate#query
*/
public interface AsyncSessionCallback<T> {
/**
* Gets called by {@link CqlTemplate#execute} with an active Cassandra {@link Session}. Does not need to care about
* activating or closing the {@link Session}.
* <p>
* Allows for returning a result object created within the callback, i.e. a domain object or a collection of domain
* objects. Note that there's special support for single step actions: see {@link CqlTemplate#queryForObject} etc. A
* thrown {@link RuntimeException} is treated as application exception: it gets propagated to the caller of the
* template.
*
* @param session active Cassandra Session, must not be {@literal null}.
* @return a result object, or {@code null} if none.
* @throws DriverException if thrown by a Session method, to be auto-converted to a {@link DataAccessException}.
* @throws DataAccessException in case of custom exceptions.
* @see AsyncCqlTemplate#queryForObject(String, Class)
* @see AsyncCqlTemplate#queryForResultSet(String)
*/
ListenableFuture<T> doInSession(Session session) throws DriverException, DataAccessException;
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright 2016 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.cassandra.core;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.FailureCallback;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.util.concurrent.SettableListenableFuture;
import org.springframework.util.concurrent.SuccessCallback;
/**
* Adapter class to {@link ListenableFuture} {@link ExecutionException} by applying a
* {@link PersistenceExceptionTranslator}.
*
* @author Mark Paluch
* @since 2.0
*/
class ExceptionTranslatingListenableFutureAdapter<T> implements ListenableFuture<T> {
private final ListenableFuture<T> adaptee;
private final ListenableFuture<T> future;
/**
* Create a new {@link ExceptionTranslatingListenableFutureAdapter} given a {@link ListenableFuture} and a
* {@link PersistenceExceptionTranslator}.
*
* @param adaptee must not be {@literal null}.
* @param persistenceExceptionTranslator must not be {@literal null}.
*/
public ExceptionTranslatingListenableFutureAdapter(ListenableFuture<T> adaptee,
PersistenceExceptionTranslator persistenceExceptionTranslator) {
Assert.notNull(adaptee, "ListenableFuture must not be null");
Assert.notNull(persistenceExceptionTranslator, "PersistenceExceptionTranslator must not be null");
this.adaptee = adaptee;
this.future = adaptListenableFuture(adaptee, persistenceExceptionTranslator);
}
private static <T> ListenableFuture adaptListenableFuture(ListenableFuture<T> listenableFuture,
PersistenceExceptionTranslator exceptionTranslator) {
SettableListenableFuture<T> settableFuture = new SettableListenableFuture<T>();
listenableFuture.addCallback(new ListenableFutureCallback<T>() {
@Override
public void onSuccess(T result) {
settableFuture.set(result);
}
@Override
public void onFailure(Throwable ex) {
if (ex instanceof RuntimeException) {
DataAccessException dataAccessException = exceptionTranslator
.translateExceptionIfPossible((RuntimeException) ex);
if (dataAccessException != null) {
settableFuture.setException(dataAccessException);
return;
}
}
settableFuture.setException(ex);
}
});
return settableFuture;
}
/*
* (non-Javadoc)
* @see org.springframework.util.concurrent.ListenableFuture#addCallback(org.springframework.util.concurrent.ListenableFutureCallback)
*/
@Override
public void addCallback(ListenableFutureCallback<? super T> callback) {
future.addCallback(callback);
}
/*
* (non-Javadoc)
* @see org.springframework.util.concurrent.ListenableFuture#addCallback(org.springframework.util.concurrent.SuccessCallback, org.springframework.util.concurrent.FailureCallback)
*/
@Override
public void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback) {
future.addCallback(successCallback, failureCallback);
}
/*
* (non-Javadoc)
* @see java.util.concurrent.Future#cancel(boolean)
*/
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return adaptee.cancel(mayInterruptIfRunning);
}
/*
* (non-Javadoc)
* @see java.util.concurrent.Future#isCancelled()
*/
@Override
public boolean isCancelled() {
return adaptee.isCancelled();
}
/*
* (non-Javadoc)
* @see java.util.concurrent.Future#isDone()
*/
@Override
public boolean isDone() {
return future.isDone();
}
/*
* (non-Javadoc)
* @see java.util.concurrent.Future#get()
*/
@Override
public T get() throws InterruptedException, ExecutionException {
return future.get();
}
/*
* (non-Javadoc)
* @see java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit)
*/
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return future.get(timeout, unit);
}
}

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2016 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.cassandra.core;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.FailureCallback;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.util.concurrent.SettableListenableFuture;
import org.springframework.util.concurrent.SuccessCallback;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
/**
* Adapter class to adapt Guava's {@link com.google.common.util.concurrent.ListenableFuture} into a Spring
* {@link ListenableFuture}.
*
* @author Mark Paluch
* @since 2.0
*/
public class GuavaListenableFutureAdapter<T> implements ListenableFuture<T> {
private final com.google.common.util.concurrent.ListenableFuture<T> adaptee;
private final ListenableFuture<T> future;
/**
* Create a new {@link GuavaListenableFutureAdapter} given a Guava
* {@link com.google.common.util.concurrent.ListenableFuture} and a {@link PersistenceExceptionTranslator}.
*
* @param adaptee must not be {@literal null}.
* @param persistenceExceptionTranslator must not be {@literal null}.
*/
public GuavaListenableFutureAdapter(com.google.common.util.concurrent.ListenableFuture<T> adaptee,
PersistenceExceptionTranslator persistenceExceptionTranslator) {
Assert.notNull(adaptee, "ListenableFuture must not be null");
Assert.notNull(persistenceExceptionTranslator, "PersistenceExceptionTranslator must not be null");
this.adaptee = adaptee;
this.future = adaptListenableFuture(adaptee, persistenceExceptionTranslator);
}
private static <T> ListenableFuture adaptListenableFuture(
com.google.common.util.concurrent.ListenableFuture<T> guavaFuture,
PersistenceExceptionTranslator exceptionTranslator) {
SettableListenableFuture<T> settableFuture = new SettableListenableFuture<T>();
Futures.addCallback(guavaFuture, new FutureCallback<T>() {
@Override
public void onSuccess(T result) {
settableFuture.set(result);
}
@Override
public void onFailure(Throwable t) {
if (t instanceof RuntimeException) {
DataAccessException dataAccessException = exceptionTranslator
.translateExceptionIfPossible((RuntimeException) t);
if (dataAccessException != null) {
settableFuture.setException(dataAccessException);
return;
}
}
settableFuture.setException(t);
}
});
return settableFuture;
}
/*
* (non-Javadoc)
* @see org.springframework.util.concurrent.ListenableFuture#addCallback(org.springframework.util.concurrent.ListenableFutureCallback)
*/
@Override
public void addCallback(ListenableFutureCallback<? super T> callback) {
future.addCallback(callback);
}
/*
* (non-Javadoc)
* @see org.springframework.util.concurrent.ListenableFuture#addCallback(org.springframework.util.concurrent.SuccessCallback, org.springframework.util.concurrent.FailureCallback)
*/
@Override
public void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback) {
future.addCallback(successCallback, failureCallback);
}
/*
* (non-Javadoc)
* @see java.util.concurrent.Future#cancel(boolean)
*/
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return adaptee.cancel(mayInterruptIfRunning);
}
/*
* (non-Javadoc)
* @see java.util.concurrent.Future#isCancelled()
*/
@Override
public boolean isCancelled() {
return adaptee.isCancelled();
}
/*
* (non-Javadoc)
* @see java.util.concurrent.Future#isDone()
*/
@Override
public boolean isDone() {
return future.isDone();
}
/*
* (non-Javadoc)
* @see java.util.concurrent.Future#get()
*/
@Override
public T get() throws InterruptedException, ExecutionException {
return future.get();
}
/*
* (non-Javadoc)
* @see java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit)
*/
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return future.get(timeout, unit);
}
}

View File

@@ -16,13 +16,30 @@
package org.springframework.cassandra.core;
import java.util.Collection;
import java.util.Set;
import com.datastax.driver.core.Host;
import com.datastax.driver.core.exceptions.DriverException;
/**
* An interface used by {@link CqlTemplate} for mapping {@link Host}s of a {@link com.datastax.driver.core.Metadata} on
* a per-item basis.. Implementations of this interface perform the actual work of mapping each host to a result object,
* but don't need to worry about exception handling. {@link DriverException} will be caught and handled by the calling
* {@link CqlTemplate}.
*
* @author Matthew T. Adams
* @author Mark Paluch
* @see CqlTemplate
*/
public interface HostMapper<T> {
Collection<T> mapHosts(Set<Host> host) throws DriverException;
/**
* Implementations must implement this method to map each {@link Host} in the
* {@link com.datastax.driver.core.Metadata}.
*
* @param hosts the {@link Iterable} of {@link Host}s to map, must not be {@literal null}.
* @return the result objects for the given hosts.
* @throws DriverException if a {@link DriverException} is encountered mapping values (that is, there's no need to
* catch {@link DriverException}).
*/
Collection<T> mapHosts(Iterable<Host> hosts) throws DriverException;
}

View File

@@ -18,13 +18,42 @@ package org.springframework.cassandra.core;
import org.springframework.dao.DataAccessException;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.exceptions.DriverException;
/**
* Generic callback interface for code that operates on a {@link PreparedStatement}. Allows to execute any number of
* operations on a single {@link PreparedStatement}, for example a single {@link Session#execute(Statement).
* <p>
* Used internally by {@link CqlTemplate}, but also useful for application code. Note that the passed-in
* {@link PreparedStatement} can have been created by the framework or by a custom {@link PreparedStatementCreator}.
* However, the latter is hardly ever necessary, as most custom callback actions will perform updates in which case a
* standard {@link PreparedStatement is fine. Custom actions will always set parameter values themselves, so that
* {@link PreparedStatementCreator} capability is not needed either.
*
* @author David Webb
* @author Mark Paluch
* @see CqlTemplate#execute(String, PreparedStatementCallback)
* @see CqlTemplate#execute(PreparedStatementCreator, PreparedStatementCallback)
*/
public interface PreparedStatementCallback<T> {
/**
* Gets called by {@link CqlTemplate#execute(String, PreparedStatementCallback)} with a {@link PreparedStatement}.
* <p>
* Allows for returning a result object created within the callback, i.e. a domain object or a collection of domain
* objects. Note that there's special support for single step actions: see
* {@link CqlTemplate#queryForObject(String, Class, Object...)} etc. A thrown RuntimeException is treated as
* application exception, it gets propagated to the caller of the template.
*
* @param ps the {@link PreparedStatement}, must not be {@literal null}.
* @return a result object publisher.
* @throws DriverException if thrown by a session method, to be auto-converted to a DataAccessException.
* @throws DataAccessException in case of custom exceptions.
* @see CqlTemplate#queryForObject(String, Class, Object...)
* @see CqlTemplate#queryForList(String, Object...)
*/
T doInPreparedStatement(PreparedStatement ps) throws DriverException, DataAccessException;
}

View File

@@ -20,21 +20,27 @@ import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.DriverException;
/**
* Creates a PreparedStatement for the usage with the DataStax Java Driver
*
* One of the two central callback interfaces used by the {@link CqlTemplate} class. This interface creates a
* {@link PreparedStatement} given a session, provided by the {@link CqlTemplate} class. Implementations are responsible
* for providing CQL and any necessary parameters.
* <p>
* Implementations <i>do not</i> need to concern themselves with {@link DriverException}s that may be thrown from
* operations they attempt. The {@link CqlTemplate} class will catch and handle {@link DriverException}s appropriately.
*
* @author David Webb
* @author Mark Paluch
* @see CqlTemplate#execute(PreparedStatementCreator, PreparedStatementCallback)
* @see CqlTemplate#query(PreparedStatementCreator, RowCallbackHandler)
*/
public interface PreparedStatementCreator {
/**
* Create a statement in this session. Allows implementations to use PreparedStatements. The CassandraTemlate will
* attempt to cache the PreparedStatement for future use without the overhead of re-preparing on the entire cluster.
* Create a statement in this session. Allows implementations to use {@link PreparedStatement}.
*
* @param session Session to use to create statement
* @param session {@link Session} to use to create statement
* @return a prepared statement
* @throws DriverException there is no need to catch DriverException that may be thrown in the implementation of this
* method. The CassandraTemlate class will handle them.
* @throws DriverException there is no need to catch {@link DriverException} that may be thrown in the implementation
* of this method. The {@link CqlTemplate} class will handle them.
*/
PreparedStatement createPreparedStatement(Session session) throws DriverException;
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2016 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.cassandra.core;
import org.springframework.util.Assert;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Update;
/**
* Utility class to associate {@link QueryOptions} and {@link WriteOptions} with QueryBuilder {@link Statement}s.
*
* @author Mark Paluch
* @since 2.0
*/
public abstract class QueryOptionsUtil {
/**
* Add common {@link QueryOptions} to Cassandra {@link PreparedStatement}s.
*
* @param preparedStatement the Cassandra {@link PreparedStatement}, must not be {@literal null}.
* @param queryOptions query options (e.g. consistency level) to add to the Cassandra {@link PreparedStatement}.
*/
public static PreparedStatement addPreparedStatementOptions(PreparedStatement preparedStatement,
QueryOptions queryOptions) {
Assert.notNull(preparedStatement, "PreparedStatement must not be null");
if (queryOptions != null) {
if (queryOptions.getDriverConsistencyLevel() != null) {
preparedStatement.setConsistencyLevel(queryOptions.getDriverConsistencyLevel());
} else if (queryOptions.getConsistencyLevel() != null) {
preparedStatement.setConsistencyLevel(ConsistencyLevelResolver.resolve(queryOptions.getConsistencyLevel()));
}
if (queryOptions.getDriverRetryPolicy() != null) {
preparedStatement.setRetryPolicy(queryOptions.getDriverRetryPolicy());
} else if (queryOptions.getRetryPolicy() != null) {
preparedStatement.setRetryPolicy(RetryPolicyResolver.resolve(queryOptions.getRetryPolicy()));
}
}
return preparedStatement;
}
/**
* Add common {@link QueryOptions} to all types of queries.
*
* @param statement CQL {@link Statement}, must not be {@literal null}.
* @param queryOptions query options (e.g. consistency level) to add to the CQL statement.
* @return the given {@link Statement}.
*/
public static <T extends Statement> T addQueryOptions(T statement, QueryOptions queryOptions) {
Assert.notNull(statement, "Statement must not be null");
if (queryOptions != null) {
if (queryOptions.getDriverConsistencyLevel() != null) {
statement.setConsistencyLevel(queryOptions.getDriverConsistencyLevel());
} else if (queryOptions.getConsistencyLevel() != null) {
statement.setConsistencyLevel(ConsistencyLevelResolver.resolve(queryOptions.getConsistencyLevel()));
}
if (queryOptions.getDriverRetryPolicy() != null) {
statement.setRetryPolicy(queryOptions.getDriverRetryPolicy());
} else if (queryOptions.getRetryPolicy() != null) {
statement.setRetryPolicy(RetryPolicyResolver.resolve(queryOptions.getRetryPolicy()));
}
if (queryOptions.getFetchSize() != null) {
statement.setFetchSize(queryOptions.getFetchSize());
}
if (queryOptions.getReadTimeout() != null) {
statement.setReadTimeoutMillis(queryOptions.getReadTimeout().intValue());
}
if (queryOptions.getTracing() != null) {
if (queryOptions.getTracing()) {
statement.enableTracing();
} else {
statement.disableTracing();
}
}
}
return statement;
}
/**
* Add common {@link WriteOptions} options to {@link Insert} CQL statements.
*
* @param insert {@link Insert} CQL statement, must not be {@literal null}.
* @param writeOptions write options (e.g. consistency level) to add to the CQL statement.
* @return the given {@link Insert}.
*/
public static Insert addWriteOptions(Insert insert, WriteOptions writeOptions) {
Assert.notNull(insert, "Insert must not be null");
if (writeOptions != null) {
addQueryOptions(insert, writeOptions);
if (writeOptions.getTtl() != null) {
insert.using(QueryBuilder.ttl(writeOptions.getTtl()));
}
}
return insert;
}
/**
* Add common {@link WriteOptions} options to {@link Update} CQL statements.
*
* @param update {@link Update} CQL statement, must not be {@literal null}.
* @param writeOptions write options (e.g. consistency level) to add to the CQL statement.
* @return the given {@link Update}.
*/
public static Update addWriteOptions(Update update, WriteOptions writeOptions) {
Assert.notNull(update, "Update must not be null");
if (writeOptions != null) {
addQueryOptions(update, writeOptions);
if (writeOptions.getTtl() != null) {
update.using(QueryBuilder.ttl(writeOptions.getTtl()));
}
}
return update;
}
}

View File

@@ -20,7 +20,36 @@ import org.springframework.dao.DataAccessException;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.exceptions.DriverException;
/**
* Callback interface used by {@link CqlTemplate}'s query methods. Implementations of this interface perform the actual
* work of extracting results from a {@link ResultSet}, but don't need to worry about exception handling.
* {@link DriverException}s will be caught and handled by the calling {@link CqlTemplate}.
* <p>
* This interface is mainly used within the CQL framework itself. A {@link RowMapper} is usually a simpler choice for
* {@link ResultSet} processing, mapping one result object per row instead of one result object for the entire
* {@link ResultSet}.
* <p>
* Note: In contrast to a {@link RowCallbackHandler}, a {@link ResultSetExtractor} object is typically stateless and
* thus reusable, as long as it doesn't access stateful resources or keep result state within the object.
*
* @author Matthew T. Adams
* @author Mark Paluch
* @since April 24, 2003
* @see CqlTemplate
* @see RowCallbackHandler
* @see RowMapper
*/
public interface ResultSetExtractor<T> {
/**
* Implementations must implement this method to process the entire {@link ResultSet}.
*
* @param rs {@link ResultSet} to extract data from.
* @return an arbitrary result object, or {@code null} if none (the extractor will typically be stateful in the latter
* case).
* @throws DriverException if a {@link DriverException} is encountered getting column values or navigating (that is,
* there's no need to catch {@link DriverException})
* @throws DataAccessException in case of custom exceptions
*/
T extractData(ResultSet rs) throws DriverException, DataAccessException;
}

View File

@@ -15,9 +15,9 @@
*/
package org.springframework.cassandra.core;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.springframework.util.Assert;
@@ -25,29 +25,26 @@ import com.datastax.driver.core.Host;
import com.datastax.driver.core.exceptions.DriverException;
/**
* {@link HostMapper} to to map hosts into {@link RingMember} objects.
*
* @author David Webb
* @author Mark Paluch
* @param <T>
*/
public class RingMemberHostMapper implements HostMapper<RingMember> {
public enum RingMemberHostMapper implements HostMapper<RingMember> {
INSTANCE;
/* (non-Javadoc)
* @see org.springframework.cassandra.core.HostMapper#mapHosts(java.util.Set)
* @see org.springframework.cassandra.core.HostMapper#mapHosts(java.util.Iterable)
*/
@Override
public List<RingMember> mapHosts(Set<Host> hosts) throws DriverException {
public Collection<RingMember> mapHosts(Iterable<Host> hosts) throws DriverException {
List<RingMember> members = new ArrayList<RingMember>();
Assert.notNull(hosts);
Assert.notEmpty(hosts);
RingMember r = null;
for (Host host : hosts) {
r = new RingMember(host);
members.add(r);
}
return members;
Assert.notNull(hosts, "Hosts must not be null");
return StreamSupport.stream(hosts.spliterator(), false) //
.map(RingMember::new) //
.collect(Collectors.toList());
}
}

View File

@@ -18,8 +18,36 @@ package org.springframework.cassandra.core;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.exceptions.DriverException;
/**
* An interface used by {@link CqlTemplate} for processing rows of a {@link com.datastax.driver.core.ResultSet} on a
* per-row basis. Implementations of this interface perform the actual work of processing each row but don't need to
* worry about exception handling. {@link DriverException}s will be caught and handled by the calling
* {@link CqlTemplate}.
* <p>
* In contrast to a {@link ResultSetExtractor}, a {@link RowCallbackHandler} object is typically stateful: It keeps the
* result state within the object, to be available for later inspection.
* <p>
* Consider using a {@link RowMapper} instead if you need to map exactly one result object per row, assembling them into
* a List.
*
* @author Mark Paluch
* @see CqlTemplate
* @see RowMapper
* @see ResultSetExtractor
*/
@FunctionalInterface
public interface RowCallbackHandler {
/**
* Implementations must implement this method to process each row of data in the {@link ResultSet}. This method is only
* supposed to extract values of the current row.
* <p>
* Exactly what the implementation chooses to do is up to it: A trivial implementation might simply count rows, while
* another implementation might build an XML document.
*
* @param row the {@link Row} to process (pre-initialized for the current row)
* @throws DriverException if a {@link DriverException} is encountered getting column values (that is, there's no need
* to catch {@link DriverException})
*/
void processRow(Row row) throws DriverException;
}

View File

@@ -18,8 +18,32 @@ package org.springframework.cassandra.core;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.exceptions.DriverException;
/**
* An interface used by {@link CqlTemplate} for mapping rows of a {@link com.datastax.driver.core.ResultSet} on a
* per-row basis. Implementations of this interface perform the actual work of mapping each row to a result object, but
* don't need to worry about exception handling. {@link DriverException}s will be caught and handled by the calling
* {@link CqlTemplate}.
* <p>
* Typically used either for {@link CqlTemplate}'s query methods or for out parameters of stored procedures.
* {@link RowMapper} objects are typically stateless and thus reusable; they are an ideal choice for implementing
* row-mapping logic in a single place.
*
* @author Matthew T. Adams
* @author Mark Paluch
* @see RowCallbackHandler
* @see ResultSetExtractor
*/
public interface RowMapper<T> {
/**
* Implementations must implement this method to map each row of data in the
* {@link com.datastax.driver.core.ResultSet}.
*
* @param row the {@link Row} to map, must not be {@literal null}.
* @param rowNum the number of the current row.
* @return the result object for the current row.
* @throws DriverException if a {@link DriverException} is encountered getting column values (that is, there's no need
* to catch {@link DriverException})
*/
T mapRow(Row row, int rowNum) throws DriverException;
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2016 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.cassandra.core;
import java.util.ArrayList;
import java.util.List;
import org.springframework.dao.DataAccessException;
import org.springframework.util.Assert;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.exceptions.DriverException;
/**
* Adapter implementation of the {@link ResultSetExtractor} interface that delegates to a {@link RowMapper} which is
* supposed to create an object for each row. Each object is added to the results List of this
* {@link ResultSetExtractor}.
* <p>
* Useful for the typical case of one object per row in the database table. The number of entries in the results will
* match the number of rows.
* <p>
* Note that a {@link RowMapper} object is typically stateless and thus reusable.
*
* @author Mark Paluch
* @since 2.0
* @see RowMapper
* @see CqlTemplate
*/
public class RowMapperResultSetExtractor<T> implements ResultSetExtractor<List<T>> {
private final RowMapper<T> rowMapper;
private final int rowsExpected;
/**
* Create a new {@link RowMapperResultSetExtractor}.
*
* @param rowMapper the {@link RowMapper} which creates an object for each row, must not be {@literal null}.
*/
public RowMapperResultSetExtractor(RowMapper<T> rowMapper) {
this(rowMapper, 0);
}
/**
* Create a new {@link RowMapperResultSetExtractor}.
*
* @param rowMapper the {@link RowMapper} which creates an object for each row, must not be {@literal null}.
* @param rowsExpected the number of expected rows (just used for optimized collection handling).
*/
public RowMapperResultSetExtractor(RowMapper<T> rowMapper, int rowsExpected) {
Assert.notNull(rowMapper, "RowMapper is must not be null");
this.rowMapper = rowMapper;
this.rowsExpected = rowsExpected;
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.ResultSetExtractor#extractData(com.datastax.driver.core.ResultSet)
*/
@Override
public List<T> extractData(ResultSet resultSet) throws DriverException, DataAccessException {
List<T> results = (this.rowsExpected > 0 ? new ArrayList<>(this.rowsExpected) : new ArrayList<T>());
int rowNum = 0;
for (Row row : resultSet) {
results.add(this.rowMapper.mapRow(row, rowNum++));
}
return results;
}
}

View File

@@ -18,22 +18,39 @@ package org.springframework.cassandra.core;
import org.springframework.dao.DataAccessException;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.DriverException;
/**
* Interface for operations on a Cassandra Session.
*
* Generic callback interface for code that operates on a Cassandra {@link Session}. Allows to execute any number of
* operations on a single session, using any type and number of statements.
* <p>
* This is particularly useful for delegating to existing data access code that expects a {@link Session} to work on and
* throws {@link DriverException}. For newly written code, it is strongly recommended to use {@link CqlTemplate}'s more
* specific operations, for example a {@code query} or {@code update} variant.
*
* @author David Webb
* @param <T>
* @author Mark Paluch
* @see CqlTemplate#execute(SessionCallback)
* @see CqlTemplate#query
*/
public interface SessionCallback<T> {
/**
* Perform the operation in the given Session
* Gets called by {@link CqlTemplate#execute} with an active Cassandra {@link Session}. Does not need to care about
* activating or closing the {@link Session}.
* <p>
* Allows for returning a result object created within the callback, i.e. a domain object or a collection of domain
* objects. Note that there's special support for single step actions: see {@link CqlTemplate#queryForObject} etc. A
* thrown {@link RuntimeException} is treated as application exception: it gets propagated to the caller of the
* template.
*
* @param s
* @return
* @throws DataAccessException
* @param session active Cassandra Session, must not be {@literal null}.
* @return a result object, or {@code null} if none.
* @throws DriverException if thrown by a Session method, to be auto-converted to a {@link DataAccessException}.
* @throws DataAccessException in case of custom exceptions.
* @see CqlTemplate#queryForObject(String, Class)
* @see CqlTemplate#queryForResultSet(String)
*/
T doInSession(Session s) throws DataAccessException;
T doInSession(Session session) throws DriverException, DataAccessException;
}

View File

@@ -15,9 +15,11 @@
*/
package org.springframework.cassandra.support;
import com.datastax.driver.core.exceptions.DriverException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.util.Assert;
import com.datastax.driver.core.Session;
@@ -103,4 +105,50 @@ public class CassandraAccessor implements InitializingBean {
Assert.state(this.session != null, "Session was not properly initialized");
return this.session;
}
/**
* Translate the given {@link DriverException} into a generic {@link DataAccessException}.
* <p>
* The returned {@link DataAccessException} is supposed to contain the original {@code DriverException} as root cause.
* However, client code may not generally rely on this due to {@link DataAccessException}s possibly being caused by
* other resource APIs as well. That said, a {@code getRootCause() instanceof DataAccessException} check (and
* subsequent cast) is considered reliable when expecting Cassandra-based access to have happened.
*
* @param ex the offending {@link DriverException}
* @return the DataAccessException, wrapping the {@code DriverException}
* @see <a href=
* "http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#dao-exceptions">Consistent
* exception hierarchy</a>
* @see DataAccessException
*/
protected DataAccessException translateExceptionIfPossible(DriverException ex) {
Assert.notNull(ex, "DriverException must not be null");
return getExceptionTranslator().translateExceptionIfPossible(ex);
}
/**
* Translate the given {@link DriverException} into a generic {@link DataAccessException}.
* <p>
* The returned {@link DataAccessException} is supposed to contain the original {@code DriverException} as root cause.
* However, client code may not generally rely on this due to {@link DataAccessException}s possibly being caused by
* other resource APIs as well. That said, a {@code getRootCause() instanceof DataAccessException} check (and
* subsequent cast) is considered reliable when expecting Cassandra-based access to have happened.
*
* @param task readable text describing the task being attempted
* @param cql CQL query or update that caused the problem (may be {@code null})
* @param ex the offending {@link DriverException}
* @return the DataAccessException, wrapping the {@code DriverException}
* @see org.springframework.dao.DataAccessException#getRootCause()
* @see <a href=
* "http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#dao-exceptions">Consistent
* exception hierarchy</a>
*/
protected DataAccessException translate(String task, String cql, DriverException ex) {
Assert.notNull(ex, "DriverException must not be null");
return getExceptionTranslator().translate(task, cql, ex);
}
}

View File

@@ -0,0 +1,214 @@
/*
* Copyright 2016 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.cassandra.core;
import static org.assertj.core.api.Assertions.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.querybuilder.QueryBuilder;
/**
* Integration tests for {@link AsyncCqlTemplate}.
*
* @author Mark Paluch
*/
public class AsyncCqlTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest {
private static final AtomicBoolean initialized = new AtomicBoolean();
private AsyncCqlTemplate template;
@Before
public void before() throws Exception {
if (initialized.compareAndSet(false, true)) {
getSession().execute("CREATE TABLE IF NOT EXISTS user (id text PRIMARY KEY, username text);");
} else {
session.execute("TRUNCATE user;");
}
session.execute("INSERT INTO user (id, username) VALUES ('WHITE', 'Walter');");
template = new AsyncCqlTemplate();
template.setSession(getSession());
}
/**
* @see DATACASS-292
*/
@Test
public void executeShouldRemoveRecords() throws Exception {
template.execute("DELETE FROM user WHERE id = 'WHITE'").get();
assertThat(session.execute("SELECT * FROM user").one()).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void queryShouldInvokeCallback() throws Exception {
List<String> result = new ArrayList<>();
template.query("SELECT id FROM user;", row -> {
result.add(row.getString(0));
}).get();
assertThat(result).contains("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryForObjectShouldReturnFirstColumn() throws Exception {
String id = template.queryForObject("SELECT id FROM user;", String.class).get();
assertThat(id).isEqualTo("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryForObjectShouldReturnMap() throws Exception {
Map<String, Object> map = template.queryForMap("SELECT * FROM user;").get();
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
}
/**
* @see DATACASS-292
*/
@Test
public void executeStatementShouldRemoveRecords() throws Exception {
template.execute(QueryBuilder.delete().from("user").where(QueryBuilder.eq("id", "WHITE"))).get();
assertThat(session.execute("SELECT * FROM user").one()).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void queryStatementShouldInvokeCallback() throws Exception {
List<String> result = new ArrayList<>();
template.query(QueryBuilder.select("id").from("user"), row -> {
result.add(row.getString(0));
}).get();
assertThat(result).contains("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryForObjectStatementShouldReturnFirstColumn() throws Exception {
String id = template.queryForObject(QueryBuilder.select("id").from("user"), String.class).get();
assertThat(id).isEqualTo("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryForObjectStatementShouldReturnMap() throws Exception {
Map<String, Object> map = template.queryForMap(QueryBuilder.select().from("user")).get();
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
}
/**
* @see DATACASS-292
*/
@Test
public void executeWithArgsShouldRemoveRecords() throws Exception {
template.execute("DELETE FROM user WHERE id = ?", "WHITE").get();
assertThat(session.execute("SELECT * FROM user").one()).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void queryPreparedStatementShouldInvokeCallback() throws Exception {
List<String> result = new ArrayList<>();
template.query("SELECT id FROM user WHERE id = ?;", row -> {
result.add(row.getString(0));
}, "WHITE").get();
assertThat(result).contains("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryPreparedStatementCreatorShouldInvokeCallback() throws Exception {
List<String> result = new ArrayList<>();
template.query(
session -> new GuavaListenableFutureAdapter<PreparedStatement>(
session.prepareAsync("SELECT id FROM user WHERE id = ?;"), template.getExceptionTranslator()),
ps -> ps.bind("WHITE"), row -> {
result.add(row.getString(0));
}).get();
assertThat(result).contains("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryForObjectWithArgsShouldReturnFirstColumn() throws Exception {
String id = template.queryForObject("SELECT id FROM user WHERE id = ?;", String.class, "WHITE").get();
assertThat(id).isEqualTo("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryForObjectWithArgsShouldReturnMap() throws Exception {
Map<String, Object> map = template.queryForMap("SELECT * FROM user WHERE id = ?;", "WHITE").get();
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright 2016 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.cassandra.core;
import static org.assertj.core.api.Assertions.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest;
import com.datastax.driver.core.querybuilder.QueryBuilder;
/**
* Integration tests for {@link CqlTemplate}.
*
* @author Mark Paluch
*/
public class CqlTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest {
static final AtomicBoolean initialized = new AtomicBoolean();
CqlTemplate template;
@Before
public void before() throws Exception {
if (initialized.compareAndSet(false, true)) {
getSession().execute("CREATE TABLE IF NOT EXISTS user (id text PRIMARY KEY, username text);");
} else {
session.execute("TRUNCATE user;");
}
session.execute("INSERT INTO user (id, username) VALUES ('WHITE', 'Walter');");
template = new CqlTemplate();
template.setSession(getSession());
}
/**
* @see DATACASS-292
*/
@Test
public void executeShouldRemoveRecords() throws Exception {
template.execute("DELETE FROM user WHERE id = 'WHITE'");
assertThat(session.execute("SELECT * FROM user").one()).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void queryShouldInvokeCallback() throws Exception {
List<String> result = new ArrayList<>();
template.query("SELECT id FROM user;", row -> {
result.add(row.getString(0));
});
assertThat(result).contains("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryForObjectShouldReturnFirstColumn() throws Exception {
String id = template.queryForObject("SELECT id FROM user;", String.class);
assertThat(id).isEqualTo("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryForObjectShouldReturnMap() throws Exception {
Map<String, Object> map = template.queryForMap("SELECT * FROM user;");
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
}
/**
* @see DATACASS-292
*/
@Test
public void executeStatementShouldRemoveRecords() throws Exception {
template.execute(QueryBuilder.delete().from("user").where(QueryBuilder.eq("id", "WHITE")));
assertThat(session.execute("SELECT * FROM user").one()).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void queryStatementShouldInvokeCallback() throws Exception {
List<String> result = new ArrayList<>();
template.query(QueryBuilder.select("id").from("user"), row -> {
result.add(row.getString(0));
});
assertThat(result).contains("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryForObjectStatementShouldReturnFirstColumn() throws Exception {
String id = template.queryForObject(QueryBuilder.select("id").from("user"), String.class);
assertThat(id).isEqualTo("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryForObjectStatementShouldReturnMap() throws Exception {
Map<String, Object> map = template.queryForMap(QueryBuilder.select().from("user"));
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
}
/**
* @see DATACASS-292
*/
@Test
public void executeWithArgsShouldRemoveRecords() throws Exception {
template.execute("DELETE FROM user WHERE id = ?", "WHITE");
assertThat(session.execute("SELECT * FROM user").one()).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void queryPreparedStatementShouldInvokeCallback() throws Exception {
List<String> result = new ArrayList<>();
template.query("SELECT id FROM user WHERE id = ?;", row -> {
result.add(row.getString(0));
}, "WHITE");
assertThat(result).contains("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryPreparedStatementCreatorShouldInvokeCallback() throws Exception {
List<String> result = new ArrayList<>();
template.query(session -> session.prepare("SELECT id FROM user WHERE id = ?;"), ps -> ps.bind("WHITE"), row -> {
result.add(row.getString(0));
});
assertThat(result).contains("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryForObjectWithArgsShouldReturnFirstColumn() throws Exception {
String id = template.queryForObject("SELECT id FROM user WHERE id = ?;", String.class, "WHITE");
assertThat(id).isEqualTo("WHITE");
}
/**
* @see DATACASS-292
*/
@Test
public void queryForObjectWithArgsShouldReturnMap() throws Exception {
Map<String, Object> map = template.queryForMap("SELECT * FROM user WHERE id = ?;", "WHITE");
assertThat(map).containsEntry("id", "WHITE").containsEntry("username", "Walter");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,194 @@
/*
* Copyright 2016 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.cassandra.core;
import static org.mockito.Mockito.*;
import java.util.concurrent.TimeUnit;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import com.datastax.driver.core.ConsistencyLevel;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.policies.FallthroughRetryPolicy;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.Update;
import com.datastax.driver.core.querybuilder.Using;
/**
* Unit tests for {@link QueryOptionsUtil}.
*
* @author John Blum
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
@SuppressWarnings("unchecked")
public class QueryOptionsUtilUnitTests {
@Rule public ExpectedException exception = ExpectedException.none();
@Mock Insert mockInsert;
@Mock PreparedStatement mockPreparedStatement;
@Mock Session mockSession;
@Mock Statement mockStatement;
@Mock Update mockUpdate;
/**
* @see DATACASS-202
*/
@Test
public void addPreparedStatementOptionsShouldAddDriverQueryOptions() {
QueryOptions queryOptions = QueryOptions.builder() //
.consistencyLevel(ConsistencyLevel.EACH_QUORUM) //
.retryPolicy(FallthroughRetryPolicy.INSTANCE) //
.build();
QueryOptionsUtil.addPreparedStatementOptions(mockPreparedStatement, queryOptions);
verify(mockPreparedStatement).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM);
verify(mockPreparedStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE);
}
/**
* @see DATACASS-202
*/
@Test
public void addPreparedStatementOptionsShouldAddOurQueryOptions() {
QueryOptions queryOptions = QueryOptions.builder().retryPolicy(RetryPolicy.FALLTHROUGH).build();
queryOptions.setConsistencyLevel(org.springframework.cassandra.core.ConsistencyLevel.LOCAL_QUOROM);
QueryOptionsUtil.addPreparedStatementOptions(mockPreparedStatement, queryOptions);
verify(mockPreparedStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE);
verify(mockPreparedStatement).setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM);
}
/**
* @see DATACASS-202
*/
@Test
public void addStatementQueryOptionsShouldAddDriverQueryOptions() {
QueryOptions queryOptions = QueryOptions.builder().consistencyLevel(ConsistencyLevel.EACH_QUORUM) //
.retryPolicy(FallthroughRetryPolicy.INSTANCE) //
.build();
QueryOptionsUtil.addQueryOptions(mockStatement, queryOptions);
verify(mockStatement).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM);
verify(mockStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE);
}
/**
* @see DATACASS-202
*/
@Test
public void addStatementQueryOptionsShouldAddOurQueryOptions() {
QueryOptions queryOptions = QueryOptions.builder().retryPolicy(RetryPolicy.FALLTHROUGH).build();
queryOptions.setConsistencyLevel(org.springframework.cassandra.core.ConsistencyLevel.LOCAL_QUOROM);
QueryOptionsUtil.addQueryOptions(mockStatement, queryOptions);
verify(mockStatement).setRetryPolicy(FallthroughRetryPolicy.INSTANCE);
verify(mockStatement).setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM);
}
/**
* @see DATACASS-202
*/
@Test
public void addStatementQueryOptionsShouldNotAddOptions() {
QueryOptions queryOptions = QueryOptions.builder().build();
QueryOptionsUtil.addQueryOptions(mockStatement, queryOptions);
verifyZeroInteractions(mockStatement);
}
/**
* @see DATACASS-202
*/
@Test
public void addStatementQueryOptionsShouldAddGenericQueryOptions() {
QueryOptions queryOptions = QueryOptions.builder() //
.fetchSize(10) //
.readTimeout(1, TimeUnit.MINUTES) //
.withTracing() //
.build();
QueryOptionsUtil.addQueryOptions(mockStatement, queryOptions);
verify(mockStatement).setReadTimeoutMillis(60 * 1000);
verify(mockStatement).setFetchSize(10);
verify(mockStatement).enableTracing();
}
/**
* @see DATACASS-202
*/
@Test
public void addInsertWriteOptionsShouldAddDriverQueryOptions() {
WriteOptions writeOptions = WriteOptions.builder() //
.consistencyLevel(ConsistencyLevel.EACH_QUORUM) //
.retryPolicy(FallthroughRetryPolicy.INSTANCE) //
.readTimeout(10) //
.ttl(10) //
.build();
QueryOptionsUtil.addWriteOptions(mockInsert, writeOptions);
verify(mockInsert).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM);
verify(mockInsert).setRetryPolicy(FallthroughRetryPolicy.INSTANCE);
verify(mockInsert).setReadTimeoutMillis(10);
verify(mockInsert).using(Mockito.any(Using.class));
}
/**
* @see DATACASS-202
*/
@Test
public void addUpdateWriteOptionsShouldAddDriverQueryOptions() {
WriteOptions writeOptions = WriteOptions.builder() //
.consistencyLevel(ConsistencyLevel.EACH_QUORUM) //
.retryPolicy(FallthroughRetryPolicy.INSTANCE) //
.ttl(10) //
.tracing(false).build();
QueryOptionsUtil.addWriteOptions(mockUpdate, writeOptions);
verify(mockUpdate).setConsistencyLevel(ConsistencyLevel.EACH_QUORUM);
verify(mockUpdate).setRetryPolicy(FallthroughRetryPolicy.INSTANCE);
verify(mockUpdate).using(Mockito.any(Using.class));
verify(mockUpdate).disableTracing();
}
}

View File

@@ -49,8 +49,8 @@ public abstract class AbstractEmbeddedCassandraIntegrationTest {
@Rule public final CassandraRule cassandraRule = cassandraEnvironment.testInstance()
.before(new SessionCallback<Object>() {
@Override
public Object doInSession(Session s) throws DataAccessException {
AbstractEmbeddedCassandraIntegrationTest.this.cluster = s.getCluster();
public Object doInSession(Session session) throws DataAccessException {
AbstractEmbeddedCassandraIntegrationTest.this.cluster = session.getCluster();
return null;
}
});

View File

@@ -71,10 +71,10 @@ public abstract class AbstractKeyspaceCreatingIntegrationTest extends AbstractEm
cassandraRule.before(new SessionCallback<Object>() {
@Override
public Object doInSession(Session s) throws DataAccessException {
public Object doInSession(Session session) throws DataAccessException {
if (!keyspace.equals(s.getLoggedKeyspace())) {
s.execute(String.format("USE %s;", keyspace));
if (!keyspace.equals(session.getLoggedKeyspace())) {
session.execute(String.format("USE %s;", keyspace));
}
return null;
}

View File

@@ -129,8 +129,8 @@ public class CassandraRule extends ExternalResource {
SessionCallback<Void> sessionCallback = new SessionCallback<Void>() {
@Override
public Void doInSession(Session s) throws DataAccessException {
load(s, cqlDataSet);
public Void doInSession(Session session) throws DataAccessException {
load(session, cqlDataSet);
return null;
}
};
@@ -180,8 +180,8 @@ public class CassandraRule extends ExternalResource {
after.add(new SessionCallback<Void>() {
@Override
public Void doInSession(Session s) throws DataAccessException {
load(session, cqlDataSet);
public Void doInSession(Session session) throws DataAccessException {
load(CassandraRule.this.session, cqlDataSet);
return null;
}
});

View File

@@ -78,7 +78,7 @@ public class KeyspaceRule extends ExternalResource {
} else {
cassandraRule.before(new SessionCallback<Object>() {
@Override
public Object doInSession(Session s) throws DataAccessException {
public Object doInSession(Session session) throws DataAccessException {
KeyspaceRule.this.session = cassandraRule.getSession();
return null;
}

View File

@@ -1,422 +0,0 @@
/*
* Copyright 2016 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.cassandra.test.integration.core.async;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.cassandra.core.keyspace.CreateTableSpecification.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CancellationException;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cassandra.core.*;
import org.springframework.cassandra.support.exception.CassandraConnectionFailureException;
import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest;
import org.springframework.cassandra.test.integration.support.ListOfMapListener;
import org.springframework.cassandra.test.integration.support.MapListener;
import org.springframework.cassandra.test.integration.support.ObjectListener;
import org.springframework.cassandra.test.integration.support.QueryListener;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
/**
* @author Mark Paluch
*/
public class AsynchronousCqlOperationsIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest {
public static final String TABLE = "book";
CqlOperations cqlOperations;
@Before
public void setUp() {
cqlOperations = new CqlTemplate(session);
ensureTableExists();
cqlOperations.truncate(TABLE);
}
public static String cql(Book book, String... columns) {
if (columns == null || columns.length == 0) {
columns = new String[] { "title", "isbn" };
}
return String.format("select %s from %s where title = '%s' and isbn = '%s'",
StringUtils.arrayToCommaDelimitedString(columns), TABLE, book.title, book.isbn);
}
public static String cql(String[] titles) {
String[] quoted = new String[titles.length];
System.arraycopy(titles, 0, quoted, 0, titles.length);
for (int i = 0; i < quoted.length; i++) {
quoted[i] = "'" + quoted[i] + "'";
}
return String.format("select * from %s where title in (%s)", TABLE,
StringUtils.arrayToCommaDelimitedString(quoted));
}
public static Select select(String isbn) {
Select select = QueryBuilder.select("isbn", "title").from(TABLE);
select.where(QueryBuilder.eq("isbn", isbn));
return select;
}
public static final Comparator<Book> BOOK_COMPARATOR = new Comparator<Book>() {
@Override
public int compare(Book l, Book r) {
return l.isbn.compareTo(r.isbn);
}
};
public static final Comparator<? super Map<String, ?>> MAP_WITH_ISBN_COMPARATOR = new Comparator<Map<String, ?>>() {
@Override
public int compare(Map<String, ?> o1, Map<String, ?> o2) {
Assert.isInstanceOf(Comparable.class, o1.get("isbn"),
"Map o1 must contain a key 'isbn' and a Comparable value to compare the maps");
Assert.isInstanceOf(Comparable.class, o2.get("isbn"),
"Map o2 must contain a key 'isbn' and a Comparable value to compare the maps");
return ((Comparable) o1.get("isbn")).compareTo(o2.get("isbn"));
}
};
public static void assertMapEquals(Map<?, ?> expected, Map<?, ?> actual) {
for (Object key : expected.keySet()) {
assertThat(actual.containsKey(key)).isTrue();
assertThat(actual.get(key)).isEqualTo(expected.get(key));
}
}
void ensureTableExists() {
cqlOperations.execute(createTable(TABLE).ifNotExists().partitionKeyColumn("title", DataType.ascii())
.clusteredKeyColumn("isbn", DataType.ascii()));
}
Book[] insert(int n) {
Book[] books = new Book[n];
for (int i = 0; i < n; i++) {
Book b = books[i] = Book.random();
cqlOperations.execute(String.format("insert into %s (isbn, title) values ('%s', '%s')", TABLE, b.isbn, b.title));
}
return books;
}
void assertBook(Book expected, Book actual) {
assertThat(actual.isbn).isEqualTo(expected.isbn);
assertThat(actual.title).isEqualTo(expected.title);
}
/**
* Tests that test {@link AsynchronousQueryListener} should create an anonymous subclass of this class then call
* {@link #test()}.
*/
abstract class AsynchronousQueryListenerTestTemplate {
/**
* Subclass must perform the asynchronous query using the given data and listener and set <code>this.expected</code>
* to the appropriate value before returning.
*/
abstract void doAsyncQuery(Book b, QueryListener listener);
void test() throws InterruptedException {
Book expected = insert(1)[0];
QueryListener listener = QueryListener.create();
doAsyncQuery(expected, listener);
listener.await();
Row r = cqlOperations.getResultSetUninterruptibly(listener.getResultSetFuture()).one();
Book actual = new Book(r.getString(0), r.getString(1));
assertBook(expected, actual);
}
}
/**
* Tests that test {@link QueryForObjectListener} should create an anonymous subclass of this class then call
* {@link #test()}
*/
abstract class QueryForObjectListenerTestTemplate<T> {
/**
* Subclass must perform the asynchronous query using the given data and listener and set <code>this.expected</code>
* to the appropriate value before returning.
*/
abstract void doAsyncQuery(Book b, QueryForObjectListener<T> listener);
T expected; // subclass should set this value in doAsyncQuery
void test() throws Exception {
Book book = insert(1)[0];
ObjectListener<T> listener = ObjectListener.create();
doAsyncQuery(book, listener);
listener.await();
if (listener.getException() != null) {
throw listener.getException();
}
assertThat(listener.getResult()).isEqualTo(expected);
}
}
/**
* Tests that test {@link QueryForMapListener} should create an anonymous subclass of this class then call
* {@link #test()}
*/
abstract class QueryForMapListenerTestTemplate {
/**
* Subclass must perform the asynchronous query using the given data and listener and set <code>this.expected</code>
* to the appropriate value before returning.
*/
abstract void doAsyncQuery(Book b, QueryForMapListener listener);
Map<String, Object> expected; // subclass should set this value in doAsyncQuery
void test() throws Exception {
Book book = insert(1)[0];
MapListener listener = MapListener.create();
doAsyncQuery(book, listener);
listener.await();
if (listener.getException() != null) {
throw listener.getException();
}
assertMapEquals(expected, listener.getResult());
}
}
/**
* Tests that test {@link QueryForMapListener} should create an anonymous subclass of this class then call or
* {@link #test(int)}
*/
abstract class QueryForListListenerTestTemplate {
/**
* Subclass must perform the asynchronous query using the given data and listener and set <code>this.expected</code>
* to the appropriate value before returning.
*/
abstract void doAsyncQuery(Book[] books, QueryForListOfMapListener listener);
List<Map<String, ? extends Comparable>> expected; // subclass should set this value in doAsyncQuery
void test(int n) throws Exception {
Book[] books = insert(n);
ListOfMapListener listener = ListOfMapListener.create();
Arrays.sort(books, BOOK_COMPARATOR);
doAsyncQuery(books, listener);
listener.await();
if (listener.getException() != null) {
throw listener.getException();
}
// sort results the same way as the books array above
Collections.sort(listener.getResult(), MAP_WITH_ISBN_COMPARATOR);
for (int i = 0; i < expected.size(); i++) {
assertMapEquals(expected.get(i), listener.getResult().get(i));
}
}
}
@Test(expected = CancellationException.class)
public void testString_AsynchronousQueryListener_Cancelled() throws InterruptedException {
new AsynchronousQueryListenerTestTemplate() {
@Override
void doAsyncQuery(Book b, QueryListener listener) {
Cancellable qc = cqlOperations.queryAsynchronously(cql(b), listener);
qc.cancel();
}
}.test();
}
@Test
public void testString_AsynchronousQueryListener() throws InterruptedException {
new AsynchronousQueryListenerTestTemplate() {
@Override
void doAsyncQuery(Book b, QueryListener listener) {
cqlOperations.queryAsynchronously(cql(b), listener);
}
}.test();
}
public void testString_AsynchronousQueryListener_QueryOptions(final ConsistencyLevel cl) throws InterruptedException {
new AsynchronousQueryListenerTestTemplate() {
@Override
void doAsyncQuery(Book b, QueryListener listener) {
cqlOperations.queryAsynchronously(cql(b), listener, new QueryOptions(cl, RetryPolicy.DEFAULT));
}
}.test();
}
@Test
public void testString_AsynchronousQueryListener_QueryOptionsWithConsistencyLevel1() throws InterruptedException {
testString_AsynchronousQueryListener_QueryOptions(ConsistencyLevel.ONE);
}
@Test(expected = CassandraConnectionFailureException.class)
public void testString_AsynchronousQueryListener_QueryOptionsWithConsistencyLevel2() throws InterruptedException {
testString_AsynchronousQueryListener_QueryOptions(ConsistencyLevel.TWO);
}
@Test
public void testSelect_AsynchronousQueryListener() throws InterruptedException {
new AsynchronousQueryListenerTestTemplate() {
@Override
void doAsyncQuery(Book b, QueryListener listener) {
cqlOperations.queryAsynchronously(cql(b), listener);
}
}.test();
}
@Test
public void testString_QueryForObjectListener() throws Exception {
new QueryForObjectListenerTestTemplate<String>() {
@Override
void doAsyncQuery(Book b, QueryForObjectListener<String> listener) {
cqlOperations.queryForObjectAsynchronously(cql(b, "title"), String.class, listener);
expected = b.title;
}
}.test();
}
public void testString_QueryForObjectListener_QueryOptions(final ConsistencyLevel cl) throws Exception {
new QueryForObjectListenerTestTemplate<String>() {
@Override
void doAsyncQuery(Book b, QueryForObjectListener<String> listener) {
QueryOptions opts = new QueryOptions(cl, RetryPolicy.LOGGING);
cqlOperations.queryForObjectAsynchronously(cql(b, "title"), String.class, listener, opts);
expected = b.title;
}
}.test();
}
@Test
public void testString_QueryForObjectListener_QueryOptionsWithConsistencyLevel() throws Exception {
testString_QueryForObjectListener_QueryOptions(ConsistencyLevel.ONE);
}
@Test(expected = CassandraConnectionFailureException.class)
public void testString_QueryForObjectListener_QueryOptionsWithConsistencyLevel2() throws Exception {
testString_QueryForObjectListener_QueryOptions(ConsistencyLevel.TWO);
}
@Test
public void testString_QueryForMapListener() throws Exception {
new QueryForMapListenerTestTemplate() {
@Override
void doAsyncQuery(Book b, QueryForMapListener listener) {
cqlOperations.queryForMapAsynchronously(cql(b), listener);
expected = new HashMap<String, Object>();
expected.put("isbn", b.isbn);
expected.put("title", b.title);
}
}.test();
}
public void testString_QueryForMapListener_QueryOptions(final ConsistencyLevel cl) throws Exception {
new QueryForMapListenerTestTemplate() {
@Override
void doAsyncQuery(Book b, QueryForMapListener listener) {
QueryOptions opts = new QueryOptions(cl, RetryPolicy.LOGGING);
cqlOperations.queryForMapAsynchronously(cql(b), listener, opts);
expected = new HashMap<String, Object>();
expected.put("isbn", b.isbn);
expected.put("title", b.title);
}
}.test();
}
@Test
public void testString_QueryForMapListener_QueryOptionsWithConsistencyLevel1() throws Exception {
testString_QueryForMapListener_QueryOptions(ConsistencyLevel.ONE);
}
@Test(expected = CassandraConnectionFailureException.class)
public void testString_QueryForMapListener_QueryOptionsWithConsistencyLevel2() throws Exception {
testString_QueryForMapListener_QueryOptions(ConsistencyLevel.TWO);
}
@Test
public void testString_QueryForListListener() throws Exception {
new QueryForListListenerTestTemplate() {
@Override
void doAsyncQuery(Book[] books, QueryForListOfMapListener listener) {
String[] titles = new String[books.length];
expected = new ArrayList<Map<String, ? extends Comparable>>(books.length);
for (int i = 0; i < books.length; i++) {
Book b = books[i];
titles[i] = b.title;
Map<String, String> row = new HashMap<String, String>(2);
row.put("title", b.title);
row.put("isbn", b.isbn);
expected.add(row);
}
cqlOperations.queryForListOfMapAsynchronously(cql(titles), listener);
}
}.test(2);
}
public void testString_QueryForListListener_QueryOptions(final ConsistencyLevel cl) throws Exception {
new QueryForListListenerTestTemplate() {
@Override
void doAsyncQuery(Book[] books, QueryForListOfMapListener listener) {
String[] titles = new String[books.length];
expected = new ArrayList<Map<String, ? extends Comparable>>(books.length);
for (int i = 0; i < books.length; i++) {
Book b = books[i];
titles[i] = b.title;
Map<String, String> row = new HashMap<String, String>(2);
row.put("title", b.title);
row.put("isbn", b.isbn);
expected.add(row);
}
cqlOperations.queryForListOfMapAsynchronously(cql(titles), listener, new QueryOptions(cl, RetryPolicy.LOGGING));
}
}.test(2);
}
@Test
public void testString_QueryForListListener_QueryOptionsWithConsistencyLevel1() throws Exception {
testString_QueryForListListener_QueryOptions(ConsistencyLevel.ONE);
}
@Test(expected = CassandraConnectionFailureException.class)
public void testString_QueryForListListener_QueryOptionsWithConsistencyLevel2() throws Exception {
testString_QueryForListListener_QueryOptions(ConsistencyLevel.TWO);
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2016 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.cassandra.test.integration.core.async;
import java.util.UUID;
/**
* @author Matthew T. Adams
*/
public class Book {
public static final String uuid() {
return UUID.randomUUID().toString();
}
public static Book random() {
return new Book("title-" + uuid(), "isbn-" + uuid());
}
public Book() {}
public Book(String title, String isbn) {
this.isbn = isbn;
this.title = title;
}
public String isbn;
public String title;
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2016 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.cassandra.test.integration.support;
import java.util.List;
import org.springframework.cassandra.core.QueryForListListener;
/**
* {@link QueryForListListener} suitable for tests.
*
* @author Matthew T. Adams
* @author David Webb
*/
public class ListListener<T> extends CallbackSynchronizationSupport implements QueryForListListener<T> {
private volatile Exception exception;
private volatile List<T> result;
/**
* Allow instances only using {@link #create()}
*/
private ListListener() {}
/**
* @return a new {@link QueryForListListener}.
*/
public static <T> ListListener<T> create() {
return new ListListener<T>();
}
@Override
public void onQueryComplete(List<T> results) {
this.result = results;
countDown();
}
@Override
public void onException(Exception x) {
this.exception = x;
countDown();
}
public Exception getException() {
return exception;
}
public List<T> getResult() {
return result;
}
}

View File

@@ -1,69 +0,0 @@
/*
* Copyright 2016 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.cassandra.test.integration.support;
import java.util.List;
import java.util.Map;
import org.springframework.cassandra.core.QueryForListListener;
import org.springframework.cassandra.core.QueryForListOfMapListener;
/**
* {@link QueryForListListener} suitable for tests.
*
* @author Matthew T. Adams
* @author David Webb
* @author Mark Paluch
*/
public class ListOfMapListener extends CallbackSynchronizationSupport implements QueryForListOfMapListener {
private volatile Exception exception;
private volatile List<Map<String, Object>> result;
/**
* Allow instances only using {@link #create()}
*/
private ListOfMapListener() {}
/**
* @return a new {@link QueryForListListener}.
*/
public static ListOfMapListener create() {
return new ListOfMapListener();
}
@Override
public void onQueryComplete(List<Map<String, Object>> results) {
this.result = results;
countDown();
}
@Override
public void onException(Exception x) {
this.exception = x;
countDown();
}
public Exception getException() {
return exception;
}
public List<Map<String, Object>> getResult() {
return result;
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2016 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.cassandra.test.integration.support;
import java.util.Map;
import org.springframework.cassandra.core.QueryForMapListener;
/**
* {@link QueryForMapListener} suitable for tests.
*
* @author Matthew T. Adams
* @author Mark Paluch
*/
public class MapListener extends CallbackSynchronizationSupport implements QueryForMapListener {
private volatile Map<String, Object> result;
private volatile Exception exception;
/**
* Allow instances only using {@link #create()}
*/
private MapListener() {}
/**
* @return a new {@link MapListener}.
*/
public static MapListener create() {
return new MapListener();
}
@Override
public void onQueryComplete(Map<String, Object> results) {
this.result = results;
countDown();
}
@Override
public void onException(Exception x) {
this.exception = x;
countDown();
}
public Map<String, Object> getResult() {
return result;
}
public Exception getException() {
return exception;
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright 2016 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.cassandra.test.integration.support;
import org.springframework.cassandra.core.QueryForObjectListener;
/**
* {@link QueryForObjectListener} suitable for tests.
*
* @author Matthew T. Adams
* @author David Webb
* @author Mark Paluch
*/
public class ObjectListener<T> extends CallbackSynchronizationSupport implements QueryForObjectListener<T> {
private volatile T result;
private volatile Exception exception;
/**
* Allow instances only using {@link #create()}
*/
private ObjectListener() {}
/**
* @return a new {@link ObjectListener}.
*/
public static <T> ObjectListener<T> create() {
return new ObjectListener<T>();
}
@Override
public void onQueryComplete(T result) {
this.result = result;
countDown();
}
@Override
public void onException(Exception x) {
this.exception = x;
countDown();
}
public T getResult() {
return result;
}
public Exception getException() {
return exception;
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2016 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.cassandra.test.integration.support;
import org.springframework.cassandra.core.AsynchronousQueryListener;
import com.datastax.driver.core.ResultSetFuture;
/**
* {@link AsynchronousQueryListener} suitable for usage in tests.
*
* @author Matthew T. Adams
* @author David Webb
* @author Mark Paluch
*/
public class QueryListener extends CallbackSynchronizationSupport implements AsynchronousQueryListener {
private volatile ResultSetFuture resultSetFuture;
/**
* Allow instances only using {@link #create()}
*/
private QueryListener() {}
/**
* @return a new {@link QueryListener}.
*/
public static QueryListener create() {
return new QueryListener();
}
@Override
public void onQueryComplete(ResultSetFuture resultSetFuture) {
this.resultSetFuture = resultSetFuture;
countDown();
}
public ResultSetFuture getResultSetFuture() {
return resultSetFuture;
}
}

View File

@@ -0,0 +1,236 @@
/*
* Copyright 2016 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.List;
import java.util.function.Consumer;
import org.springframework.cassandra.core.AsyncCqlOperations;
import org.springframework.cassandra.core.QueryOptions;
import org.springframework.cassandra.core.WriteOptions;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.driver.core.Statement;
/**
* Interface specifying a basic set of asynchronous Cassandra operations. Implemented by {@link AsyncCassandraTemplate}.
* Not often used directly, but a useful option to enhance testability, as it can easily be mocked or stubbed.
*
* @author Mark Paluch
* @since 2.0
* @see AsyncCassandraTemplate
*/
public interface AsyncCassandraOperations {
// -------------------------------------------------------------------------
// Methods dealing with static CQL
// -------------------------------------------------------------------------
/**
* Execute a {@code SELECT} query and convert the resulting items to a {@link List} of entities.
*
* @param cql must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> select(String cql, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items notifying {@link Consumer} for each entity.
*
* @param cql must not be {@literal null}.
* @param entityConsumer object that will be notified on each entity, one object at a time, must not be
* {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the completion handle
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<Void> select(String cql, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting item to an entity.
*
* @param cql must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOne(String cql, Class<T> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.driver.core.Statement
// -------------------------------------------------------------------------
/**
* Execute a {@code SELECT} query and convert the resulting items to a {@link List} of entities.
*
* @param statement must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<List<T>> select(Statement statement, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting items notifying {@link Consumer} for each entity.
*
* @param statement must not be {@literal null}.
* @param entityConsumer object that will be notified on each entity, one object at a time, must not be
* {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the completion handle
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<Void> select(Statement statement, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting item to an entity.
*
* @param statement must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOne(Statement statement, Class<T> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with entities
// -------------------------------------------------------------------------
/**
* Execute the Select by {@code id} for the given {@code entityClass}.
*
* @param id must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> selectOneById(Object id, Class<T> entityClass) throws DataAccessException;
/**
* Determine whether the row {@code entityClass} with the given {@code id} exists.
*
* @param id must not be {@literal null}.
* @param entityClass must not be {@literal null}.
* @return {@literal true}, if the object exists.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> exists(Object id, Class<?> entityClass) throws DataAccessException;
/**
* Returns the number of rows for the given entity class.
*
* @param entityClass must not be {@literal null}.
* @return the number of existing entities.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Long> count(Class<?> entityClass) throws DataAccessException;
/**
* Insert the given entity and return the entity if the insert was applied.
*
* @param entity The entity to insert, must not be {@literal null}.
* @return the inserted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> insert(T entity) throws DataAccessException;
/**
* Insert the given entity applying {@link WriteOptions} and return the entity if the insert was applied.
*
* @param entity The entity to insert, must not be {@literal null}.
* @param options may be {@literal null}.
* @return the inserted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> insert(T entity, WriteOptions options) throws DataAccessException;
/**
* Update the given entity and return the entity if the update was applied.
*
* @param entity The entity to update, must not be {@literal null}.
* @return the updated entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> update(T entity) throws DataAccessException;
/**
* Update the given entity applying {@link WriteOptions} and return the entity if the update was applied.
*
* @param entity The entity to update, must not be {@literal null}.
* @param options may be {@literal null}.
* @return the updated entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> update(T entity, WriteOptions options) throws DataAccessException;
/**
* Remove the given object from the table by id.
*
* @param id must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return {@literal true} if the deletion was applied.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Boolean> deleteById(Object id, Class<?> entityClass) throws DataAccessException;
/**
* Delete the given entity and return the entity if the delete was applied.
*
* @param entity must not be {@literal null}.
* @return the deleted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> delete(T entity) throws DataAccessException;
/**
* Delete the given entity applying {@link QueryOptions} and return the entity if the delete was applied.
*
* @param entity must not be {@literal null}.
* @param options may be {@literal null}.
* @return the deleted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> ListenableFuture<T> delete(T entity, QueryOptions options) throws DataAccessException;
/**
* Execute a {@code TRUNCATE} query to remove all entities of a given class.
*
* @param entityClass The entity type must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
ListenableFuture<Void> truncate(Class<?> entityClass) throws DataAccessException;
/**
* Returns the underlying {@link CassandraConverter}.
*
* @return the underlying {@link CassandraConverter}.
*/
CassandraConverter getConverter();
/**
* Expose the underlying {@link AsyncCqlOperationsOperations} to allow asynchronous CQL operations.
*
* @return the underlying {@link AsyncCqlOperations}.
* @see AsyncCqlOperations
*/
AsyncCqlOperations getAsyncCqlOperations();
}

View File

@@ -0,0 +1,467 @@
/*
* Copyright 2016 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.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.cassandra.core.AsyncCqlOperations;
import org.springframework.cassandra.core.AsyncCqlTemplate;
import org.springframework.cassandra.core.AsyncSessionCallback;
import org.springframework.cassandra.core.CqlProvider;
import org.springframework.cassandra.core.GuavaListenableFutureAdapter;
import org.springframework.cassandra.core.QueryOptions;
import org.springframework.cassandra.core.WriteOptions;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.cassandra.core.support.CQLExceptionTranslator;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.SimpleStatement;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.exceptions.DriverException;
import com.datastax.driver.core.querybuilder.Delete;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.core.querybuilder.Truncate;
import com.datastax.driver.core.querybuilder.Update;
/**
* Primary implementation of {@link AsyncCassandraOperations}. It simplifies the use of asynchronous Cassandra usage and
* helps to avoid common errors. It executes core Cassandra workflow. This class executes CQL queries or updates,
* initiating iteration over {@link ResultSet} and catching Cassandra exceptions and translating them to the generic,
* more informative exception hierarchy defined in the {@code org.springframework.dao} package.
* <p>
* Can be used within a service implementation via direct instantiation with a {@link Session} reference, or get
* prepared in an application context and given to services as bean reference.
* <p>
* Note: The {@link Session} should always be configured as a bean in the application context, in the first case given
* to the service directly, in the second case to the prepared template.
*
* @author Mark Paluch
* @since 2.0
*/
public class AsyncCassandraTemplate implements AsyncCassandraOperations {
private final CQLExceptionTranslator exceptionTranslator;
private final CassandraConverter converter;
private final CassandraMappingContext mappingContext;
private final AsyncCqlOperations cqlOperations;
/**
* Creates an instance of {@link AsyncCassandraTemplate} initialized with the given {@link Session} and a default
* {@link MappingCassandraConverter}.
*
* @param session {@link Session} used to interact with Cassandra; must not be {@literal null}.
* @see CassandraConverter
* @see Session
*/
public AsyncCassandraTemplate(Session session) {
this(session, newConverter());
}
/**
* Creates an instance of {@link AsyncCassandraTemplate} initialized with the given {@link Session} and
* {@link CassandraConverter}.
*
* @param session {@link Session} used to interact with Cassandra; must not be {@literal null}.
* @param converter {@link CassandraConverter} used to convert between Java and Cassandra types; must not be
* {@literal null}.
* @see CassandraConverter
* @see Session
*/
public AsyncCassandraTemplate(Session session, CassandraConverter converter) {
Assert.notNull(session, "Session must not be null");
Assert.notNull(converter, "CassandraConverter must not be null");
this.converter = converter;
this.mappingContext = converter.getMappingContext();
AsyncCqlTemplate asyncCqlTemplate = new AsyncCqlTemplate(session);
this.exceptionTranslator = asyncCqlTemplate.getExceptionTranslator();
this.cqlOperations = asyncCqlTemplate;
}
/**
* Creates an instance of {@link AsyncCassandraTemplate} initialized with the given {@link AsyncCqlTemplate} and
* {@link CassandraConverter}.
*
* @param asyncCqlTemplate {@link AsyncCqlTemplate} used to interact with Cassandra; must not be {@literal null}.
* @param converter {@link CassandraConverter} used to convert between Java and Cassandra types; must not be
* {@literal null}.
* @see CassandraConverter
* @see Session
*/
public AsyncCassandraTemplate(AsyncCqlTemplate asyncCqlTemplate, CassandraConverter converter) {
Assert.notNull(asyncCqlTemplate, "AsyncCqlTemplate must not be null");
Assert.notNull(converter, "CassandraConverter must not be null");
this.converter = converter;
this.mappingContext = converter.getMappingContext();
this.cqlOperations = asyncCqlTemplate;
this.exceptionTranslator = asyncCqlTemplate.getExceptionTranslator();
}
private static MappingCassandraConverter newConverter() {
MappingCassandraConverter converter = new MappingCassandraConverter();
converter.afterPropertiesSet();
return converter;
}
// -------------------------------------------------------------------------
// Methods dealing with static CQL
// -------------------------------------------------------------------------
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#select(java.lang.String, java.lang.Class)
*/
@Override
public <T> ListenableFuture<List<T>> select(String cql, Class<T> entityClass) {
Assert.hasText(cql, "Statement must not be empty");
return select(new SimpleStatement(cql), entityClass);
}
@Override
public <T> ListenableFuture<Void> select(String cql, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException {
Assert.hasText(cql, "Statement must not be empty");
Assert.notNull(entityConsumer, "Entity Consumer must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
return select(new SimpleStatement(cql), entityConsumer, entityClass);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#selectOne(java.lang.String, java.lang.Class)
*/
@Override
public <T> ListenableFuture<T> selectOne(String cql, Class<T> entityClass) {
Assert.hasText(cql, "Statement must not be empty");
Assert.notNull(entityClass, "Entity type must not be null");
return selectOne(new SimpleStatement(cql), entityClass);
}
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.driver.core.Statement
// -------------------------------------------------------------------------
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#select(com.datastax.driver.core.Statement, java.lang.Class)
*/
@Override
public <T> ListenableFuture<List<T>> select(Statement statement, Class<T> entityClass) {
Assert.notNull(statement, "Statement must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
return cqlOperations.query(statement, (row, rowNum) -> converter.read(entityClass, row));
}
@Override
public <T> ListenableFuture<Void> select(Statement statement, Consumer<T> entityConsumer, Class<T> entityClass)
throws DataAccessException {
Assert.notNull(statement, "Statement must not be null");
Assert.notNull(entityConsumer, "Entity Consumer must not be empty");
Assert.notNull(entityClass, "Entity type must not be null");
return cqlOperations.query(statement, (row) -> {
entityConsumer.accept(converter.read(entityClass, row));
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#selectOne(com.datastax.driver.core.Statement, java.lang.Class)
*/
@Override
public <T> ListenableFuture<T> selectOne(Statement statement, Class<T> entityClass) {
return new MappingListenableFutureAdapter<>(select(statement, entityClass), list -> {
if (list.isEmpty()) {
return null;
}
return list.get(0);
});
}
// -------------------------------------------------------------------------
// Methods dealing with entities
// -------------------------------------------------------------------------
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#selectOneById(java.lang.Object, java.lang.Class)
*/
@Override
public <T> ListenableFuture<T> selectOneById(Object id, Class<T> entityClass) {
Assert.notNull(id, "Id must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
CassandraPersistentEntity<?> entity = getPersistentEntity(entityClass);
Select select = QueryBuilder.select().all().from(entity.getTableName().toCql());
converter.write(id, select.where(), entity);
return selectOne(select, entityClass);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#exists(java.lang.Object, java.lang.Class)
*/
@Override
public ListenableFuture<Boolean> exists(Object id, Class<?> entityClass) {
Assert.notNull(id, "Id must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
CassandraPersistentEntity<?> entity = getPersistentEntity(entityClass);
Select select = QueryBuilder.select().from(entity.getTableName().toCql());
converter.write(id, select.where(), entity);
return new MappingListenableFutureAdapter<>(cqlOperations.queryForResultSet(select),
resultSet -> resultSet.iterator().hasNext());
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#count(java.lang.Class)
*/
@Override
public ListenableFuture<Long> count(Class<?> entityClass) {
Assert.notNull(entityClass, "Entity type must not be null");
Select select = QueryBuilder.select().countAll().from(getPersistentEntity(entityClass).getTableName().toCql());
return cqlOperations.queryForObject(select, Long.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#insert(java.lang.Object)
*/
@Override
public <T> ListenableFuture<T> insert(T entity) {
return insert(entity, null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#insert(java.lang.Object, org.springframework.cassandra.core.WriteOptions)
*/
@Override
public <T> ListenableFuture<T> insert(T entity, WriteOptions options) {
Assert.notNull(entity, "Entity must not be null");
CqlIdentifier tableName = getTableName(entity);
Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, converter);
return new MappingListenableFutureAdapter<>(cqlOperations.execute(new AsyncStatementCallback(insert)),
resultSet -> resultSet.wasApplied() ? entity : null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#update(java.lang.Object)
*/
@Override
public <T> ListenableFuture<T> update(T entity) {
return update(entity, null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#update(java.lang.Object, org.springframework.cassandra.core.WriteOptions)
*/
@Override
public <T> ListenableFuture<T> update(T entity, WriteOptions options) {
Assert.notNull(entity, "Entity must not be null");
CqlIdentifier tableName = getTableName(entity);
Update update = QueryUtils.createUpdateQuery(tableName.toCql(), entity, options, converter);
return new MappingListenableFutureAdapter<>(cqlOperations.execute(new AsyncStatementCallback(update)),
resultSet -> resultSet.wasApplied() ? entity : null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#deleteById(java.lang.Object, java.lang.Class)
*/
@Override
public ListenableFuture<Boolean> deleteById(Object id, Class<?> entityClass) {
Assert.notNull(id, "Id must not be null");
Assert.notNull(entityClass, "Entity type must not be null");
CassandraPersistentEntity<?> entity = getPersistentEntity(entityClass);
Delete delete = QueryBuilder.delete().from(entity.getTableName().toCql());
converter.write(id, delete.where(), entity);
return cqlOperations.execute(delete);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#delete(java.lang.Object)
*/
@Override
public <T> ListenableFuture<T> delete(T entity) {
return delete(entity, null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#delete(java.lang.Object, org.springframework.cassandra.core.QueryOptions)
*/
@Override
public <T> ListenableFuture<T> delete(T entity, QueryOptions options) {
Assert.notNull(entity, "Entity must not be null");
CqlIdentifier tableName = getTableName(entity);
Delete delete = QueryUtils.createDeleteQuery(tableName.toCql(), entity, options, converter);
return new MappingListenableFutureAdapter<>(cqlOperations.execute(new AsyncStatementCallback(delete)),
resultSet -> resultSet.wasApplied() ? entity : null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#truncate(java.lang.Class)
*/
@Override
public ListenableFuture<Void> truncate(Class<?> entityClass) {
Assert.notNull(entityClass, "Entity type must not be null");
Truncate truncate = QueryBuilder.truncate(getPersistentEntity(entityClass).getTableName().toCql());
return new MappingListenableFutureAdapter<>(cqlOperations.execute(truncate), aBoolean -> null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#getConverter()
*/
@Override
public CassandraConverter getConverter() {
return converter;
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.AsyncCassandraOperations#getAsyncCqlOperations()
*/
@Override
public AsyncCqlOperations getAsyncCqlOperations() {
return cqlOperations;
}
private <T> CassandraPersistentEntity<?> getPersistentEntity(Class<T> entityClass) {
Assert.notNull(entityClass, "Entity type must not be null");
CassandraPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
if (entity == null) {
throw new InvalidDataAccessApiUsageException(
String.format("No Persistent Entity information found for the class [%s]", entityClass.getName()));
}
return entity;
}
private CqlIdentifier getTableName(Object entity) {
return getPersistentEntity(ClassUtils.getUserClass(entity)).getTableName();
}
private static class MappingListenableFutureAdapter<T, S>
extends org.springframework.util.concurrent.ListenableFutureAdapter<T, S> {
private final Function<S, T> mapper;
public MappingListenableFutureAdapter(ListenableFuture<S> adaptee, Function<S, T> mapper) {
super(adaptee);
this.mapper = mapper;
}
@Override
protected T adapt(S adapteeResult) throws ExecutionException {
return mapper.apply(adapteeResult);
}
}
private class AsyncStatementCallback implements AsyncSessionCallback<ResultSet>, CqlProvider {
private final Statement statement;
AsyncStatementCallback(Statement statement) {
this.statement = statement;
}
@Override
public ListenableFuture<ResultSet> doInSession(Session session) throws DriverException, DataAccessException {
return new GuavaListenableFutureAdapter<>(session.executeAsync(statement), e -> {
if (e instanceof DriverException) {
return exceptionTranslator.translate("AsyncStatementCallback", getCql(), (DriverException) e);
}
return exceptionTranslator.translateExceptionIfPossible(e);
});
}
@Override
public String getCql() {
return statement.toString();
}
}
}

View File

@@ -47,27 +47,6 @@ public interface CassandraAdminOperations extends CassandraOperations {
void createTable(boolean ifNotExists, CqlIdentifier tableName, Class<?> entityClass,
Map<String, Object> optionsByName);
/**
* Add columns to the given table from the given class. If parameter dropRemovedAttributColumns is true, then this
* effectively becomes a synchronization operation between the class's fields and the existing table's columns.
*
* @param tableName The name of the existing table.
* @param entityClass The class whose fields determine the columns added.
* @param dropRemovedAttributeColumns Whether to drop columns that exist on the table but that don't have
* corresponding fields in the class. If true, this effectively becomes a synchronziation operation.
*/
void alterTable(CqlIdentifier tableName, Class<?> entityClass, boolean dropRemovedAttributeColumns);
/**
* Drops the existing table with the given name and creates a new one; basically a {@link #dropTable(String)} followed
* by a {@link #createTable(boolean, String, Class, Map)}.
*
* @param tableName The name of the table.
* @param entityClass The class whose fields determine the new table's columns.
* @param optionsByName Table options, given by the string option name and the appropriate option value.
*/
void replaceTable(CqlIdentifier tableName, Class<?> entityClass, Map<String, Object> optionsByName);
/**
* Drops the named table.
*
@@ -86,7 +65,7 @@ public interface CassandraAdminOperations extends CassandraOperations {
/**
* Returns {@link KeyspaceMetadata} for the current keyspace.
*
*
* @return {@link KeyspaceMetadata} for the current keyspace.
* @since 1.5
*/
@@ -94,7 +73,7 @@ public interface CassandraAdminOperations extends CassandraOperations {
/**
* Drops a user type.
*
*
* @param typeName must not be {@literal null}.
* @since 1.5
*/

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.data.cassandra.core;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
@@ -23,19 +22,19 @@ import org.slf4j.LoggerFactory;
import org.springframework.cassandra.core.SessionCallback;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.cassandra.core.cql.generator.CreateTableCqlGenerator;
import org.springframework.cassandra.core.cql.generator.DropTableCqlGenerator;
import org.springframework.cassandra.core.cql.generator.DropUserTypeCqlGenerator;
import org.springframework.cassandra.core.keyspace.CreateTableSpecification;
import org.springframework.cassandra.core.keyspace.DropTableSpecification;
import org.springframework.cassandra.core.keyspace.DropUserTypeSpecification;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.util.CqlUtils;
import org.springframework.util.Assert;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.TableMetadata;
import com.datastax.driver.core.UserType;
/**
* Default implementation of {@link CassandraAdminOperations}.
@@ -65,71 +64,11 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand
public void createTable(final boolean ifNotExists, final CqlIdentifier tableName, Class<?> entityClass,
Map<String, Object> optionsByName) {
final CassandraPersistentEntity<?> entity = getCassandraMappingContext().getPersistentEntity(entityClass);
CassandraPersistentEntity<?> entity = getPersistentEntity(entityClass);
CreateTableSpecification createTableSpecification = getConverter().getMappingContext()
.getCreateTableSpecificationFor(entity).ifNotExists(ifNotExists);
execute(new SessionCallback<Object>() {
@Override
public Object doInSession(Session s) throws DataAccessException {
String cql = new CreateTableCqlGenerator(
getCassandraMappingContext().getCreateTableSpecificationFor(entity).ifNotExists(ifNotExists)).toCql();
log.debug(cql);
s.execute(cql);
return null;
}
});
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraAdminOperations#alterTable(org.springframework.cassandra.core.cql.CqlIdentifier, java.lang.Class, boolean)
*/
@Override
public void alterTable(CqlIdentifier tableName, Class<?> entityClass, boolean dropRemovedAttributeColumns) {
throw new UnsupportedOperationException("not yet implemented");
}
/*
* (non-Javadoc)
* @see org.springframework.data.cassandra.core.CassandraAdminOperations#replaceTable(org.springframework.cassandra.core.cql.CqlIdentifier, java.lang.Class, java.util.Map)
*/
@Override
public void replaceTable(CqlIdentifier tableName, Class<?> entityClass, Map<String, Object> optionsByName) {
dropTable(tableName);
createTable(false, tableName, entityClass, optionsByName);
}
/**
* Create a list of query operations to alter the table for the given entity
*
* @param entityClass
* @param tableName
*/
protected void doAlterTable(Class<?> entityClass, String keyspace, CqlIdentifier tableName) {
CassandraPersistentEntity<?> entity = getCassandraMappingContext().getPersistentEntity(entityClass);
Assert.notNull(entity);
final TableMetadata tableMetadata = getTableMetadata(keyspace, tableName);
final List<String> queryList = CqlUtils.alterTable(tableName.toCql(), entity, tableMetadata);
execute(new SessionCallback<Object>() {
@Override
public Object doInSession(Session s) throws DataAccessException {
for (String q : queryList) {
log.info(q);
s.execute(q);
}
return null;
}
});
getCqlOperations().execute(CreateTableCqlGenerator.toCql(createTableSpecification));
}
public void dropTable(Class<?> entityClass) {
@@ -142,12 +81,7 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand
*/
@Override
public void dropTable(CqlIdentifier tableName) {
Assert.notNull(tableName, "Table name must not be null");
log.info("Dropping table => " + tableName);
execute(DropTableSpecification.dropTable(tableName));
getCqlOperations().execute(DropTableCqlGenerator.toCql(DropTableSpecification.dropTable(tableName)));
}
/*
@@ -158,10 +92,7 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand
public void dropUserType(CqlIdentifier typeName) {
Assert.notNull(typeName, "Type name must not be null");
log.info("Dropping user type => {}", typeName);
execute(DropUserTypeCqlGenerator.toCql(DropUserTypeSpecification.dropType(typeName)));
getCqlOperations().execute(DropUserTypeCqlGenerator.toCql(DropUserTypeSpecification.dropType(typeName)));
}
/*
@@ -169,17 +100,13 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand
* @see org.springframework.data.cassandra.core.CassandraAdminOperations#getTableMetadata(java.lang.String, org.springframework.cassandra.core.cql.CqlIdentifier)
*/
@Override
public TableMetadata getTableMetadata(final String keyspace, final CqlIdentifier tableName) {
public TableMetadata getTableMetadata(String keyspace, CqlIdentifier tableName) {
Assert.hasText(keyspace, "Keyspace name must not be empty");
Assert.notNull(tableName, "Table name must not be null");
return execute(new SessionCallback<TableMetadata>() {
@Override
public TableMetadata doInSession(Session s) {
return s.getCluster().getMetadata().getKeyspace(keyspace).getTable(tableName.toCql());
}
});
return getCqlOperations().execute((SessionCallback<TableMetadata>) session -> session.getCluster().getMetadata()
.getKeyspace(keyspace).getTable(tableName.toCql()));
}
/*
@@ -189,7 +116,7 @@ public class CassandraAdminTemplate extends CassandraTemplate implements Cassand
@Override
public KeyspaceMetadata getKeyspaceMetadata() {
return execute(new SessionCallback<KeyspaceMetadata>() {
return getCqlOperations().execute(new SessionCallback<KeyspaceMetadata>() {
@Override
public KeyspaceMetadata doInSession(Session s) throws DataAccessException {

View File

@@ -36,16 +36,19 @@ class CassandraBatchTemplate implements CassandraBatchOperations {
static final Object[] EMPTY_ARRAY = new Object[0];
private AtomicBoolean executed = new AtomicBoolean();
private final Batch batch;
private final CassandraOperations operations;
private final CassandraTemplate cassandraTemplate;
/**
* Creates a new {@link CassandraBatchTemplate} given {@link CassandraOperations}.
*
* @param operations must not be {@literal null}.
*/
public CassandraBatchTemplate(CassandraOperations operations) {
public CassandraBatchTemplate(CassandraTemplate cassandraTemplate) {
Assert.notNull(operations, "CassandraOperations must not be null");
Assert.notNull(cassandraTemplate, "CassandraTemplate must not be null");
this.cassandraTemplate = cassandraTemplate;
this.operations = operations;
this.batch = QueryBuilder.batch();
}
@@ -57,7 +60,7 @@ class CassandraBatchTemplate implements CassandraBatchOperations {
public void execute() {
if (executed.compareAndSet(false, true)) {
cassandraTemplate.execute(batch);
operations.getCqlOperations().execute(batch);
return;
}
@@ -98,7 +101,7 @@ class CassandraBatchTemplate implements CassandraBatchOperations {
for (Object entity : nullSafeIterable(entities)) {
Assert.notNull(entity, "Entity must not be null");
batch.add(cassandraTemplate.createInsertQuery(entity, null));
batch.add(QueryUtils.createInsertQuery(getTableName(entity), entity, null, operations.getConverter()));
}
return this;
@@ -124,7 +127,7 @@ class CassandraBatchTemplate implements CassandraBatchOperations {
for (Object entity : nullSafeIterable(entities)) {
Assert.notNull(entity, "Entity must not be null");
batch.add(cassandraTemplate.createUpdateQuery(entity, null));
batch.add(QueryUtils.createUpdateQuery(getTableName(entity), entity, null, operations.getConverter()));
}
return this;
@@ -150,7 +153,7 @@ class CassandraBatchTemplate implements CassandraBatchOperations {
for (Object entity : nullSafeIterable(entities)) {
Assert.notNull(entity, "Entity must not be null");
batch.add(cassandraTemplate.createDeleteQuery(entity, null));
batch.add(QueryUtils.createDeleteQuery(getTableName(entity), entity, null, operations.getConverter()));
}
return this;
@@ -160,11 +163,17 @@ class CassandraBatchTemplate implements CassandraBatchOperations {
Assert.state(!executed.get(), "This Cassandra Batch was already executed");
}
private String getTableName(Object entity) {
Assert.notNull(entity, "Entity must not be null");
return operations.getTableName(entity.getClass()).toCql();
}
private <T> Iterable<T> nullSafeIterable(T... array) {
return (array == null ? Collections.<T>emptyList() : Arrays.asList(array));
return (array == null ? Collections.<T> emptyList() : Arrays.asList(array));
}
private <T> Iterable<T> nullSafeIterable(Iterable<T> iterable) {
return (iterable != null ? iterable : Collections.<T>emptyList());
return (iterable != null ? iterable : Collections.<T> emptyList());
}
}

View File

@@ -1,11 +1,11 @@
/*
* Copyright 2013-2016 the original author or authors
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
* 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,
@@ -17,43 +17,30 @@ package org.springframework.data.cassandra.core;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.cassandra.core.Cancellable;
import org.springframework.cassandra.core.CqlOperations;
import org.springframework.cassandra.core.QueryForObjectListener;
import org.springframework.cassandra.core.QueryOptions;
import org.springframework.cassandra.core.WriteOptions;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.convert.CassandraConverter;
import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.core.Statement;
/**
* Operations for interacting with Cassandra. These operations are used by the Repository implementation, but can also
* be used directly when that is desired by the developer.
* <h3>Deprecation note</h3>
* <p>
* Methods accepting a {@link List} of entities perform batching operations (insert/update/delete). This can be fine for
* entities sharing a partition key but leads in most cases to distributed batches across a Cassandra cluster which is
* an anti-pattern. Please use {@link #batchOps()} if your intention is batching. As of Version 1.5, all methods
* accepting a {@link List} of entities are deprecated because there is no alternative of inserting multiple rows in an
* atomic way that guarantees not to harm Cassandra performance. These methods will be removed in Version 2.0. Please
* issue multiple calls to the corresponding single-entity method.
* <p>
* {@link CassandraOperations} mixes synchronous and asynchronous methods so asynchronous methods are subject to be
* moved into an asynchronous Cassandra template.
*
* Interface specifying a basic set of Cassandra operations. Implemented by {@link CassandraTemplate}. Not often used
* directly, but a useful option to enhance testability, as it can easily be mocked or stubbed.
*
* @author Alex Shvid
* @author David Webb
* @author Matthew Adams
* @author Mark Paluch
* @see CassandraTemplate
* @see CqlOperations
* @see Select
* @see WriteListener
* @see DeletionListener
* @see QueryForObjectListener
* @see Statement
*/
public interface CassandraOperations extends CqlOperations {
public interface CassandraOperations {
/**
* The table name used for the specified class by this template.
@@ -63,552 +50,198 @@ public interface CassandraOperations extends CqlOperations {
*/
CqlIdentifier getTableName(Class<?> entityClass);
/**
* Executes the given select {@code query} on the entity table of the specified {@code type} backed by a Cassandra
* {@link com.datastax.driver.core.ResultSet}.
* <p>
* Returns a {@link java.util.Iterator} that wraps the Cassandra {@link com.datastax.driver.core.ResultSet}.
*
* @param <T> element return type.
* @param query query to execute. Must not be empty or {@literal null}.
* @param entityClass Class type of the elements in the {@link Iterator} stream. Must not be {@literal null}.
* @return an {@link Iterator} (stream) over the elements in the query result set.
* @since 1.5
*/
<T> Iterator<T> stream(String query, Class<T> entityClass);
// -------------------------------------------------------------------------
// Methods dealing with static CQL
// -------------------------------------------------------------------------
/**
* Execute query and convert ResultSet to the list of entities.
* Execute a {@code SELECT} query and convert the resulting items to a {@link List} of entities.
*
* @param cql must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<T> List<T> select(String cql, Class<T> entityClass);
<T> List<T> select(String cql, Class<T> entityClass) throws DataAccessException;
/**
* Execute the Select Query and convert to the list of entities.
* Execute a {@code SELECT} query and convert the resulting items to a {@link Iterator} of entities.
* <p>
* Returns a {@link Iterator} that wraps the Cassandra {@link com.datastax.driver.core.ResultSet}.
*
* @param select must not be {@literal null}.
* @param <T> element return type.
* @param cql query to execute. Must not be empty or {@literal null}.
* @param entityClass Class type of the elements in the {@link Iterator} stream. Must not be {@literal null}.
* @return an {@link Iterator} (stream) over the elements in the query result set.
* @throws DataAccessException if there is any problem executing the query.
* @since 1.5
*/
<T> Stream<T> stream(String cql, Class<T> entityClass) throws DataAccessException;
/**
* Execute a {@code SELECT} query and convert the resulting item to an entity.
*
* @param cql must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> T selectOne(String cql, Class<T> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with com.datastax.driver.core.Statement
// -------------------------------------------------------------------------
/**
* Execute a {@code SELECT} query and convert the resulting items to a {@link List} of entities.
*
* @param statement must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<T> List<T> select(Select select, Class<T> entityClass);
<T> List<T> select(Statement statement, Class<T> entityClass) throws DataAccessException;
/**
* Select objects for the given {@code entityClass} and {@code ids}.
* Execute a {@code SELECT} query and convert the resulting items to a {@link Iterator} of entities.
* <p>
* Returns a {@link Iterator} that wraps the Cassandra {@link com.datastax.driver.core.ResultSet}.
*
* @param entityClass The entity type must not be {@literal null}.
* @param ids must not be {@literal null}.
* @return the converted results
* @param <T> element return type.
* @param statement query to execute. Must not be empty or {@literal null}.
* @param entityClass Class type of the elements in the {@link Iterator} stream. Must not be {@literal null}.
* @return an {@link Iterator} (stream) over the elements in the query result set.
* @throws DataAccessException if there is any problem executing the query.
* @since 1.5
*/
<T> List<T> selectBySimpleIds(Class<T> entityClass, Iterable<?> ids);
<T> Stream<T> stream(Statement statement, Class<T> entityClass) throws DataAccessException;
/**
* @deprecated Calling this method could result in {@link OutOfMemoryError}, as this is a brute force selection.
* Execute a {@code SELECT} query and convert the resulting item to an entity.
*
* @param statement must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return A list of all entities of type <code>T</code>.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
@Deprecated
<T> List<T> selectAll(Class<T> entityClass);
<T> T selectOne(Statement statement, Class<T> entityClass) throws DataAccessException;
// -------------------------------------------------------------------------
// Methods dealing with entities
// -------------------------------------------------------------------------
/**
* Execute the Select by {@code id} for the given {@code entityClass}.
*
* @param entityClass The entity type must not be {@literal null}.
* @param id must not be {@literal null}.
* @return the converted object or {@literal null}.
*/
<T> T selectOneById(Class<T> entityClass, Object id);
/**
* Execute CQL and convert ResultSet to the entity
*
* @param cql must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> T selectOne(String cql, Class<T> entityClass);
<T> T selectOneById(Object id, Class<T> entityClass) throws DataAccessException;
/**
* Execute Select query and convert ResultSet to the entity
* Select objects for the given {@code entityClass} and {@code ids}.
*
* @param select must not be {@literal null}.
* @param ids must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @return the converted object or {@literal null}.
* @return the converted results
* @throws DataAccessException if there is any problem executing the query.
*/
<T> T selectOne(Select select, Class<T> entityClass);
/**
* Executes the {@link Select} query asynchronously.
*
* @param select The {@link Select} query to execute.
* @param entityClass The entity type must not be {@literal null}.
* @return A {@link Cancellable} that can be used to cancel the query.
*/
<T> Cancellable selectOneAsynchronously(Select select, Class<T> entityClass, QueryForObjectListener<T> listener);
/**
* Executes the string CQL query asynchronously.
*
* @param cql The string query CQL to execute.
* @param entityClass The entity type must not be {@literal null}.
* @return A {@link Cancellable} that can be used to cancel the query.
*/
<T> Cancellable selectOneAsynchronously(String cql, Class<T> entityClass, QueryForObjectListener<T> listener);
/**
* Executes the {@link Select} query asynchronously.
*
* @param select The {@link Select} query to execute.
* @param entityClass The entity type must not be {@literal null}.
* @param options The {@link QueryOptions} to use.
* @return A {@link Cancellable} that can be used to cancel the query.
*/
<T> Cancellable selectOneAsynchronously(Select select, Class<T> entityClass, QueryForObjectListener<T> listener,
QueryOptions options);
/**
* Executes the string CQL query asynchronously.
*
* @param cql The string query CQL to execute.
* @param entityClass The entity type must not be {@literal null}.
* @param options The {@link QueryOptions} to use.
* @return A {@link Cancellable} that can be used to cancel the query.
*/
<T> Cancellable selectOneAsynchronously(String cql, Class<T> entityClass, QueryForObjectListener<T> listener,
QueryOptions options);
<T> List<T> selectBySimpleIds(Iterable<?> ids, Class<T> entityClass) throws DataAccessException;
/**
* Determine whether the row {@code entityClass} with the given {@code id} exists.
*
* @param entityClass The entity type must not be {@literal null}.
* @param id must not be {@literal null}.
* @return true, if the object exists
*/
boolean exists(Class<?> entityClass, Object id);
/**
* Returns the number of rows for the given {@code entityClass} by querying the table of the given entity class.
*
* @param entityClass The entity type must not be {@literal null}.
* @return number of rows
* @return true, if the object exists.
* @throws DataAccessException if there is any problem executing the query.
*/
long count(Class<?> entityClass);
boolean exists(Object id, Class<?> entityClass) throws DataAccessException;
/**
* Insert the given entity.
* Returns the number of rows for the given entity class.
*
* @param entity The entity to insert
* @return The entity given
* @param entityClass must not be {@literal null}.
* @return the number of existing entities.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> T insert(T entity);
long count(Class<?> entityClass) throws DataAccessException;
/**
* Insert the given entity.
* Insert the given entity and return the entity if the insert was applied.
*
* @param entity The entity to insert
* @param options The {@link WriteOptions} to use.
* @return The entity given
* @param entity The entity to insert, must not be {@literal null}.
* @return the inserted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> T insert(T entity, WriteOptions options);
<T> T insert(T entity) throws DataAccessException;
/**
* Insert the given list of entities.
* Insert the given entity applying {@link WriteOptions} and return the entity if the insert was applied.
*
* @param entities The entities to insert.
* @return The entities given.
* @deprecated as of 1.5. This method accepts a {@link List} of entities and inserts all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. Please use
* {@link #batchOps()} for if your intent is batching or issue multiple calls to {@link #insert(Object)}
* as that's the preferred approach. This method will be removed in Version 2.0.
* @param entity The entity to insert, must not be {@literal null}.
* @param options may be {@literal null}.
* @return the inserted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
@Deprecated
<T> List<T> insert(List<T> entities);
<T> T insert(T entity, WriteOptions options) throws DataAccessException;
/**
* Insert the given list of entities.
* Update the given entity and return the entity if the update was applied.
*
* @param entities The entities to insert.
* @param options The {@link WriteOptions} to use.
* @return The entities given.
* @deprecated as of 1.5. This method accepts a {@link List} of entities and inserts all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. Please use
* {@link #batchOps()} for if your intent is batching or issue multiple calls to
* {@link #insert(Object, WriteOptions)} as that's the preferred approach. This method will be removed in
* Version 2.0.
* @param entity The entity to update, must not be {@literal null}.
* @return the updated entity.
* @throws DataAccessException if there is any problem executing the query.
*/
@Deprecated
<T> List<T> insert(List<T> entities, WriteOptions options);
<T> T update(T entity) throws DataAccessException;
/**
* Inserts the given entity asynchronously.
* Update the given entity applying {@link WriteOptions} and return the entity if the update was applied.
*
* @param entity The entity to insert
* @return The entity given
* @see #insertAsynchronously(Object, WriteListener)
* @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor
* {@link #insertAsynchronously(Object, WriteListener)}.
* @param entity The entity to update, must not be {@literal null}.
* @param options may be {@literal null}.
* @return the updated entity.
* @throws DataAccessException if there is any problem executing the query.
*/
@Deprecated
<T> T insertAsynchronously(T entity);
/**
* Inserts the given entity asynchronously.
*
* @param entity The entity to insert
* @return The entity given
* @see #insertAsynchronously(Object, WriteOptions)
* @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor
* {@link #insertAsynchronously(Object, WriteListener, WriteOptions)}.
*/
@Deprecated
<T> T insertAsynchronously(T entity, WriteOptions options);
/**
* Inserts the given entity asynchronously.
*
* @param entity The entity to insert
* @param listener The listener to receive notification of completion
* @return A {@link Cancellable} enabling the cancellation of the operation
*/
<T> Cancellable insertAsynchronously(T entity, WriteListener<T> listener);
/**
* Inserts the given entity asynchronously.
*
* @param entity The entity to insert
* @param listener The listener to receive notification of completion
* @param options The {@link WriteOptions} to use
* @return A {@link Cancellable} enabling the cancellation of the operation
*/
<T> Cancellable insertAsynchronously(T entity, WriteListener<T> listener, WriteOptions options);
/**
* Inserts the given entities asynchronously in a batch.
*
* @param entities The entities to insert
* @return The entities given
* @see #insertAsynchronously(List, WriteListener)
* @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor
* {@link #insertAsynchronously(List, WriteListener)}.
*/
@Deprecated
<T> List<T> insertAsynchronously(List<T> entities);
/**
* Inserts the given entities asynchronously in a batch.
*
* @param entities The entities to insert
* @return The entities given
* @see #insertAsynchronously(List, WriteListener, WriteOptions)
* @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor
* {@link #insertAsynchronously(List, WriteListener, WriteOptions)}.
*/
@Deprecated
<T> List<T> insertAsynchronously(List<T> entities, WriteOptions options);
/**
* Inserts the given entities asynchronously in a batch.
*
* @param entities The entities to insert
* @param listener The listener to receive notification of completion
* @return A {@link Cancellable} enabling the cancellation of the operation
* @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method
* will be removed in Version 2.0.
*/
@Deprecated
<T> Cancellable insertAsynchronously(List<T> entities, WriteListener<T> listener);
/**
* Inserts the given entities asynchronously in a batch.
*
* @param entities The entities to insert
* @param listener The listener to receive notification of completion
* @param options The {@link WriteOptions} to use
* @return A {@link Cancellable} enabling the cancellation of the operation
* @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method
* will be removed in Version 2.0.
*/
@Deprecated
<T> Cancellable insertAsynchronously(List<T> entities, WriteListener<T> listener, WriteOptions options);
/**
* Update the given entity.
*
* @param entity The entity to update
* @return The entity given
*/
<T> T update(T entity);
/**
* Update the given entity.
*
* @param entity The entity to update
* @param options The {@link WriteOptions} to use.
* @return The entity given
*/
<T> T update(T entity, WriteOptions options);
/**
* Update the given list of entities.
*
* @param entities The entities to update.
* @return The entities given.
* @deprecated as of 1.5. This method accepts a {@link List} of entities and updates all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. Please use
* {@link #batchOps()} for if your intent is batching or issue multiple calls to {@link #update(Object)}
* as that's the preferred approach. This method will be removed in Version 2.0.
*/
@Deprecated
<T> List<T> update(List<T> entities);
/**
* Update the given list of entities.
*
* @param entities The entities to update.
* @param options The {@link WriteOptions} to use.
* @return The entities given.
* @deprecated as of 1.5. This method accepts a {@link List} of entities and updates all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. Please use
* {@link #batchOps()} for if your intent is batching or issue multiple calls to
* {@link #update(Object, WriteOptions)} as that's the preferred approach. This method will be removed in
* Version 2.0.
*/
@Deprecated
<T> List<T> update(List<T> entities, WriteOptions options);
/**
* Updates the given entity asynchronously.
*
* @param entity The entity to update
* @return The entity given
* @see #updateAsynchronously(Object, WriteListener)
* @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor
* {@link #updateAsynchronously(Object, WriteListener)}.
*/
@Deprecated
<T> T updateAsynchronously(T entity);
/**
* Updates the given entity asynchronously.
*
* @param entity The entity to update
* @return The entity given
* @see #updateAsynchronously(Object, WriteOptions)
* @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor
* {@link #updateAsynchronously(Object, WriteListener, WriteOptions)}.
*/
@Deprecated
<T> T updateAsynchronously(T entity, WriteOptions options);
/**
* Updates the given entity asynchronously.
*
* @param entity The entity to update
* @param listener The listener to receive notification of completion
* @return A {@link Cancellable} enabling the cancellation of the operation
*/
<T> Cancellable updateAsynchronously(T entity, WriteListener<T> listener);
/**
* Updates the given entity asynchronously.
*
* @param entity The entity to update
* @param listener The listener to receive notification of completion
* @param options The {@link WriteOptions} to use
* @return A {@link Cancellable} enabling the cancellation of the operation
*/
<T> Cancellable updateAsynchronously(T entity, WriteListener<T> listener, WriteOptions options);
/**
* Updates the given entities asynchronously in a batch.
*
* @param entities The entities to update
* @return The entities given
* @see #updateAsynchronously(List, WriteListener)
* @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor
* {@link #updateAsynchronously(List, WriteListener)}.
*/
@Deprecated
<T> List<T> updateAsynchronously(List<T> entities);
/**
* Updates the given entities asynchronously in a batch.
*
* @param entities The entities to update
* @return The entities given
* @see #updateAsynchronously(List, WriteListener, WriteOptions)
* @deprecated as of 1.2, this method does not allow for query cancellation or notification of completion. Favor
* {@link #updateAsynchronously(List, WriteListener, WriteOptions)}.
*/
@Deprecated
<T> List<T> updateAsynchronously(List<T> entities, WriteOptions options);
/**
* Updates the given entities asynchronously in a batch.
*
* @param entities The entities to update
* @param listener The listener to receive notification of completion
* @return A {@link Cancellable} enabling the cancellation of the operation
* @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method
* will be removed in Version 2.0.
*/
@Deprecated
<T> Cancellable updateAsynchronously(List<T> entities, WriteListener<T> listener);
/**
* Updates the given entities asynchronously in a batch.
*
* @param entities The entities to update
* @param listener The listener to receive notification of completion
* @param options The {@link WriteOptions} to use
* @return A {@link Cancellable} enabling the cancellation of the operation
* @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method
* will be removed in Version 2.0.
*/
@Deprecated
<T> Cancellable updateAsynchronously(List<T> entities, WriteListener<T> listener, WriteOptions options);
<T> T update(T entity, WriteOptions options) throws DataAccessException;
/**
* Remove the given object from the table by id.
*
* @param entityClass The entity type must not be {@literal null}.
* @param id must not be {@literal null}.
* @param entityClass The entity type must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
void deleteById(Class<?> entityClass, Object id);
boolean deleteById(Object id, Class<?> entityClass) throws DataAccessException;
/**
* Remove the given object from the table by id.
* Delete the given entity and return the entity if the delete was applied.
*
* @param entity must not be {@literal null}.
* @return the deleted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> void delete(T entity);
<T> T delete(T entity) throws DataAccessException;
/**
* Remove the given object from the table by id.
* Delete the given entity applying {@link QueryOptions} and return the entity if the delete was applied.
*
* @param entity must not be {@literal null}.
* @param options may be {@literal null}.
* @return the deleted entity.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> void delete(T entity, QueryOptions options);
<T> T delete(T entity, QueryOptions options) throws DataAccessException;
/**
* Remove the given objects from the table by id.
*
* @param entities must not be {@literal null}.
* @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. Please use
* {@link #batchOps()} for if your intent is batching or issue multiple calls to {@link #delete(Object)}
* as that's the preferred approach. This method will be removed in Version 2.0.
*/
@Deprecated
<T> void delete(List<T> entities);
/**
* Remove the given objects from the table by id.
*
* @param entities must not be {@literal null}.
* @param options may be {@literal null}.
* @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. Please use
* {@link #batchOps()} for if your intent is batching or issue multiple calls to
* {@link #delete(Object, WriteOptions)} as that's the preferred approach. This method will be removed in
* Version 2.0.
*/
@Deprecated
<T> void delete(List<T> entities, QueryOptions options);
/**
* Deletes all entities of a given class.
* Execute a {@code TRUNCATE} query to remove all entities of a given class.
*
* @param entityClass The entity type must not be {@literal null}.
* @throws DataAccessException if there is any problem executing the query.
*/
<T> void deleteAll(Class<T> entityClass);
/**
* Remove the given object from the table by id.
*
* @param entity The object to delete
*/
<T> Cancellable deleteAsynchronously(T entity);
/**
* Remove the given object from the table by id.
*
* @param entity The object to delete
* @param options The {@link QueryOptions} to use
*/
<T> Cancellable deleteAsynchronously(T entity, QueryOptions options);
/**
* Remove the given object from the table by id.
*
* @param entity The object to delete
* @param listener The {@link DeletionListener} to receive notification upon completion
*/
<T> Cancellable deleteAsynchronously(T entity, DeletionListener<T> listener);
/**
* Remove the given object from the table by id.
*
* @param entity The object to delete
* @param listener The {@link DeletionListener} to receive notification upon completion
* @param options The {@link QueryOptions} to use
*/
<T> Cancellable deleteAsynchronously(T entity, DeletionListener<T> listener, QueryOptions options);
/**
* Remove the given objects from the table by id.
*
* @param entities The objects to delete
* @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method
* will be removed in Version 2.0.
*/
@Deprecated
<T> Cancellable deleteAsynchronously(List<T> entities);
/**
* Remove the given objects from the table by id.
*
* @param entities The objects to delete
* @param listener The {@link DeletionListener} to receive notification upon completion
* @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method
* will be removed in Version 2.0.
*/
@Deprecated
<T> Cancellable deleteAsynchronously(List<T> entities, DeletionListener<T> listener);
/**
* Remove the given objects from the table by id.
*
* @param entities The objects to delete
* @param options The {@link QueryOptions} to use
* @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method
* will be removed in Version 2.0.
*/
@Deprecated
<T> Cancellable deleteAsynchronously(List<T> entities, QueryOptions options);
/**
* Remove the given objects from the table by id.
*
* @param entities The objects to delete
* @param listener The {@link DeletionListener} to receive notification upon completion
* @param options The {@link QueryOptions} to use
* @deprecated as of 1.5. This method accepts a {@link List} of entities and deletes all entities in a batch. That's
* not transparent to users and a Cassandra anti-pattern if used with multiple partition keys. This method
* will be removed in Version 2.0.
*/
@Deprecated
<T> Cancellable deleteAsynchronously(List<T> entities, DeletionListener<T> listener, QueryOptions options);
void truncate(Class<?> entityClass) throws DataAccessException;
/**
* Returns a new {@link CassandraBatchOperations}. Each {@link CassandraBatchOperations} instance can be executed only
@@ -625,4 +258,11 @@ public interface CassandraOperations extends CqlOperations {
*/
CassandraConverter getConverter();
/**
* Expose the underlying {@link CqlOperations} to allow CQL operations.
*
* @return the underlying {@link CqlOperations}.
* @see CqlOperations
*/
CqlOperations getCqlOperations();
}

View File

@@ -90,7 +90,7 @@ public class CassandraPersistentEntitySchemaCreator {
List<CreateUserTypeSpecification> specifications = createUserTypeSpecifications(ifNotExists);
for (CreateUserTypeSpecification specification : specifications) {
cassandraAdminOperations.execute(CreateUserTypeCqlGenerator.toCql(specification));
cassandraAdminOperations.getCqlOperations().execute(CreateUserTypeCqlGenerator.toCql(specification));
}
}
@@ -111,7 +111,7 @@ public class CassandraPersistentEntitySchemaCreator {
List<CreateTableSpecification> specifications = createTableSpecifications(ifNotExists);
for (CreateTableSpecification specification : specifications) {
cassandraAdminOperations.execute(CreateTableCqlGenerator.toCql(specification));
cassandraAdminOperations.getCqlOperations().execute(CreateTableCqlGenerator.toCql(specification));
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2016 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 org.springframework.cassandra.core.CqlTemplate;
import org.springframework.cassandra.core.QueryOptions;
import org.springframework.cassandra.core.QueryOptionsUtil;
import org.springframework.cassandra.core.WriteOptions;
import org.springframework.data.convert.EntityWriter;
import org.springframework.util.Assert;
import com.datastax.driver.core.querybuilder.Delete;
import com.datastax.driver.core.querybuilder.Delete.Where;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Update;
/**
* Simple utility class for working with the QueryBuilder API.
* <p>
* Only intended for internal use.
*
* @author Mark Paluch
* @since 2.0
*/
class QueryUtils {
/**
* Creates a Query Object for an insert.
*
* @param tableName the table name, must not be empty and not {@literal null}.
* @param objectToUpdate the object to save, must not be {@literal null}.
* @param options optional {@link WriteOptions} to apply to the {@link Insert} statement, may be {@literal null}.
* @param entityWriter the {@link EntityWriter} to write insert values.
* @return The Query object to run with session.execute();
*/
public static Insert createInsertQuery(String tableName, Object objectToUpdate, WriteOptions options,
EntityWriter<Object, Object> entityWriter) {
Assert.hasText(tableName, "TableName must not be empty");
Assert.notNull(objectToUpdate, "Object to insert must not be null");
Assert.notNull(entityWriter, "EntityWriter must not be null");
Insert insert = QueryOptionsUtil.addWriteOptions(QueryBuilder.insertInto(tableName), options);
entityWriter.write(objectToUpdate, insert);
return insert;
}
/**
* Creates a Query Object for an Update. The {@link Update} uses the identity and values from the given
* {@code objectsToUpdate}.
*
* @param tableName the table name, must not be empty and not {@literal null}.
* @param objectToUpdate the object to update, must not be {@literal null}.
* @param options optional {@link WriteOptions} to apply to the {@link Update} statement, may be {@literal null}.
* @param entityWriter the {@link EntityWriter} to write update assignments and where clauses.
* @return The Query object to run with session.execute();
*/
public static Update createUpdateQuery(String tableName, Object objectToUpdate, WriteOptions options,
EntityWriter<Object, Object> entityWriter) {
Assert.hasText(tableName, "TableName must not be empty");
Assert.notNull(objectToUpdate, "Object to update must not be null");
Assert.notNull(entityWriter, "EntityWriter must not be null");
Update update = QueryOptionsUtil.addWriteOptions(QueryBuilder.update(tableName), options);
entityWriter.write(objectToUpdate, update);
return update;
}
/**
* Creates a Delete Query Object from an annotated POJO. The {@link Delete} uses the identity from the given
* {@code objectToDelete}.
*
* @param tableName the table name, must not be empty and not {@literal null}.
* @param objectToDelete the object to delete, must not be {@literal null}.
* @param options optional {@link QueryOptions} to apply to the {@link Delete} statement, may be {@literal null}.
* @param entityWriter the {@link EntityWriter} to write delete where clauses.
* @return The Query object to run with session.execute();
*/
public static Delete createDeleteQuery(String tableName, Object objectToDelete, QueryOptions options,
EntityWriter<Object, Object> entityWriter) {
Assert.hasText(tableName, "TableName must not be empty");
Assert.notNull(objectToDelete, "Object to delete must not be null");
Assert.notNull(entityWriter, "EntityWriter must not be null");
Delete.Selection deleteSelection = QueryBuilder.delete();
Delete delete = deleteSelection.from(tableName);
Where where = QueryOptionsUtil.addQueryOptions(delete.where(), options);
entityWriter.write(objectToDelete, where);
return delete;
}
}

View File

@@ -15,10 +15,6 @@
*/
package org.springframework.data.cassandra.core;
import static org.springframework.data.cassandra.core.CassandraTemplate.createDeleteQuery;
import static org.springframework.data.cassandra.core.CassandraTemplate.createInsertQuery;
import static org.springframework.data.cassandra.core.CassandraTemplate.createUpdateQuery;
import org.reactivestreams.Publisher;
import org.springframework.cassandra.core.CqlProvider;
import org.springframework.cassandra.core.DefaultReactiveSessionFactory;
@@ -274,7 +270,7 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations {
CqlIdentifier tableName = getTableName(entity);
Insert insert = createInsertQuery(tableName.toCql(), entity, options, converter);
Insert insert = QueryUtils.createInsertQuery(tableName.toCql(), entity, options, converter);
class InsertCallback implements ReactiveSessionCallback<T>, CqlProvider {
@@ -334,7 +330,7 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations {
CqlIdentifier tableName = getTableName(entity);
Update update = createUpdateQuery(tableName.toCql(), entity, options, converter);
Update update = QueryUtils.createUpdateQuery(tableName.toCql(), entity, options, converter);
class UpdateCallback implements ReactiveSessionCallback<T>, CqlProvider {
@@ -412,7 +408,7 @@ public class ReactiveCassandraTemplate implements ReactiveCassandraOperations {
CqlIdentifier tableName = getTableName(entity);
Delete delete = createDeleteQuery(tableName.toCql(), entity, options, converter);
Delete delete = QueryUtils.createDeleteQuery(tableName.toCql(), entity, options, converter);
class DeleteCallback implements ReactiveSessionCallback<T>, CqlProvider {

View File

@@ -54,14 +54,7 @@ interface CassandraQueryExecution {
*/
@Override
public Object execute(String query, Class<?> type) {
return StreamUtils.createStreamFromIterator(operations.stream(query, type)).map(new Function<Object, Object>() {
@Override
public Object apply(Object t) {
return resultProcessing.convert(t);
}
});
return operations.stream(query, type).map(resultProcessing::convert);
}
}
@@ -118,7 +111,7 @@ interface CassandraQueryExecution {
*/
@Override
public Object execute(String query, Class<?> type) {
return operations.query(query);
return operations.getCqlOperations().queryForResultSet(query);
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.cassandra.repository.query;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.cassandra.core.CassandraOperations;
@@ -73,7 +75,9 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
super(queryMethod, operations);
CodecRegistry codecRegistry = operations.getSession().getCluster().getConfiguration().getCodecRegistry();
Cluster cluster = operations.getCqlOperations().execute(Session::getCluster);
CodecRegistry codecRegistry = cluster.getConfiguration().getCodecRegistry();
this.stringBasedQuery = new StringBasedQuery(query,
new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider), codecRegistry);
}

View File

@@ -25,6 +25,7 @@ import org.springframework.data.cassandra.repository.TypedIdCassandraRepository;
import org.springframework.data.cassandra.repository.query.CassandraEntityInformation;
import org.springframework.util.Assert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
/**
@@ -36,8 +37,8 @@ import com.datastax.driver.core.querybuilder.Select;
*/
public class SimpleCassandraRepository<T, ID extends Serializable> implements TypedIdCassandraRepository<T, ID> {
protected CassandraOperations operations;
protected CassandraEntityInformation<T, ID> entityInformation;
private CassandraOperations operations;
private CassandraEntityInformation<T, ID> entityInformation;
/**
* Creates a new {@link SimpleCassandraRepository} for the given {@link CassandraEntityInformation} and
@@ -67,22 +68,22 @@ public class SimpleCassandraRepository<T, ID extends Serializable> implements Ty
@Override
public T findOne(ID id) {
return operations.selectOneById(entityInformation.getJavaType(), id);
return operations.selectOneById(id, entityInformation.getJavaType());
}
@Override
public boolean exists(ID id) {
return operations.exists(entityInformation.getJavaType(), id);
return operations.exists(id, entityInformation.getJavaType());
}
@Override
public long count() {
return operations.count(entityInformation.getTableName());
return operations.count(entityInformation.getJavaType());
}
@Override
public void delete(ID id) {
operations.deleteById(entityInformation.getJavaType(), id);
operations.deleteById(id, entityInformation.getJavaType());
}
@Override
@@ -97,20 +98,19 @@ public class SimpleCassandraRepository<T, ID extends Serializable> implements Ty
@Override
public void deleteAll() {
operations.truncate(entityInformation.getTableName());
operations.truncate(entityInformation.getJavaType());
}
@Override
public List<T> findAll() {
return operations.selectAll(entityInformation.getJavaType());
Select select = QueryBuilder.select().all().from(entityInformation.getTableName().toCql());
return operations.select(select, entityInformation.getJavaType());
}
@Override
public Iterable<T> findAll(Iterable<ID> ids) {
return operations.selectBySimpleIds(entityInformation.getJavaType(), ids);
}
protected List<T> findAll(Select query) {
return operations.select(query, entityInformation.getJavaType());
return operations.selectBySimpleIds(ids, entityInformation.getJavaType());
}
}

View File

@@ -70,7 +70,7 @@ public class CassandraSessionFactoryBeanUnitTests {
@Before
public void setup() {
when(mockCluster.connect()).thenReturn(mockSession);
when(mockSession.getCluster()).thenReturn(mockCluster);
@@ -84,7 +84,7 @@ public class CassandraSessionFactoryBeanUnitTests {
@Test
public void afterPropertiesSetPerformsSchemaAction() throws Exception {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
@@ -109,7 +109,7 @@ public class CassandraSessionFactoryBeanUnitTests {
@Test
public void afterPropertiesSetThrowsIllegalStateExceptionWhenConverterIsNull() throws Exception {
exception.expect(IllegalStateException.class);
exception.expectMessage("Converter was not properly initialized");
@@ -165,7 +165,7 @@ public class CassandraSessionFactoryBeanUnitTests {
@Test
public void performsSchemaActionDoesNotCallCreateTablesWhenSchemaActionIsNone() {
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
@@ -185,7 +185,7 @@ public class CassandraSessionFactoryBeanUnitTests {
@Test
public void setAndGetConverter() {
assertThat(factoryBean.getConverter()).isNull();
factoryBean.setConverter(mockConverter);
assertThat(factoryBean.getConverter()).isEqualTo(mockConverter);
@@ -194,7 +194,7 @@ public class CassandraSessionFactoryBeanUnitTests {
@Test
public void setConverterToNull() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage("CassandraConverter must not be null");
@@ -203,7 +203,7 @@ public class CassandraSessionFactoryBeanUnitTests {
@Test
public void setAndGetSchemaAction() {
assertThat(factoryBean.getSchemaAction()).isEqualTo(SchemaAction.NONE);
factoryBean.setSchemaAction(SchemaAction.CREATE);
assertThat(factoryBean.getSchemaAction()).isEqualTo(SchemaAction.CREATE);

View File

@@ -39,9 +39,9 @@ public class ColumnReaderUnitTests {
public static final String NON_EXISTENT_COLUMN = "column_name";
@Mock private Row row;
@Mock Row row;
@Mock private ColumnDefinitions columnDefinitions;
@Mock ColumnDefinitions columnDefinitions;
private ColumnReader underTest;

View File

@@ -97,9 +97,9 @@ public class MappingCassandraConverterUnitTests {
@Rule public final ExpectedException expectedException = ExpectedException.none();
@Mock private ColumnDefinitions columnDefinitionsMock;
@Mock ColumnDefinitions columnDefinitionsMock;
@Mock private Row rowMock;
@Mock Row rowMock;
private CassandraMappingContext mappingContext;
private MappingCassandraConverter mappingCassandraConverter;

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2016 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 java.util.concurrent.Future;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cassandra.core.AsyncCqlTemplate;
import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.domain.Person;
import org.springframework.data.cassandra.test.integration.support.SchemaTestUtils;
import org.springframework.util.concurrent.ListenableFuture;
/**
* Integration tests for {@link AsyncCassandraTemplate}.
*
* @author Mark Paluch
*/
public class AsyncCassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest {
private AsyncCassandraTemplate template;
@Before
public void setUp() throws Exception {
MappingCassandraConverter converter = new MappingCassandraConverter();
CassandraTemplate cassandraTemplate = new CassandraTemplate(session, converter);
template = new AsyncCassandraTemplate(new AsyncCqlTemplate(session), converter);
SchemaTestUtils.potentiallyCreateTableFor(Person.class, cassandraTemplate);
SchemaTestUtils.truncate(Person.class, cassandraTemplate);
}
/**
* @see DATACASS-292
*/
@Test
public void insertShouldInsertEntity() {
Person person = new Person("heisenberg", "Walter", "White");
assertThat(getUninterruptibly(template.selectOneById(person.getId(), Person.class))).isNull();
ListenableFuture<Person> insert = template.insert(person);
assertThat(getUninterruptibly(insert)).isNotNull().isEqualTo(person);
assertThat(getUninterruptibly(template.selectOneById(person.getId(), Person.class))).isEqualTo(person);
}
/**
* @see DATACASS-292
*/
@Test
public void shouldInsertAndCountEntities() throws Exception {
Person person = new Person("heisenberg", "Walter", "White");
template.insert(person).get();
ListenableFuture<Long> count = template.count(Person.class);
assertThat(getUninterruptibly(count)).isEqualTo(1L);
}
/**
* @see DATACASS-292
*/
@Test
public void updateShouldUpdateEntity() throws Exception {
Person person = new Person("heisenberg", "Walter", "White");
template.insert(person).get();
person.setFirstname("Walter Hartwell");
Person updated = template.update(person).get();
assertThat(updated).isNotNull();
assertThat(getUninterruptibly(template.selectOneById(person.getId(), Person.class))).isEqualTo(person);
}
/**
* @see DATACASS-292
*/
@Test
public void deleteShouldRemoveEntity() throws Exception {
Person person = new Person("heisenberg", "Walter", "White");
template.insert(person).get();
Person deleted = template.delete(person).get();
assertThat(deleted).isNotNull();
assertThat(getUninterruptibly(template.selectOneById(person.getId(), Person.class))).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void deleteByIdShouldRemoveEntity() throws Exception {
Person person = new Person("heisenberg", "Walter", "White");
template.insert(person).get();
Boolean deleted = template.deleteById(person.getId(), Person.class).get();
assertThat(deleted).isTrue();
assertThat(getUninterruptibly(template.selectOneById(person.getId(), Person.class))).isNull();
}
private static <T> T getUninterruptibly(Future<T> future) {
try {
return future.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}

View File

@@ -0,0 +1,497 @@
/*
* Copyright 2016 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 static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.anyInt;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cassandra.support.exception.CassandraConnectionFailureException;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.domain.Person;
import org.springframework.util.concurrent.ListenableFuture;
import com.datastax.driver.core.ColumnDefinitions;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.exceptions.NoHostAvailableException;
import com.google.common.util.concurrent.AbstractFuture;
/**
* Unit tests for {@link AsyncCassandraTemplate}.
*
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class AsyncCassandraTemplateUnitTests {
@Mock Session session;
@Mock ResultSet resultSet;
@Mock Row row;
@Mock ColumnDefinitions columnDefinitions;
@Captor ArgumentCaptor<Statement> statementCaptor;
private AsyncCassandraTemplate template;
@Before
public void setUp() {
template = new AsyncCassandraTemplate(session);
when(session.executeAsync(anyString())).thenReturn(new TestResultSetFuture(resultSet));
when(session.executeAsync(any(Statement.class))).thenReturn(new TestResultSetFuture(resultSet));
when(resultSet.getColumnDefinitions()).thenReturn(columnDefinitions);
when(row.getColumnDefinitions()).thenReturn(columnDefinitions);
}
/**
* @see DATACASS-292
*/
@Test
public void selectUsingCqlShouldReturnMappedResults() {
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
when(columnDefinitions.contains(anyString())).thenReturn(true);
when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii());
when(columnDefinitions.getIndexOf("id")).thenReturn(0);
when(columnDefinitions.getIndexOf("firstname")).thenReturn(1);
when(columnDefinitions.getIndexOf("lastname")).thenReturn(2);
when(row.getObject(0)).thenReturn("myid");
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
ListenableFuture<List<Person>> list = template.select("SELECT * FROM person", Person.class);
assertThat(getUninterruptibly(list)).hasSize(1).contains(new Person("myid", "Walter", "White"));
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person");
}
/**
* @see DATACASS-292
*/
@Test
public void selectUsingCqlShouldInvokeCallbackWithMappedResults() {
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
when(resultSet.spliterator()).thenReturn(Arrays.asList(row).spliterator());
when(columnDefinitions.contains(anyString())).thenReturn(true);
when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii());
when(columnDefinitions.getIndexOf("id")).thenReturn(0);
when(columnDefinitions.getIndexOf("firstname")).thenReturn(1);
when(columnDefinitions.getIndexOf("lastname")).thenReturn(2);
when(row.getObject(0)).thenReturn("myid");
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
List<Person> list = new ArrayList<>();
ListenableFuture<Void> result = template.select("SELECT * FROM person", list::add, Person.class);
assertThat(getUninterruptibly(result)).isNull();
assertThat(list).hasSize(1).contains(new Person("myid", "Walter", "White"));
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person");
}
/**
* @see DATACASS-292
*/
@Test
public void selectShouldTranslateException() throws Exception {
when(resultSet.iterator()).thenThrow(new NoHostAvailableException(Collections.emptyMap()));
ListenableFuture<List<Person>> list = template.select("SELECT * FROM person", Person.class);
try {
list.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class)
.hasRootCauseInstanceOf(NoHostAvailableException.class);
}
}
/**
* @see DATACASS-292
*/
@Test
public void selectOneShouldReturnMappedResults() {
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
when(columnDefinitions.contains(anyString())).thenReturn(true);
when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii());
when(columnDefinitions.getIndexOf("id")).thenReturn(0);
when(columnDefinitions.getIndexOf("firstname")).thenReturn(1);
when(columnDefinitions.getIndexOf("lastname")).thenReturn(2);
when(row.getObject(0)).thenReturn("myid");
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
ListenableFuture<Person> future = template.selectOne("SELECT * FROM person WHERE id='myid';", Person.class);
assertThat(getUninterruptibly(future)).isEqualTo(new Person("myid", "Walter", "White"));
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';");
}
/**
* @see DATACASS-292
*/
@Test
public void selectOneByIdShouldReturnMappedResults() {
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
when(columnDefinitions.contains(anyString())).thenReturn(true);
when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii());
when(columnDefinitions.getIndexOf("id")).thenReturn(0);
when(columnDefinitions.getIndexOf("firstname")).thenReturn(1);
when(columnDefinitions.getIndexOf("lastname")).thenReturn(2);
when(row.getObject(0)).thenReturn("myid");
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
ListenableFuture<Person> future = template.selectOneById("myid", Person.class);
assertThat(getUninterruptibly(future)).isEqualTo(new Person("myid", "Walter", "White"));
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';");
}
/**
* @see DATACASS-292
*/
@Test
public void existsShouldReturnExistingElement() {
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
when(columnDefinitions.contains(anyString())).thenReturn(true);
when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii());
ListenableFuture<Boolean> future = template.exists("myid", Person.class);
assertThat(getUninterruptibly(future)).isTrue();
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';");
}
/**
* @see DATACASS-292
*/
@Test
public void existsShouldReturnNonExistingElement() {
when(resultSet.iterator()).thenReturn(Collections.emptyIterator());
ListenableFuture<Boolean> future = template.exists("myid", Person.class);
assertThat(getUninterruptibly(future)).isFalse();
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';");
}
/**
* @see DATACASS-292
*/
@Test
public void countShouldExecuteCountQueryElement() {
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
when(row.getLong(0)).thenReturn(42L);
when(columnDefinitions.size()).thenReturn(1);
ListenableFuture<Long> future = template.count(Person.class);
assertThat(getUninterruptibly(future)).isEqualTo(42L);
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT count(*) FROM person;");
}
/**
* @see DATACASS-292
*/
@Test
public void insertShouldInsertEntity() {
when(resultSet.wasApplied()).thenReturn(true);
Person person = new Person("heisenberg", "Walter", "White");
ListenableFuture<Person> future = template.insert(person);
assertThat(getUninterruptibly(future)).isEqualTo(person);
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString())
.isEqualTo("INSERT INTO person (firstname,id,lastname) VALUES ('Walter','heisenberg','White');");
}
/**
* @see DATACASS-292
*/
@Test
public void insertShouldTranslateException() throws Exception {
reset(session);
when(session.executeAsync(any(Statement.class)))
.thenReturn(TestResultSetFuture.failed(new NoHostAvailableException(Collections.emptyMap())));
ListenableFuture<Person> future = template.insert(new Person("heisenberg", "Walter", "White"));
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class)
.hasRootCauseInstanceOf(NoHostAvailableException.class);
}
}
/**
* @see DATACASS-292
*/
@Test
public void insertShouldNotApplyInsert() {
when(resultSet.wasApplied()).thenReturn(false);
Person person = new Person("heisenberg", "Walter", "White");
ListenableFuture<Person> future = template.insert(person);
assertThat(getUninterruptibly(future)).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void updateShouldUpdateEntity() {
when(resultSet.wasApplied()).thenReturn(true);
Person person = new Person("heisenberg", "Walter", "White");
ListenableFuture<Person> future = template.update(person);
assertThat(getUninterruptibly(future)).isEqualTo(person);
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString())
.isEqualTo("UPDATE person SET firstname='Walter',lastname='White' WHERE id='heisenberg';");
}
/**
* @see DATACASS-292
*/
@Test
public void updateShouldTranslateException() throws Exception {
reset(session);
when(session.executeAsync(any(Statement.class)))
.thenReturn(TestResultSetFuture.failed(new NoHostAvailableException(Collections.emptyMap())));
ListenableFuture<Person> future = template.update(new Person("heisenberg", "Walter", "White"));
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class)
.hasRootCauseInstanceOf(NoHostAvailableException.class);
}
}
/**
* @see DATACASS-292
*/
@Test
public void updateShouldNotApplyUpdate() {
when(resultSet.wasApplied()).thenReturn(false);
Person person = new Person("heisenberg", "Walter", "White");
ListenableFuture<Person> future = template.update(person);
assertThat(getUninterruptibly(future)).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void deleteByIdShouldRemoveEntity() {
when(resultSet.wasApplied()).thenReturn(true);
Person person = new Person("heisenberg", "Walter", "White");
ListenableFuture<Boolean> future = template.deleteById(person.getId(), Person.class);
assertThat(getUninterruptibly(future)).isTrue();
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("DELETE FROM person WHERE id='heisenberg';");
}
/**
* @see DATACASS-292
*/
@Test
public void deleteShouldRemoveEntity() {
when(resultSet.wasApplied()).thenReturn(true);
Person person = new Person("heisenberg", "Walter", "White");
ListenableFuture<Person> future = template.delete(person);
assertThat(getUninterruptibly(future)).isEqualTo(person);
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("DELETE FROM person WHERE id='heisenberg';");
}
/**
* @see DATACASS-292
*/
@Test
public void deleteShouldTranslateException() throws Exception {
reset(session);
when(session.executeAsync(any(Statement.class)))
.thenReturn(TestResultSetFuture.failed(new NoHostAvailableException(Collections.emptyMap())));
ListenableFuture<Person> future = template.delete(new Person("heisenberg", "Walter", "White"));
try {
future.get();
fail("Missing CassandraConnectionFailureException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(CassandraConnectionFailureException.class)
.hasRootCauseInstanceOf(NoHostAvailableException.class);
}
}
/**
* @see DATACASS-292
*/
@Test
public void deleteShouldNotApplyRemoval() {
when(resultSet.wasApplied()).thenReturn(false);
Person person = new Person("heisenberg", "Walter", "White");
ListenableFuture<Person> future = template.delete(person);
assertThat(getUninterruptibly(future)).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void truncateShouldRemoveEntities() {
template.truncate(Person.class);
verify(session).executeAsync(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("TRUNCATE person;");
}
private static <T> T getUninterruptibly(Future<T> future) {
try {
return future.get();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private static class TestResultSetFuture extends AbstractFuture<ResultSet> implements ResultSetFuture {
public TestResultSetFuture() {}
public TestResultSetFuture(ResultSet resultSet) {
set(resultSet);
}
@Override
public boolean set(ResultSet value) {
return super.set(value);
}
@Override
public ResultSet getUninterruptibly() {
return null;
}
@Override
public ResultSet getUninterruptibly(long l, TimeUnit timeUnit) throws TimeoutException {
return null;
}
@Override
protected boolean setException(Throwable throwable) {
return super.setException(throwable);
}
/**
* Create a completed future that reports a failure given {@link Throwable}.
*
* @param throwable must not be {@literal null}.
* @return the completed/failed {@link TestResultSetFuture}.
*/
public static TestResultSetFuture failed(Throwable throwable) {
TestResultSetFuture future = new TestResultSetFuture();
future.setException(throwable);
return future;
}
}
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.test.integration.core;
package org.springframework.data.cassandra.core;
import static org.assertj.core.api.Assertions.*;
@@ -22,6 +22,7 @@ import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.cassandra.core.cql.generator.DropTableCqlGenerator;
import org.springframework.cassandra.core.keyspace.DropTableSpecification;
import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
@@ -49,7 +50,8 @@ public class CassandraAdminTemplateIntegrationTests extends AbstractKeyspaceCrea
KeyspaceMetadata keyspace = getKeyspaceMetadata();
Collection<TableMetadata> tables = keyspace.getTables();
for (TableMetadata table : tables) {
cassandraAdminTemplate.execute(DropTableSpecification.dropTable(table.getName()));
cassandraAdminTemplate.getCqlOperations()
.execute(DropTableCqlGenerator.toCql(DropTableSpecification.dropTable(table.getName())));
}
}

View File

@@ -65,7 +65,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea
CassandraBatchOperations batchOperations = new CassandraBatchTemplate(template);
batchOperations.insert(walter).insert(mike).execute();
Group loaded = template.selectOneById(Group.class, walter.getId());
Group loaded = template.selectOneById(walter.getId(), Group.class);
assertThat(loaded.getId().getUsername()).isEqualTo(walter.getId().getUsername());
}
@@ -82,7 +82,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea
CassandraBatchOperations batchOperations = new CassandraBatchTemplate(template);
batchOperations.insert(Arrays.asList(walter, mike)).execute();
Group loaded = template.selectOneById(Group.class, walter.getId());
Group loaded = template.selectOneById(walter.getId(), Group.class);
assertThat(loaded.getId().getUsername()).isEqualTo(walter.getId().getUsername());
}
@@ -102,7 +102,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea
CassandraBatchOperations batchOperations = new CassandraBatchTemplate(template);
batchOperations.update(walter).update(mike).execute();
Group loaded = template.selectOneById(Group.class, walter.getId());
Group loaded = template.selectOneById(walter.getId(), Group.class);
assertThat(loaded.getEmail()).isEqualTo(walter.getEmail());
}
@@ -122,7 +122,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea
CassandraBatchOperations batchOperations = new CassandraBatchTemplate(template);
batchOperations.update(Arrays.asList(walter, mike)).execute();
Group loaded = template.selectOneById(Group.class, walter.getId());
Group loaded = template.selectOneById(walter.getId(), Group.class);
assertThat(loaded.getEmail()).isEqualTo(walter.getEmail());
}
@@ -142,7 +142,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea
CassandraBatchOperations batchOperations = new CassandraBatchTemplate(template);
batchOperations.update(Arrays.asList(walter, mike)).execute();
FlatGroup loaded = template.selectOneById(FlatGroup.class, walter);
FlatGroup loaded = template.selectOneById(walter, FlatGroup.class);
assertThat(loaded.getEmail()).isEqualTo(walter.getEmail());
}
@@ -160,7 +160,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea
batchOperations.delete(walter).delete(mike).execute();
Group loaded = template.selectOneById(Group.class, walter.getId());
Group loaded = template.selectOneById(walter.getId(), Group.class);
assertThat(loaded).isNull();
}
@@ -178,7 +178,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea
batchOperations.delete(Arrays.asList(walter, mike)).execute();
Group loaded = template.selectOneById(Group.class, walter.getId());
Group loaded = template.selectOneById(walter.getId(), Group.class);
assertThat(loaded).isNull();
}
@@ -200,7 +200,7 @@ public class CassandraBatchTemplateIntegrationTests extends AbstractKeyspaceCrea
CassandraBatchOperations batchOperations = new CassandraBatchTemplate(template);
batchOperations.insert(walter).insert(mike).withTimestamp(timestamp).execute();
ResultSet resultSet = template.query("SELECT writetime(email) FROM group;");
ResultSet resultSet = template.getCqlOperations().queryForResultSet("SELECT writetime(email) FROM group;");
assertThat(resultSet.getAvailableWithoutFetching()).isEqualTo(2);

View File

@@ -26,6 +26,7 @@ import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cassandra.core.CqlOperations;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.UserDefinedType;
@@ -44,7 +45,8 @@ import lombok.Data;
@RunWith(MockitoJUnitRunner.class)
public class CassandraPersistentEntitySchemaCreatorUnitTests {
@Mock CassandraAdminOperations operations;
@Mock CassandraAdminOperations adminOperations;
@Mock CqlOperations operations;
@Mock KeyspaceMetadata metadata;
@Mock UserType universetype;
@Mock UserType moontype;
@@ -63,6 +65,8 @@ public class CassandraPersistentEntitySchemaCreatorUnitTests {
return metadata.getUserType(typeName.toCql());
}
});
when(adminOperations.getCqlOperations()).thenReturn(operations);
}
@Test
@@ -76,7 +80,7 @@ public class CassandraPersistentEntitySchemaCreatorUnitTests {
when(metadata.getUserType("moontype")).thenReturn(moontype);
CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator(context,
operations);
adminOperations);
schemaCreator.createUserTypes(false, false, false);

View File

@@ -0,0 +1,242 @@
/*
* Copyright 2016 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 java.util.Arrays;
import java.util.Collections;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cassandra.core.CqlTemplate;
import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.domain.Person;
import org.springframework.data.cassandra.domain.UserToken;
import org.springframework.data.cassandra.repository.support.BasicMapId;
import org.springframework.data.cassandra.test.integration.simpletons.BookReference;
import org.springframework.data.cassandra.test.integration.support.SchemaTestUtils;
import com.datastax.driver.core.utils.UUIDs;
/**
* Integration tests for {@link CassandraTemplate}.
*
* @author Mark Paluch
*/
public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest {
private CassandraTemplate template;
@Before
public void setUp() {
MappingCassandraConverter converter = new MappingCassandraConverter();
converter.afterPropertiesSet();
template = new CassandraTemplate(new CqlTemplate(session), converter);
SchemaTestUtils.potentiallyCreateTableFor(Person.class, template);
SchemaTestUtils.potentiallyCreateTableFor(UserToken.class, template);
SchemaTestUtils.potentiallyCreateTableFor(BookReference.class, template);
SchemaTestUtils.truncate(Person.class, template);
SchemaTestUtils.truncate(UserToken.class, template);
SchemaTestUtils.truncate(BookReference.class, template);
}
/**
* @see DATACASS-292
*/
@Test
public void insertShouldInsertEntity() {
Person person = new Person("heisenberg", "Walter", "White");
assertThat(template.selectOneById(person.getId(), Person.class)).isNull();
Person inserted = template.insert(person);
assertThat(inserted).isNotNull().isEqualTo(person);
assertThat(template.selectOneById(person.getId(), Person.class)).isEqualTo(person);
}
/**
* @see DATACASS-292
*/
@Test
public void shouldInsertAndCountEntities() {
Person person = new Person("heisenberg", "Walter", "White");
template.insert(person);
long count = template.count(Person.class);
assertThat(count).isEqualTo(1L);
}
/**
* @see DATACASS-292
*/
@Test
public void updateShouldUpdateEntity() {
Person person = new Person("heisenberg", "Walter", "White");
template.insert(person);
person.setFirstname("Walter Hartwell");
Person updated = template.update(person);
assertThat(updated).isNotNull();
assertThat(template.selectOneById(person.getId(), Person.class)).isEqualTo(person);
}
/**
* @see DATACASS-292
*/
@Test
public void deleteShouldRemoveEntity() {
Person person = new Person("heisenberg", "Walter", "White");
template.insert(person);
Person deleted = template.delete(person);
assertThat(deleted).isNotNull();
assertThat(template.selectOneById(person.getId(), Person.class)).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void deleteByIdShouldRemoveEntity() {
Person person = new Person("heisenberg", "Walter", "White");
template.insert(person);
Boolean deleted = template.deleteById(person.getId(), Person.class);
assertThat(deleted).isTrue();
assertThat(template.selectOneById(person.getId(), Person.class)).isNull();
}
/**
* @see DATACASS-182
*/
@Test
public void stream() {
Person person = new Person("heisenberg", "Walter", "White");
template.insert(person);
Stream<Person> stream = template.stream("SELECT * FROM person", Person.class);
assertThat(stream.collect(Collectors.toList())).hasSize(1).contains(person);
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-182">DATACASS-182</a>
*/
@Test
public void updateShouldRemoveFields() {
Person person = new Person("heisenberg", "Walter", "White");
template.insert(person);
person.setFirstname(null);
template.update(person);
Person loaded = template.selectOneById(person.getId(), Person.class);
assertThat(loaded.getFirstname()).isNull();
assertThat(loaded.getId()).isEqualTo("heisenberg");
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-182">DATACASS-182</a>
*/
@Test
public void insertShouldRemoveFields() {
Person person = new Person("heisenberg", "Walter", "White");
template.insert(person);
person.setFirstname(null);
template.insert(person);
Person loaded = template.selectOneById(person.getId(), Person.class);
assertThat(loaded.getFirstname()).isNull();
assertThat(loaded.getId()).isEqualTo("heisenberg");
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-182">DATACASS-182</a>
*/
@Test
public void insertAndUpdateToEmptyCollection() {
BookReference bookReference = new BookReference();
bookReference.setIsbn("isbn");
bookReference.setBookmarks(Arrays.asList(1, 2, 3, 4));
template.insert(bookReference);
bookReference.setBookmarks(Collections.<Integer> emptyList());
template.update(bookReference);
BookReference loaded = template.selectOneById(bookReference.getIsbn(), BookReference.class);
assertThat(loaded.getTitle()).isNull();
assertThat(loaded.getBookmarks()).isNull();
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-206">DATACASS-206</a>
*/
@Test
public void shouldUseSpecifiedColumnNamesForSingleEntityModifyingOperations() {
UserToken userToken = new UserToken();
userToken.setToken(UUIDs.startOf(System.currentTimeMillis()));
userToken.setUserId(UUIDs.endOf(System.currentTimeMillis()));
template.insert(userToken);
userToken.setUserComment("comment");
template.update(userToken);
UserToken loaded = template.selectOneById(
BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken()), UserToken.class);
assertThat(loaded).isNotNull();
assertThat(loaded.getUserComment()).isEqualTo("comment");
template.delete(userToken);
UserToken loadAfterDelete = template.selectOneById(
BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken()), UserToken.class);
assertThat(loadAfterDelete).isNull();
}
}

View File

@@ -1,175 +1,409 @@
/*
* Copyright 2013-2016 the original author or authors
* Copyright 2016 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
* 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
* 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.
* 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 static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.anyInt;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.datastax.driver.core.querybuilder.Batch;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.cassandra.convert.CassandraConverter;
import org.springframework.data.cassandra.test.integration.simpletons.Book;
import org.springframework.cassandra.support.exception.CassandraConnectionFailureException;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.domain.Person;
import com.datastax.driver.core.ColumnDefinitions;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.querybuilder.Batch;
import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.exceptions.NoHostAvailableException;
import org.springframework.data.cassandra.test.integration.simpletons.Book;
/**
* Test suite of test cases testing the contract and functionality of the {@link CassandraTemplate} class.
*
* @author John Blum
* @see org.springframework.data.cassandra.core.CassandraTemplate
* @since 1.5.0
* Unit tests for {@link CassandraTemplate}.
*
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class CassandraTemplateUnitTests {
@Mock Session session;
@Mock ResultSet resultSet;
@Mock Row row;
@Mock ColumnDefinitions columnDefinitions;
@Captor ArgumentCaptor<Statement> statementCaptor;
private CassandraTemplate template;
@Mock private Session mockSession;
@Before
public void setup() {
template = new CassandraTemplate(mockSession);
}
public void setUp() {
protected <T> Iterator<T> iterator(T... elements) {
return Collections.unmodifiableList(Arrays.asList(elements)).iterator();
}
protected Row mockRow(String name) {
return mock(Row.class, name);
}
protected <T> CassandraConverterRowCallback<T> newRollCallback(CassandraConverter converter, Class<T> type) {
return new CassandraConverterRowCallback<T>(converter, type);
template = new CassandraTemplate(session, new MappingCassandraConverter());
when(session.execute(anyString())).thenReturn(resultSet);
when(session.execute(any(Statement.class))).thenReturn(resultSet);
when(resultSet.getColumnDefinitions()).thenReturn(columnDefinitions);
when(row.getColumnDefinitions()).thenReturn(columnDefinitions);
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-310">DATACASS-310</a>
* @see DATACASS-292
*/
@Test
public void processResultSetHandlesResultSetRows() {
ResultSet mockResultSet = mock(ResultSet.class);
public void selectUsingCqlShouldReturnMappedResults() {
Row mockRowOne = mockRow("MockRowOne");
Row mockRowTwo = mockRow("MockRowTwo");
Row mockRowThree = mockRow("MockRowThree");
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
when(columnDefinitions.contains(anyString())).thenReturn(true);
when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii());
CassandraConverter mockCassandraConverter = mock(CassandraConverter.class);
when(columnDefinitions.getIndexOf("id")).thenReturn(0);
when(columnDefinitions.getIndexOf("firstname")).thenReturn(1);
when(columnDefinitions.getIndexOf("lastname")).thenReturn(2);
when(mockSession.execute(eq("SELECT * FROM Test"))).thenReturn(mockResultSet);
when(mockResultSet.iterator()).thenReturn(iterator(mockRowOne, mockRowTwo, mockRowThree));
when(mockCassandraConverter.read(eq(Integer.class), eq(mockRowOne))).thenReturn(1);
when(mockCassandraConverter.read(eq(Integer.class), eq(mockRowTwo))).thenReturn(2);
when(mockCassandraConverter.read(eq(Integer.class), eq(mockRowThree))).thenReturn(3);
when(row.getObject(0)).thenReturn("myid");
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
List<Integer> results = template.select("SELECT * FROM Test",
newRollCallback(mockCassandraConverter, Integer.class));
List<Person> list = template.select("SELECT * FROM person", Person.class);
assertThat(results).isNotNull().hasSize(3).contains(1, 2, 3);
verify(mockSession, times(1)).execute(eq("SELECT * FROM Test"));
verify(mockResultSet, times(1)).iterator();
verify(mockCassandraConverter, times(1)).read(eq(Integer.class), eq(mockRowOne));
verify(mockCassandraConverter, times(1)).read(eq(Integer.class), eq(mockRowTwo));
verify(mockCassandraConverter, times(1)).read(eq(Integer.class), eq(mockRowThree));
assertThat(list).hasSize(1).contains(new Person("myid", "Walter", "White"));
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person");
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-310">DATACASS-310</a>
* @see DATACASS-292
*/
@Test
public void processResultSetHandlesSingleElementResultSet() {
Select mockSelect = mock(Select.class);
ResultSet mockResultSet = mock(ResultSet.class);
Row mockRow = mock(Row.class);
CassandraConverter mockCassandraConverter = mock(CassandraConverter.class);
public void selectShouldTranslateException() throws Exception {
when(mockSession.execute(eq(mockSelect))).thenReturn(mockResultSet);
when(mockResultSet.iterator()).thenReturn(iterator(mockRow));
when(mockCassandraConverter.read(eq(String.class), eq(mockRow))).thenReturn("test");
when(resultSet.iterator()).thenThrow(new NoHostAvailableException(Collections.emptyMap()));
List<String> results = template.select(mockSelect, newRollCallback(mockCassandraConverter, String.class));
try {
template.select("SELECT * FROM person", Person.class);
assertThat(results).hasSize(1).contains("test");
verify(mockSession, times(1)).execute(eq(mockSelect));
verify(mockResultSet, times(1)).iterator();
verify(mockCassandraConverter, times(1)).read(eq(String.class), eq(mockRow));
fail("Missing CassandraConnectionFailureException");
} catch (CassandraConnectionFailureException e) {
assertThat(e).hasRootCauseInstanceOf(NoHostAvailableException.class);
}
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-310">DATACASS-310</a>
* @see DATACASS-292
*/
@Test
public void processResultSetHandlesEmptyResultSet() {
CassandraConverter mockCassandraConverter = mock(CassandraConverter.class);
ResultSet mockResultSet = mock(ResultSet.class);
public void selectOneShouldReturnMappedResults() {
when(mockSession.execute(eq("SELECT * FROM Test"))).thenReturn(mockResultSet);
when(mockResultSet.iterator()).thenReturn(this.<Row> iterator());
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
when(columnDefinitions.contains(anyString())).thenReturn(true);
when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii());
List<Object> results = template.select("SELECT * FROM Test", newRollCallback(mockCassandraConverter, Object.class));
when(columnDefinitions.getIndexOf("id")).thenReturn(0);
when(columnDefinitions.getIndexOf("firstname")).thenReturn(1);
when(columnDefinitions.getIndexOf("lastname")).thenReturn(2);
assertThat(results).isNotNull();
assertThat(results.isEmpty()).isTrue();
when(row.getObject(0)).thenReturn("myid");
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
verify(mockSession, times(1)).execute(eq("SELECT * FROM Test"));
verify(mockResultSet, times(1)).iterator();
verifyZeroInteractions(mockCassandraConverter);
Person person = template.selectOne("SELECT * FROM person WHERE id='myid';", Person.class);
assertThat(person).isEqualTo(new Person("myid", "Walter", "White"));
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';");
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-310">DATACASS-310</a>
* @see DATACASS-292
*/
@Test
public void processResultSetHandlesNullResultSet() {
CassandraConverter mockCassandraConverter = mock(CassandraConverter.class);
public void selectOneByIdShouldReturnMappedResults() {
when(mockSession.execute(anyString())).thenReturn(null);
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
when(columnDefinitions.contains(anyString())).thenReturn(true);
when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii());
List<Object> results = template.select("SELECT * FROM Test", newRollCallback(mockCassandraConverter, Object.class));
when(columnDefinitions.getIndexOf("id")).thenReturn(0);
when(columnDefinitions.getIndexOf("firstname")).thenReturn(1);
when(columnDefinitions.getIndexOf("lastname")).thenReturn(2);
assertThat(results).isNotNull();
assertThat(results.isEmpty()).isTrue();
when(row.getObject(0)).thenReturn("myid");
when(row.getObject(1)).thenReturn("Walter");
when(row.getObject(2)).thenReturn("White");
verify(mockSession, times(1)).execute(eq("SELECT * FROM Test"));
verifyZeroInteractions(mockCassandraConverter);
Person person = template.selectOneById("myid", Person.class);
assertThat(person).isEqualTo(new Person("myid", "Walter", "White"));
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';");
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-288">DATACASS-288</a>
* @see DATACASS-292
*/
@Test
public void existsShouldReturnExistingElement() {
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
when(columnDefinitions.contains(anyString())).thenReturn(true);
when(columnDefinitions.getType(anyInt())).thenReturn(DataType.ascii());
boolean exists = template.exists("myid", Person.class);
assertThat(exists).isTrue();
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';");
}
/**
* @see DATACASS-292
*/
@Test
public void existsShouldReturnNonExistingElement() {
when(resultSet.iterator()).thenReturn(Collections.emptyIterator());
boolean exists = template.exists("myid", Person.class);
assertThat(exists).isFalse();
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT * FROM person WHERE id='myid';");
}
/**
* @see DATACASS-292
*/
@Test
public void countShouldExecuteCountQueryElement() {
when(resultSet.iterator()).thenReturn(Collections.singleton(row).iterator());
when(row.getLong(0)).thenReturn(42L);
when(columnDefinitions.size()).thenReturn(1);
long count = template.count(Person.class);
assertThat(count).isEqualTo(42L);
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("SELECT count(*) FROM person;");
}
/**
* @see DATACASS-292
*/
@Test
public void insertShouldInsertEntity() {
when(resultSet.wasApplied()).thenReturn(true);
Person person = new Person("heisenberg", "Walter", "White");
Person inserted = template.insert(person);
assertThat(inserted).isEqualTo(person);
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString())
.isEqualTo("INSERT INTO person (firstname,id,lastname) VALUES ('Walter','heisenberg','White');");
}
/**
* @see DATACASS-292
*/
@Test
public void insertShouldTranslateException() throws Exception {
reset(session);
when(session.execute(any(Statement.class))).thenThrow(new NoHostAvailableException(Collections.emptyMap()));
try {
template.insert(new Person("heisenberg", "Walter", "White"));
fail("Missing CassandraConnectionFailureException");
} catch (CassandraConnectionFailureException e) {
assertThat(e).hasRootCauseInstanceOf(NoHostAvailableException.class);
}
}
/**
* @see DATACASS-292
*/
@Test
public void insertShouldNotApplyInsert() {
when(resultSet.wasApplied()).thenReturn(false);
Person person = new Person("heisenberg", "Walter", "White");
Person inserted = template.insert(person);
assertThat(inserted).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void updateShouldUpdateEntity() {
when(resultSet.wasApplied()).thenReturn(true);
Person person = new Person("heisenberg", "Walter", "White");
Person updated = template.update(person);
assertThat(updated).isEqualTo(person);
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString())
.isEqualTo("UPDATE person SET firstname='Walter',lastname='White' WHERE id='heisenberg';");
}
/**
* @see DATACASS-292
*/
@Test
public void updateShouldTranslateException() throws Exception {
reset(session);
when(session.execute(any(Statement.class))).thenThrow(new NoHostAvailableException(Collections.emptyMap()));
try {
template.update(new Person("heisenberg", "Walter", "White"));
fail("Missing CassandraConnectionFailureException");
} catch (CassandraConnectionFailureException e) {
assertThat(e).hasRootCauseInstanceOf(NoHostAvailableException.class);
}
}
/**
* @see DATACASS-292
*/
@Test
public void updateShouldNotApplyUpdate() {
when(resultSet.wasApplied()).thenReturn(false);
Person person = new Person("heisenberg", "Walter", "White");
Person updated = template.update(person);
assertThat(updated).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void deleteByIdShouldRemoveEntity() {
when(resultSet.wasApplied()).thenReturn(true);
Person person = new Person("heisenberg", "Walter", "White");
boolean deleted = template.deleteById(person.getId(), Person.class);
assertThat(deleted).isTrue();
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("DELETE FROM person WHERE id='heisenberg';");
}
/**
* @see DATACASS-292
*/
@Test
public void deleteShouldRemoveEntity() {
when(resultSet.wasApplied()).thenReturn(true);
Person person = new Person("heisenberg", "Walter", "White");
Person deleted = template.delete(person);
assertThat(deleted).isEqualTo(person);
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("DELETE FROM person WHERE id='heisenberg';");
}
/**
* @see DATACASS-292
*/
@Test
public void deleteShouldTranslateException() throws Exception {
reset(session);
when(session.execute(any(Statement.class))).thenThrow(new NoHostAvailableException(Collections.emptyMap()));
try {
template.delete(new Person("heisenberg", "Walter", "White"));
fail("Missing CassandraConnectionFailureException");
} catch (CassandraConnectionFailureException e) {
assertThat(e).hasRootCauseInstanceOf(NoHostAvailableException.class);
}
}
/**
* @see DATACASS-292
*/
@Test
public void deleteShouldNotApplyRemoval() {
when(resultSet.wasApplied()).thenReturn(false);
Person person = new Person("heisenberg", "Walter", "White");
Person deleted = template.delete(person);
assertThat(deleted).isNull();
}
/**
* @see DATACASS-292
*/
@Test
public void truncateShouldRemoveEntities() {
template.truncate(Person.class);
verify(session).execute(statementCaptor.capture());
assertThat(statementCaptor.getValue().toString()).isEqualTo("TRUNCATE person;");
}
/**
* @see DATACASS-292
*/
@Test
@Ignore
public void batchOperationsShouldCallSession() {
template.batchOps().insert(new Book()).execute();
verify(mockSession).execute(Mockito.any(Batch.class));
verify(session).execute(Mockito.any(Batch.class));
}
}

View File

@@ -35,9 +35,9 @@ import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class CassandraPersistentPropertyComparatorUnitTests {
@Mock private CassandraPersistentProperty left;
@Mock CassandraPersistentProperty left;
@Mock private CassandraPersistentProperty right;
@Mock CassandraPersistentProperty right;
@Test
public void leftAndRightAreNullReturnsZero() {

View File

@@ -92,11 +92,11 @@ abstract class ParameterConversionTestSupport extends AbstractSpringDataEmbedded
deleteAllEntities();
template.execute("CREATE INDEX IF NOT EXISTS contact_address ON contact (address);");
template.execute("CREATE INDEX IF NOT EXISTS contact_addresses ON contact (addresses);");
template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS contact_address ON contact (address);");
template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS contact_addresses ON contact (addresses);");
template.execute("CREATE INDEX IF NOT EXISTS contact_main_phones ON contact (mainphone);");
template.execute("CREATE INDEX IF NOT EXISTS contact_alternative_phones ON contact (alternativephones);");
template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS contact_main_phones ON contact (mainphone);");
template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS contact_alternative_phones ON contact (alternativephones);");
walter = new Contact("Walter");
walter.setAddress(new Address("Albuquerque", "USA"));

View File

@@ -45,9 +45,9 @@ import com.datastax.driver.core.DataType;
@RunWith(MockitoJUnitRunner.class)
public class ConvertingParameterAccessorUnitTests {
@Mock private CassandraParameterAccessor mockParameterAccessor;
@Mock CassandraParameterAccessor mockParameterAccessor;
@Mock private CassandraPersistentProperty mockProperty;
@Mock CassandraPersistentProperty mockProperty;
ConvertingParameterAccessor convertingParameterAccessor;

View File

@@ -31,6 +31,9 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cassandra.core.CqlOperations;
import org.springframework.cassandra.core.ReactiveSessionCallback;
import org.springframework.cassandra.core.SessionCallback;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.CassandraOperations;
@@ -75,6 +78,7 @@ public class StringBasedCassandraQueryUnitTests {
SpelExpressionParser PARSER = new SpelExpressionParser();
@Mock CassandraOperations operations;
@Mock CqlOperations cqlOperations;
@Mock Session session;
@Mock Cluster cluster;
@Mock Configuration configuration;
@@ -92,8 +96,9 @@ public class StringBasedCassandraQueryUnitTests {
mappingContext.setUserTypeResolver(userTypeResolver);
when(operations.getConverter()).thenReturn(converter);
when(operations.getSession()).thenReturn(session);
when(operations.getConverter()).thenReturn(converter);
when(operations.getCqlOperations()).thenReturn(cqlOperations);
when(cqlOperations.execute(any(SessionCallback.class)))
.thenAnswer(invocation -> ((SessionCallback) invocation.getArguments()[0]).doInSession(session));
when(session.getCluster()).thenReturn(cluster);
when(cluster.getConfiguration()).thenReturn(configuration);
when(configuration.getCodecRegistry()).thenReturn(CodecRegistry.DEFAULT_INSTANCE);

View File

@@ -42,13 +42,13 @@ import org.springframework.data.repository.Repository;
@SuppressWarnings({ "rawtypes", "unchecked" })
public class CassandraRepositoryFactoryUnitTests {
@Mock private CassandraConverter converter;
@Mock CassandraConverter converter;
@Mock private CassandraMappingContext mappingContext;
@Mock CassandraMappingContext mappingContext;
@Mock private CassandraPersistentEntity entity;
@Mock CassandraPersistentEntity entity;
@Mock private CassandraTemplate template;
@Mock CassandraTemplate template;
@Before
public void setUp() {

View File

@@ -1,288 +0,0 @@
/*
* Copyright 2013-2016 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.test.integration.core;
import static org.assertj.core.api.Assertions.*;
import static org.junit.Assume.*;
import static org.springframework.data.cassandra.repository.support.BasicMapId.*;
import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.CancellationException;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cassandra.core.Cancellable;
import org.springframework.cassandra.core.ConsistencyLevel;
import org.springframework.cassandra.core.PrimaryKeyType;
import org.springframework.cassandra.core.RetryPolicy;
import org.springframework.cassandra.core.WriteOptions;
import org.springframework.cassandra.support.exception.CassandraConnectionFailureException;
import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest;
import org.springframework.cassandra.test.integration.support.ObjectListener;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.data.cassandra.core.DeletionListener;
import org.springframework.data.cassandra.core.WriteListener;
import org.springframework.data.cassandra.mapping.Column;
import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.mapping.Table;
import org.springframework.data.cassandra.test.integration.support.SchemaTestUtils;
import org.springframework.data.cassandra.test.integration.support.TestListener;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Integration tests for asynchronous {@link CassandraTemplate} operations.
*
* @author Matthew T. Adams
* @author Mark Paluch
*/
public class AsynchronousCassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest {
CassandraOperations operations;
@Before
public void before() {
operations = new CassandraTemplate(session);
SchemaTestUtils.potentiallyCreateTableFor(Person.class, operations);
SchemaTestUtils.truncate(Person.class, operations);
}
@Test
public void insertAsynchronously() throws Exception {
insertAsynchronously(ConsistencyLevel.ONE);
}
@Test(expected = CassandraConnectionFailureException.class)
public void insertAsynchronouslyThrows() throws Exception {
insertAsynchronously(ConsistencyLevel.TWO);
}
public void insertAsynchronously(ConsistencyLevel cl) throws Exception {
Person person = Person.random();
PersonListener listener = new PersonListener();
operations.insertAsynchronously(person, listener, new WriteOptions(cl, RetryPolicy.LOGGING));
listener.await();
if (listener.exception != null) {
throw listener.exception;
}
assertThat(listener.entities.iterator().next()).isEqualTo(person);
}
@Test(expected = CancellationException.class)
public void insertAsynchronouslyCancelled() throws Exception {
insertOrUpdateAsynchronouslyCancelled(true);
}
@Test(expected = CancellationException.class)
public void updateAsynchronouslyCancelled() throws Exception {
insertOrUpdateAsynchronouslyCancelled(false);
}
public void insertOrUpdateAsynchronouslyCancelled(boolean insert) throws Exception {
Person person = Person.random();
PersonListener listener = new PersonListener();
Cancellable cancellable;
if (insert) {
cancellable = operations.insertAsynchronously(person, listener, null);
} else {
cancellable = operations.updateAsynchronously(person, listener, null);
}
cancellable.cancel();
listener.await();
// if listener.success is true then the
// async operations was faster than it could be cancelled so we cannot
// verify that a CancellationException was thrown.
assumeFalse(listener.success);
if (listener.exception != null) {
throw listener.exception;
}
fail("should've thrown CancellationException");
}
@Test
public void updateAsynchronously() throws Exception {
updateAsynchronously(ConsistencyLevel.ONE);
}
@Test(expected = CassandraConnectionFailureException.class)
public void updateAsynchronouslyThrows() throws Exception {
updateAsynchronously(ConsistencyLevel.TWO);
}
public void updateAsynchronously(ConsistencyLevel cl) throws Exception {
Person person = Person.random();
person.setFirstname("Homer");
operations.insert(person);
PersonListener listener = new PersonListener();
operations.updateAsynchronously(person, listener, new WriteOptions(cl, RetryPolicy.LOGGING));
listener.await();
if (listener.exception != null) {
throw listener.exception;
}
assertThat(listener.entities.iterator().next()).isEqualTo(person);
}
@Test
public void deleteAsynchronously() throws Exception {
deleteAsynchronously(ConsistencyLevel.ONE);
}
@Test(expected = CassandraConnectionFailureException.class)
public void deleteAsynchronouslyThrows() throws Exception {
deleteAsynchronously(ConsistencyLevel.TWO);
}
public void deleteAsynchronously(ConsistencyLevel cl) throws Exception {
Person person = Person.random();
operations.insert(person);
PersonListener listener = new PersonListener();
operations.deleteAsynchronously(person, listener, new WriteOptions(cl, RetryPolicy.LOGGING));
listener.await();
if (listener.exception != null) {
throw listener.exception;
}
assertThat(operations.exists(Person.class, id("id", person.id))).isFalse();
}
@Test(expected = CancellationException.class)
public void deleteAsynchronouslyCancelled() throws Exception {
Person person = Person.random();
PersonListener listener = new PersonListener();
operations.deleteAsynchronously(person, listener, null).cancel();
listener.await();
// if listener.success is true then the
// async operations was faster than it could be cancelled so we cannot
// verify that a CancellationException was thrown.
assumeFalse(listener.success);
if (listener.exception != null) {
throw listener.exception;
}
fail("should've thrown CancellationException");
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-287">DATACASS-287</a>
*/
@Test(timeout = 10000)
public void shouldSelectOneAsynchronously() throws Exception {
Person person = Person.random();
operations.insert(person);
ObjectListener<Person> objectListener = ObjectListener.create();
String cql = String.format("SELECT * from person where id = '%s'", person.id);
operations.selectOneAsynchronously(cql, Person.class, objectListener);
objectListener.await();
assertThat(objectListener.getResult()).isNotNull();
assertThat(objectListener.getResult().id).isEqualTo(person.id);
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-287">DATACASS-287</a>
*/
@Test(timeout = 10000)
public void shouldSelectOneAsynchronouslyIfObjectIsAbsent() throws Exception {
ObjectListener<Person> objectListener = ObjectListener.create();
String cql = String.format("SELECT * from person where id = '%s'", "unknown");
operations.selectOneAsynchronously(cql, Person.class, objectListener);
objectListener.await();
assertThat(objectListener.getResult()).isNull();
}
@Table
@Data
@AllArgsConstructor
@NoArgsConstructor
@SuppressWarnings("unused")
static class Person {
@PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) String id;
@Column String firstname;
public static String uuid() {
return UUID.randomUUID().toString();
}
public static Person random() {
return new Person(uuid(), null);
}
}
public static class PersonListener extends TestListener implements WriteListener<Person>, DeletionListener<Person> {
public volatile Exception exception;
public volatile Collection<Person> entities;
public volatile boolean success;
@Override
public void onWriteComplete(Collection<Person> entities) {
this.entities = entities;
this.success = true;
countDown();
}
@Override
public void onDeletionComplete(Collection<Person> entities) {
this.entities = entities;
this.success = true;
countDown();
}
@Override
public void onException(Exception x) {
this.exception = x;
this.success = false;
countDown();
}
}
}

View File

@@ -1,815 +0,0 @@
/*
* Copyright 2013-2016 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.test.integration.core;
import static org.assertj.core.api.Assertions.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cassandra.core.ConsistencyLevel;
import org.springframework.cassandra.core.QueryOptions;
import org.springframework.cassandra.core.RetryPolicy;
import org.springframework.cassandra.core.WriteOptions;
import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.data.cassandra.domain.UserToken;
import org.springframework.data.cassandra.repository.support.BasicMapId;
import org.springframework.data.cassandra.test.integration.simpletons.Book;
import org.springframework.data.cassandra.test.integration.simpletons.BookCondition;
import org.springframework.data.cassandra.test.integration.simpletons.BookReference;
import org.springframework.data.cassandra.test.integration.support.SchemaTestUtils;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.core.utils.UUIDs;
/**
* Integration tests for {@link CassandraTemplate}.
*
* @author David Webb
* @author Mark Paluch
* @author John Blum
*/
public class CassandraOperationsIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest {
CassandraTemplate template;
@Before
public void before() {
template = new CassandraTemplate(session);
SchemaTestUtils.potentiallyCreateTableFor(Book.class, template);
SchemaTestUtils.potentiallyCreateTableFor(BookReference.class, template);
SchemaTestUtils.potentiallyCreateTableFor(UserToken.class, template);
SchemaTestUtils.truncate(Book.class, template);
SchemaTestUtils.truncate(BookReference.class, template);
SchemaTestUtils.truncate(UserToken.class, template);
}
@Test
public void insertTest() {
Book b1 = new Book();
b1.setIsbn("123456-1");
b1.setTitle("Spring Data Cassandra Guide");
b1.setAuthor("Cassandra Guru");
b1.setPages(521);
b1.setSaleDate(new Date());
b1.setInStock(true);
b1.setCondition(BookCondition.NEW);
template.insert(b1);
Book b2 = new Book();
b2.setIsbn("123456-2");
b2.setTitle("Spring Data Cassandra Guide");
b2.setAuthor("Cassandra Guru");
b2.setPages(521);
b2.setCondition(BookCondition.NEW);
template.insert(b2);
Book b3 = new Book();
b3.setIsbn("123456-3");
b3.setTitle("Spring Data Cassandra Guide");
b3.setAuthor("Cassandra Guru");
b3.setPages(265);
b3.setCondition(BookCondition.USED);
WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60);
template.insert(b3, options);
Book b5 = new Book();
b5.setIsbn("123456-5");
b5.setTitle("Spring Data Cassandra Guide");
b5.setAuthor("Cassandra Guru");
b5.setPages(265);
b5.setCondition(BookCondition.USED);
template.insert(b5, options);
}
@Test
@SuppressWarnings("deprecation")
public void insertAsynchronouslyTest() {
Book b1 = new Book();
b1.setIsbn("123456-1");
b1.setTitle("Spring Data Cassandra Guide");
b1.setAuthor("Cassandra Guru");
b1.setPages(521);
b1.setCondition(BookCondition.NEW);
template.insertAsynchronously(b1);
Book b2 = new Book();
b2.setIsbn("123456-2");
b2.setTitle("Spring Data Cassandra Guide");
b2.setAuthor("Cassandra Guru");
b2.setPages(521);
b2.setCondition(BookCondition.NEW);
template.insertAsynchronously(b2);
/*
* Test Single Insert with entity
*/
Book b3 = new Book();
b3.setIsbn("123456-3");
b3.setTitle("Spring Data Cassandra Guide");
b3.setAuthor("Cassandra Guru");
b3.setPages(265);
b3.setCondition(BookCondition.USED);
WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60);
template.insertAsynchronously(b3, options);
/*
* Test Single Insert with entity
*/
Book b4 = new Book();
b4.setIsbn("123456-4");
b4.setTitle("Spring Data Cassandra Guide");
b4.setAuthor("Cassandra Guru");
b4.setPages(465);
b4.setCondition(BookCondition.USED);
/*
* Test Single Insert with entity
*/
Book b5 = new Book();
b5.setIsbn("123456-5");
b5.setTitle("Spring Data Cassandra Guide");
b5.setAuthor("Cassandra Guru");
b5.setPages(265);
b5.setCondition(BookCondition.USED);
template.insertAsynchronously(b5, options);
}
@Test
public void insertEmptyList() {
List<Book> list = template.insert(new ArrayList<Book>());
assertThat(list.isEmpty()).isTrue();
}
@Test
public void insertNullList() {
List<Book> list = template.insert((List<Book>) null);
assertThat(list).isNull();
}
@Test
public void insertBatchTest() {
WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60);
List<Book> books = getBookList(20);
template.insert(books);
books = getBookList(20);
template.insert(books);
books = getBookList(20);
template.insert(books, options);
books = getBookList(20);
template.insert(books, options);
assertThat(template.count(Book.class)).isEqualTo(80l);
}
@Test
@SuppressWarnings("deprecation")
public void insertBatchAsynchronouslyTest() {
WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60);
List<Book> books = getBookList(20);
template.insertAsynchronously(books);
books = getBookList(20);
template.insertAsynchronously(books);
books = getBookList(20);
template.insertAsynchronously(books, options);
books = getBookList(20);
template.insertAsynchronously(books, options);
}
private List<Book> getBookList(long numBooks) {
List<Book> books = new ArrayList<Book>();
Book book;
for (int index = 0; index < numBooks; index++) {
book = new Book();
book.setIsbn(UUID.randomUUID().toString());
book.setTitle("Spring Data Cassandra Guide");
book.setAuthor("Cassandra Guru");
book.setPages(index * 10 + 5);
book.setInStock(true);
book.setSaleDate(new Date());
book.setCondition(BookCondition.NEW);
books.add(book);
}
return books;
}
@Test
public void updateTest() {
insertTest();
WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60);
/*
* Test Single Insert with entity
*/
Book b1 = new Book();
b1.setIsbn("123456-1");
b1.setTitle("Spring Data Cassandra Book");
b1.setAuthor("Cassandra Guru");
b1.setPages(521);
template.update(b1);
Book b2 = new Book();
b2.setIsbn("123456-2");
b2.setTitle("Spring Data Cassandra Book");
b2.setAuthor("Cassandra Guru");
b2.setPages(521);
template.update(b2);
/*
* Test Single Insert with entity
*/
Book b3 = new Book();
b3.setIsbn("123456-3");
b3.setTitle("Spring Data Cassandra Book");
b3.setAuthor("Cassandra Guru");
b3.setPages(265);
template.update(b3, options);
/*
* Test Single Insert with entity
*/
Book b5 = new Book();
b5.setIsbn("123456-5");
b5.setTitle("Spring Data Cassandra Book");
b5.setAuthor("Cassandra Guru");
b5.setPages(265);
template.update(b5, options);
}
@Test
@SuppressWarnings("deprecation")
public void updateAsynchronouslyTest() {
insertTest();
WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60);
/*
* Test Single Insert with entity
*/
Book b1 = new Book();
b1.setIsbn("123456-1");
b1.setTitle("Spring Data Cassandra Book");
b1.setAuthor("Cassandra Guru");
b1.setPages(521);
template.updateAsynchronously(b1);
Book b2 = new Book();
b2.setIsbn("123456-2");
b2.setTitle("Spring Data Cassandra Book");
b2.setAuthor("Cassandra Guru");
b2.setPages(521);
template.updateAsynchronously(b2);
/*
* Test Single Insert with entity
*/
Book b3 = new Book();
b3.setIsbn("123456-3");
b3.setTitle("Spring Data Cassandra Book");
b3.setAuthor("Cassandra Guru");
b3.setPages(265);
template.updateAsynchronously(b3, options);
/*
* Test Single Insert with entity
*/
Book b5 = new Book();
b5.setIsbn("123456-5");
b5.setTitle("Spring Data Cassandra Book");
b5.setAuthor("Cassandra Guru");
b5.setPages(265);
template.updateAsynchronously(b5, options);
}
@Test
public void updateBatchTest() {
WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60);
List<Book> books = getBookList(20);
template.insert(books);
alterBooks(books);
template.update(books);
books = getBookList(20);
template.insert(books);
alterBooks(books);
template.update(books);
books = getBookList(20);
template.insert(books, options);
alterBooks(books);
template.update(books, options);
books = getBookList(20);
template.insert(books, options);
alterBooks(books);
template.update(books, options);
}
@Test
@SuppressWarnings("deprecation")
public void updateBatchAsynchronouslyTest() {
WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60);
List<Book> books = getBookList(20);
template.insert(books);
alterBooks(books);
template.updateAsynchronously(books);
books = getBookList(20);
template.insert(books);
alterBooks(books);
template.updateAsynchronously(books);
books = getBookList(20);
template.insert(books, options);
alterBooks(books);
template.updateAsynchronously(books, options);
books = getBookList(20);
template.insert(books, options);
alterBooks(books);
template.updateAsynchronously(books, options);
}
private void alterBooks(List<Book> books) {
for (Book book : books) {
book.setAuthor("Ernest Hemmingway");
book.setTitle("The Old Man and the Sea");
book.setPages(115);
}
}
@Test
public void deleteTest() {
insertTest();
QueryOptions options = new QueryOptions();
options.setConsistencyLevel(ConsistencyLevel.ONE);
options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY);
// Test Single Insert with entity
Book b1 = new Book();
b1.setIsbn("123456-1");
template.delete(b1);
Book b2 = new Book();
b2.setIsbn("123456-2");
template.delete(b2);
// Test Single Insert with entity
Book b3 = new Book();
b3.setIsbn("123456-3");
template.delete(b3, options);
// Test Single Insert with entity
Book b5 = new Book();
b5.setIsbn("123456-5");
template.delete(b5, options);
}
@Test
public void deleteAsynchronouslyTest() {
insertTest();
QueryOptions options = new QueryOptions();
options.setConsistencyLevel(ConsistencyLevel.ONE);
options.setRetryPolicy(RetryPolicy.DOWNGRADING_CONSISTENCY);
/*
* Test Single Insert with entity
*/
Book b1 = new Book();
b1.setIsbn("123456-1");
template.deleteAsynchronously(b1);
Book b2 = new Book();
b2.setIsbn("123456-2");
template.deleteAsynchronously(b2);
/*
* Test Single Insert with entity
*/
Book b3 = new Book();
b3.setIsbn("123456-3");
template.deleteAsynchronously(b3, options);
/*
* Test Single Insert with entity
*/
Book b5 = new Book();
b5.setIsbn("123456-5");
template.deleteAsynchronously(b5, options);
}
@Test
public void deleteBatchTest() {
WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60);
List<Book> books = getBookList(20);
template.insert(books);
template.delete(books);
books = getBookList(20);
template.insert(books);
template.delete(books);
books = getBookList(20);
template.insert(books, options);
template.delete(books, options);
books = getBookList(20);
template.insert(books, options);
template.delete(books, options);
}
@Test
public void deleteBatchAsynchronouslyTest() {
WriteOptions options = newWriteOptions(ConsistencyLevel.ONE, RetryPolicy.DOWNGRADING_CONSISTENCY, 60);
List<Book> books = getBookList(20);
template.insert(books);
template.deleteAsynchronously(books);
books = getBookList(20);
template.insert(books);
template.deleteAsynchronously(books);
books = getBookList(20);
template.insert(books, options);
template.deleteAsynchronously(books, options);
books = getBookList(20);
template.insert(books, options);
template.deleteAsynchronously(books, options);
}
@Test
public void selectOneTest() {
/*
* Test Single Insert with entity
*/
Book b1 = new Book();
b1.setIsbn("123456-1");
b1.setTitle("Spring Data Cassandra Guide");
b1.setAuthor("Cassandra Guru");
b1.setPages(521);
template.insert(b1);
Select select = QueryBuilder.select().all().from("book");
select.where(QueryBuilder.eq("isbn", "123456-1"));
Book book = template.selectOne(select, Book.class);
assertThat(book.getTitle()).isEqualTo("Spring Data Cassandra Guide");
assertThat(book.getAuthor()).isEqualTo("Cassandra Guru");
}
@Test
public void selectTest() {
List<Book> books = getBookList(20);
template.insert(books);
Select select = QueryBuilder.select().all().from("book");
List<Book> selectedBooks = template.select(select, Book.class);
assertThat(selectedBooks).hasSize(20);
for (Book book : selectedBooks) {
assertThat(book.isInStock()).isTrue();
assertThat(book.getCondition()).isEqualTo(BookCondition.NEW);
}
}
@Test
public void selectCountTest() {
long count = 20;
List<Book> books = getBookList(count);
template.insert(books);
assertThat(template.count(Book.class)).isEqualTo(count);
}
@Test
public void insertAndSelect() {
long count = 20;
List<Book> books = getBookList(count);
template.insert(books);
assertThat(template.count(Book.class)).isEqualTo(count);
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-182">DATACASS-182</a>
*/
@Test
public void updateShouldRemoveFields() {
Book book = new Book();
book.setIsbn("isbn");
book.setTitle("title");
book.setAuthor("author");
template.insert(book);
book.setTitle(null);
template.update(book);
Book loaded = template.selectOneById(Book.class, book.getIsbn());
assertThat(loaded.getTitle()).isNull();
assertThat(loaded.getAuthor()).isEqualTo("author");
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-182">DATACASS-182</a>
*/
@Test
public void insertShouldRemoveFields() {
Book book = new Book();
book.setIsbn("isbn");
book.setTitle("title");
book.setAuthor("author");
template.insert(book);
book.setTitle(null);
template.insert(book);
Book loaded = template.selectOneById(Book.class, book.getIsbn());
assertThat(loaded.getTitle()).isNull();
assertThat(loaded.getAuthor()).isEqualTo("author");
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-182">DATACASS-182</a>
*/
@Test
public void updateShouldInsertEntity() {
Book book = new Book();
book.setIsbn("isbn");
book.setTitle("title");
book.setAuthor("author");
template.update(book);
Book loaded = template.selectOneById(Book.class, book.getIsbn());
assertThat(loaded).isNotNull();
assertThat(loaded.getAuthor()).isEqualTo("author");
assertThat(loaded.getTitle()).isEqualTo("title");
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-182">DATACASS-182</a>
*/
@Test
public void insertAndUpdateToEmptyCollection() {
BookReference bookReference = new BookReference();
bookReference.setIsbn("isbn");
bookReference.setBookmarks(Arrays.asList(1, 2, 3, 4));
template.insert(bookReference);
bookReference.setBookmarks(Collections.<Integer> emptyList());
template.update(bookReference);
BookReference loaded = template.selectOneById(BookReference.class, bookReference.getIsbn());
assertThat(loaded.getTitle()).isNull();
assertThat(loaded.getBookmarks()).isNull();
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-182">DATACASS-182</a>
*/
@Test
public void stream() throws InterruptedException {
while (template.select("SELECT * FROM book", Book.class).size() != 0) {
template.truncate("book");
Thread.sleep(10);
}
template.insert(getBookList(20));
Iterator<Book> iterator = template.stream("SELECT * FROM book", Book.class);
assertThat(iterator).isNotNull();
List<Book> selectedBooks = new ArrayList<Book>();
for (Book book : toIterable(iterator)) {
selectedBooks.add(book);
}
assertThat(selectedBooks).hasSize(20);
assertThat(selectedBooks.get(0)).isInstanceOf(Book.class);
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-206">DATACASS-206</a>
*/
@Test
public void shouldUseSpecifiedColumnNamesForSingleEntityModifyingOperations() {
UserToken userToken = new UserToken();
userToken.setToken(UUIDs.startOf(System.currentTimeMillis()));
userToken.setUserId(UUIDs.endOf(System.currentTimeMillis()));
template.insert(userToken);
userToken.setUserComment("comment");
template.update(userToken);
UserToken loaded = template.selectOneById(UserToken.class,
BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken()));
assertThat(loaded).isNotNull();
assertThat(loaded.getUserComment()).isEqualTo("comment");
template.delete(userToken);
UserToken loadAfterDelete = template.selectOneById(UserToken.class,
BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken()));
assertThat(loadAfterDelete).isNull();
}
/**
* @see <a href="https://jira.spring.io/browse/DATACASS-206">DATACASS-206</a>
*/
@Test
public void shouldUseSpecifiedColumnNamesForMultiEntityModifyingOperations() {
UserToken userToken = new UserToken();
userToken.setToken(UUIDs.startOf(System.currentTimeMillis()));
userToken.setUserId(UUIDs.endOf(System.currentTimeMillis()));
template.insert(Collections.singletonList(userToken));
userToken.setUserComment("comment");
template.update(Collections.singletonList(userToken));
UserToken loaded = template.selectOneById(UserToken.class,
BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken()));
assertThat(loaded).isNotNull();
assertThat(loaded.getUserComment()).isEqualTo("comment");
template.delete(Collections.singletonList(userToken));
UserToken loadAfterDelete = template.selectOneById(UserToken.class,
BasicMapId.id("userId", userToken.getUserId()).with("token", userToken.getToken()));
assertThat(loadAfterDelete).isNull();
}
WriteOptions newWriteOptions(ConsistencyLevel consistencyLevel, RetryPolicy retryPolicy, int timeToLive) {
return new WriteOptions(consistencyLevel, retryPolicy, timeToLive);
}
<T> Iterable<T> toIterable(final Iterator<T> iterator) {
return new Iterable<T>() {
@Override
public Iterator<T> iterator() {
return iterator;
}
};
}
}

View File

@@ -79,16 +79,14 @@ public class CompositeKeyCrudIntegrationTests extends AbstractKeyspaceCreatingIn
assertThat(correlationEntities).hasSize(2);
QueryOptions qo = new QueryOptions();
qo.setConsistencyLevel(org.springframework.cassandra.core.ConsistencyLevel.ONE);
ArrayList<CorrelationEntity> entities = new ArrayList<CorrelationEntity>();
entities.add(correlationEntity1);
entities.add(correlationEntity2);
operations.delete(entities, qo);
QueryOptions queryOptions = new QueryOptions();
queryOptions.setConsistencyLevel(org.springframework.cassandra.core.ConsistencyLevel.ONE);
operations.delete(correlationEntity1, queryOptions);
operations.delete(correlationEntity2, queryOptions);
correlationEntities = operations.select(select, CorrelationEntity.class);
assertThat(correlationEntities).isEmpty();
}
}

View File

@@ -31,13 +31,14 @@ public class ForceQuotedCompositePrimaryKeyRepositoryTests {
CassandraTemplate cassandraTemplate;
public void before() {
cassandraTemplate.deleteAll(Implicit.class);
cassandraTemplate.truncate(Implicit.class);
}
public String query(String columnName, String tableName, String keyZeroColumnName, String keyZero,
String keyOneColumnName, String keyOne) {
return cassandraTemplate.queryForObject(String.format("select %s from %s where %s = '%s' and %s = '%s'", columnName,
return cassandraTemplate.getCqlOperations()
.queryForObject(String.format("select %s from %s where %s = '%s' and %s = '%s'", columnName,
tableName, keyZeroColumnName, keyZero, keyOneColumnName, keyOne), String.class);
}

View File

@@ -33,11 +33,11 @@ public class ForceQuotedRepositoryTests {
CassandraOperations cassandraTemplate;
public void before() {
cassandraTemplate.deleteAll(Implicit.class);
cassandraTemplate.truncate(Implicit.class);
}
public String query(String columnName, String tableName, String keyColumnName, String key) {
return cassandraTemplate.queryForObject(
return cassandraTemplate.getCqlOperations().queryForObject(
String.format("select %s from %s where %s = '%s'", columnName, tableName, keyColumnName, key), String.class);
}

View File

@@ -168,7 +168,8 @@ public class CustomConversionTests extends AbstractKeyspaceCreatingIntegrationTe
@Test
public void shouldLoadCustomConvertedObject() {
cassandraOperations.execute(QueryBuilder.insertInto("employee").value("id", "employee-id").value("person",
cassandraOperations.getCqlOperations().execute(QueryBuilder.insertInto("employee").value("id", "employee-id")
.value("person",
"{\"firstname\":\"Homer\",\"lastname\":\"Simpson\"}"));
Employee employee = cassandraOperations.selectOne(QueryBuilder.select("id", "person").from("employee"),
@@ -186,7 +187,8 @@ public class CustomConversionTests extends AbstractKeyspaceCreatingIntegrationTe
@Test
public void shouldLoadCustomConvertedWithCollectionsObject() {
cassandraOperations.execute(QueryBuilder.insertInto("employee").value("id", "employee-id").value("people",
cassandraOperations.getCqlOperations().execute(QueryBuilder.insertInto("employee").value("id", "employee-id")
.value("people",
Collections.singleton("{\"firstname\":\"Apu\",\"lastname\":\"Nahasapeemapetilon\"}")));
Employee employee = cassandraOperations.selectOne(QueryBuilder.select("id", "people").from("employee"),
@@ -205,9 +207,9 @@ public class CustomConversionTests extends AbstractKeyspaceCreatingIntegrationTe
@Test
public void dummy() {
cassandraOperations.execute(QueryBuilder.insertInto("employee").value("id", "employee-id"));
cassandraOperations.getCqlOperations().execute(QueryBuilder.insertInto("employee").value("id", "employee-id"));
cassandraOperations
cassandraOperations.getCqlOperations()
.execute(QueryBuilder.update("employee").where(QueryBuilder.eq("id", "employee-id")).with(QueryBuilder
.set("people", Collections.singleton("{\"firstname\":\"Apu\",\"lastname\":\"Nahasapeemapetilon\"}"))));
}

View File

@@ -63,7 +63,7 @@ public class CassandraTemplateMapIdProxyDelegateIntegrationTests extends Abstrac
// select
SinglePkcId id = id(SinglePkcId.class).key(saved.getKey());
SinglePkc selected = operations.selectOneById(SinglePkc.class, id);
SinglePkc selected = operations.selectOneById(id, SinglePkc.class);
assertThat(saved).isNotSameAs(selected);
assertThat(selected.getKey()).isEqualTo(saved.getKey());
assertThat(selected.getValue()).isEqualTo(saved.getValue());
@@ -73,13 +73,13 @@ public class CassandraTemplateMapIdProxyDelegateIntegrationTests extends Abstrac
SinglePkc updated = operations.update(selected);
assertThat(selected).isSameAs(updated);
selected = operations.selectOneById(SinglePkc.class, id);
selected = operations.selectOneById(id, SinglePkc.class);
assertThat(updated).isNotSameAs(selected);
assertThat(selected.getValue()).isEqualTo(updated.getValue());
// delete
operations.delete(selected);
assertThat(operations.selectOneById(SinglePkc.class, id)).isNull();
assertThat(operations.selectOneById(id, SinglePkc.class)).isNull();
}
public interface SinglePkcId {
@@ -127,7 +127,7 @@ public class CassandraTemplateMapIdProxyDelegateIntegrationTests extends Abstrac
// select
MultiPkcId id = id(MultiPkcId.class).key0(saved.getKey0()).key1(saved.getKey1());
MultiPkc selected = operations.selectOneById(MultiPkc.class, id);
MultiPkc selected = operations.selectOneById(id, MultiPkc.class);
assertThat(saved).isNotSameAs(selected);
assertThat(selected.getKey0()).isEqualTo(saved.getKey0());
assertThat(selected.getKey1()).isEqualTo(saved.getKey1());
@@ -138,13 +138,13 @@ public class CassandraTemplateMapIdProxyDelegateIntegrationTests extends Abstrac
MultiPkc updated = operations.update(selected);
assertThat(selected).isSameAs(updated);
selected = operations.selectOneById(MultiPkc.class, id);
selected = operations.selectOneById(id, MultiPkc.class);
assertThat(updated).isNotSameAs(selected);
assertThat(selected.getValue()).isEqualTo(updated.getValue());
// delete
operations.delete(selected);
assertThat(operations.selectOneById(MultiPkc.class, id)).isNull();
assertThat(operations.selectOneById(id, MultiPkc.class)).isNull();
}
public interface MultiPkcId {

View File

@@ -63,7 +63,7 @@ public class CassandraTemplateMapIdIntegrationTest extends AbstractKeyspaceCreat
// select
MapId id = id("key", saved.getKey());
SinglePkc selected = operations.selectOneById(SinglePkc.class, id);
SinglePkc selected = operations.selectOneById(id, SinglePkc.class);
assertThat(saved).isNotSameAs(selected);
assertThat(selected.getKey()).isEqualTo(saved.getKey());
assertThat(selected.getValue()).isEqualTo(saved.getValue());
@@ -73,13 +73,13 @@ public class CassandraTemplateMapIdIntegrationTest extends AbstractKeyspaceCreat
SinglePkc updated = operations.update(selected);
assertThat(selected).isSameAs(updated);
selected = operations.selectOneById(SinglePkc.class, id);
selected = operations.selectOneById(id, SinglePkc.class);
assertThat(updated).isNotSameAs(selected);
assertThat(selected.getValue()).isEqualTo(updated.getValue());
// delete
operations.delete(selected);
assertThat(operations.selectOneById(SinglePkc.class, id)).isNull();
assertThat(operations.selectOneById(id, SinglePkc.class)).isNull();
}
@Table
@@ -121,7 +121,7 @@ public class CassandraTemplateMapIdIntegrationTest extends AbstractKeyspaceCreat
// select
MapId id = id("key0", saved.getKey0()).with("key1", saved.getKey1());
MultiPkc selected = operations.selectOneById(MultiPkc.class, id);
MultiPkc selected = operations.selectOneById(id, MultiPkc.class);
assertThat(saved).isNotSameAs(selected);
assertThat(selected.getKey0()).isEqualTo(saved.getKey0());
assertThat(selected.getKey1()).isEqualTo(saved.getKey1());
@@ -132,13 +132,13 @@ public class CassandraTemplateMapIdIntegrationTest extends AbstractKeyspaceCreat
MultiPkc updated = operations.update(selected);
assertThat(selected).isSameAs(updated);
selected = operations.selectOneById(MultiPkc.class, id);
selected = operations.selectOneById(id, MultiPkc.class);
assertThat(updated).isNotSameAs(selected);
assertThat(selected.getValue()).isEqualTo(updated.getValue());
// delete
operations.delete(selected);
assertThat(operations.selectOneById(MultiPkc.class, id)).isNull();
assertThat(operations.selectOneById(id, MultiPkc.class)).isNull();
}
@Table

View File

@@ -28,10 +28,12 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.UUID;
import com.datastax.driver.core.SimpleStatement;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.cassandra.test.integration.AbstractKeyspaceCreatingIntegrationTest;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.data.cassandra.domain.AllPossibleTypes;
@@ -74,7 +76,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setInet(InetAddress.getByName("127.0.0.1"));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getInet()).isEqualTo(entity.getInet());
}
@@ -89,7 +91,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setUuid(UUID.randomUUID());
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getUuid()).isEqualTo(entity.getUuid());
}
@@ -104,7 +106,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBoxedShort(Short.MAX_VALUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBoxedShort()).isEqualTo(entity.getBoxedShort());
}
@@ -119,7 +121,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setPrimitiveShort(Short.MAX_VALUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getPrimitiveShort()).isEqualTo(entity.getPrimitiveShort());
}
@@ -134,7 +136,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBoxedByte(Byte.MAX_VALUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBoxedByte()).isEqualTo(entity.getBoxedByte());
}
@@ -149,7 +151,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setPrimitiveByte(Byte.MAX_VALUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getPrimitiveByte()).isEqualTo(entity.getPrimitiveByte());
}
@@ -164,7 +166,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBoxedLong(Long.MAX_VALUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBoxedLong()).isEqualTo(entity.getBoxedLong());
}
@@ -179,7 +181,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setPrimitiveLong(Long.MAX_VALUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getPrimitiveLong()).isEqualTo(entity.getPrimitiveLong());
}
@@ -194,7 +196,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBoxedInteger(Integer.MAX_VALUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBoxedInteger()).isEqualTo(entity.getBoxedInteger());
}
@@ -209,7 +211,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setPrimitiveInteger(Integer.MAX_VALUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getPrimitiveInteger()).isEqualTo(entity.getPrimitiveInteger());
}
@@ -224,7 +226,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBoxedFloat(Float.MAX_VALUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBoxedFloat()).isEqualTo(entity.getBoxedFloat());
}
@@ -239,7 +241,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setPrimitiveFloat(Float.MAX_VALUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getPrimitiveFloat()).isEqualTo(entity.getPrimitiveFloat());
}
@@ -254,7 +256,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBoxedDouble(Double.MAX_VALUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBoxedDouble()).isEqualTo(entity.getBoxedDouble());
}
@@ -269,7 +271,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setPrimitiveDouble(Double.MAX_VALUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getPrimitiveDouble()).isEqualTo(entity.getPrimitiveDouble());
}
@@ -284,7 +286,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBoxedBoolean(Boolean.TRUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBoxedBoolean()).isEqualTo(entity.getBoxedBoolean());
}
@@ -299,7 +301,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setPrimitiveBoolean(Boolean.TRUE);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.isPrimitiveBoolean()).isEqualTo(entity.isPrimitiveBoolean());
}
@@ -315,7 +317,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setTimestamp(new Date(1));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getTimestamp()).isEqualTo(entity.getTimestamp());
}
@@ -330,7 +332,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setDate(LocalDate.fromDaysSinceEpoch(1));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getDate()).isEqualTo(entity.getDate());
}
@@ -345,7 +347,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBigInteger(new BigInteger("123456"));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBigInteger()).isEqualTo(entity.getBigInteger());
}
@@ -360,7 +362,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBigDecimal(new BigDecimal("123456.7890123"));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBigDecimal()).isEqualTo(entity.getBigDecimal());
}
@@ -375,7 +377,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBlob(ByteBuffer.wrap("Hello".getBytes()));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
ByteBuffer blob = loaded.getBlob();
byte[] bytes = new byte[blob.remaining()];
@@ -393,7 +395,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setSetOfString(Collections.singleton("hello"));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getSetOfString()).isEqualTo(entity.getSetOfString());
}
@@ -408,7 +410,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setSetOfString(new HashSet<String>());
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getSetOfString()).isNull();
}
@@ -423,7 +425,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setListOfString(Collections.singletonList("hello"));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getListOfString()).isEqualTo(entity.getListOfString());
}
@@ -438,7 +440,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setListOfString(new ArrayList<String>());
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getListOfString()).isNull();
}
@@ -453,7 +455,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setMapOfString(Collections.singletonMap("hello", "world"));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getMapOfString()).isEqualTo(entity.getMapOfString());
}
@@ -468,7 +470,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setMapOfString(new HashMap<String, String>());
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getMapOfString()).isNull();
}
@@ -483,7 +485,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setAnEnum(Condition.MINT);
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getAnEnum()).isEqualTo(entity.getAnEnum());
}
@@ -499,11 +501,10 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
String id = "1";
long time = 21312214L;
PreparedStatement prepare = operations.getSession().prepare("INSERT INTO timeentity (id, time) values(?,?)");
BoundStatement boundStatement = prepare.bind(id, time);
operations.execute(boundStatement);
operations.getCqlOperations()
.execute(new SimpleStatement("INSERT INTO timeentity (id, time) values(?,?)", id, time));
TimeEntity loaded = operations.selectOneById(TimeEntity.class, id);
TimeEntity loaded = operations.selectOneById(id, TimeEntity.class);
assertThat(loaded.getTime()).isEqualTo(time);
}
@@ -518,7 +519,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setLocalDate(java.time.LocalDate.of(2010, 7, 4));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getLocalDate()).isEqualTo(entity.getLocalDate());
}
@@ -533,7 +534,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setLocalDateTime(java.time.LocalDateTime.of(2010, 7, 4, 1, 2, 3));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getLocalDateTime()).isEqualTo(entity.getLocalDateTime());
}
@@ -548,7 +549,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setLocalTime(java.time.LocalTime.of(1, 2, 3));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getLocalTime()).isEqualTo(entity.getLocalTime());
}
@@ -563,7 +564,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setInstant(java.time.Instant.now());
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getInstant()).isEqualTo(entity.getInstant());
}
@@ -578,7 +579,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setZoneId(java.time.ZoneId.of("Europe/Paris"));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getZoneId()).isEqualTo(entity.getZoneId());
}
@@ -593,7 +594,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setJodaLocalDate(new org.joda.time.LocalDate(2010, 7, 4));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getJodaLocalDate()).isEqualTo(entity.getJodaLocalDate());
}
@@ -608,7 +609,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setJodaDateMidnight(new org.joda.time.DateMidnight(2010, 7, 4));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getJodaDateMidnight()).isEqualTo(entity.getJodaDateMidnight());
}
@@ -623,7 +624,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setJodaDateTime(new org.joda.time.DateTime(2010, 7, 4, 1, 2, 3));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getJodaDateTime()).isEqualTo(entity.getJodaDateTime());
}
@@ -638,7 +639,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBpLocalDate(org.threeten.bp.LocalDate.of(2010, 7, 4));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBpLocalDate()).isEqualTo(entity.getBpLocalDate());
}
@@ -653,7 +654,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBpLocalDateTime(org.threeten.bp.LocalDateTime.of(2010, 7, 4, 1, 2, 3));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBpLocalDateTime()).isEqualTo(entity.getBpLocalDateTime());
}
@@ -668,7 +669,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBpLocalTime(org.threeten.bp.LocalTime.of(1, 2, 3));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBpLocalTime()).isEqualTo(entity.getBpLocalTime());
}
@@ -683,7 +684,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBpInstant(org.threeten.bp.Instant.now());
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBpZoneId()).isEqualTo(entity.getBpZoneId());
}
@@ -698,7 +699,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setBpZoneId(org.threeten.bp.ZoneId.of("Europe/Paris"));
operations.insert(entity);
AllPossibleTypes loaded = operations.selectOneById(AllPossibleTypes.class, entity.getId());
AllPossibleTypes loaded = operations.selectOneById(entity.getId(), AllPossibleTypes.class);
assertThat(loaded.getBpZoneId()).isEqualTo(entity.getBpZoneId());
}
@@ -714,7 +715,7 @@ public class CassandraTypeMappingIntegrationTest extends AbstractKeyspaceCreatin
entity.setCount(1);
operations.update(entity);
CounterEntity loaded = operations.selectOneById(CounterEntity.class, entity.getId());
CounterEntity loaded = operations.selectOneById(entity.getId(), CounterEntity.class);
assertThat(loaded.getCount()).isEqualTo(entity.getCount());
}

View File

@@ -23,6 +23,9 @@ import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.inject.Singleton;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.cassandra.core.cql.generator.CreateKeyspaceCqlGenerator;
import org.springframework.cassandra.core.cql.generator.DropKeyspaceCqlGenerator;
import org.springframework.cassandra.core.keyspace.CreateKeyspaceSpecification;
import org.springframework.cassandra.core.keyspace.DropKeyspaceSpecification;
import org.springframework.cassandra.support.RandomKeySpaceName;
@@ -72,16 +75,16 @@ class CassandraOperationsProducer {
CreateKeyspaceSpecification createKeyspaceSpecification = new CreateKeyspaceSpecification(KEYSPACE_NAME)
.ifNotExists();
cassandraTemplate.execute(createKeyspaceSpecification);
cassandraTemplate.execute("USE " + KEYSPACE_NAME);
cassandraTemplate.getCqlOperations().execute(CreateKeyspaceCqlGenerator.toCql(createKeyspaceSpecification));
cassandraTemplate.getCqlOperations().execute("USE " + KEYSPACE_NAME);
CassandraPersistentEntitySchemaCreator schemaCreator = new CassandraPersistentEntitySchemaCreator(mappingContext, cassandraTemplate);
schemaCreator.createUserTypes(false, false, true);
schemaCreator.createTables(false, false, true);
for (CassandraPersistentEntity<?> entity : cassandraTemplate.getConverter().getMappingContext()
.getNonPrimaryKeyEntities()) {
cassandraTemplate.truncate(entity.getTableName());
.getPersistentEntities()) {
cassandraTemplate.truncate(entity.getType());
}
return cassandraTemplate;
@@ -97,8 +100,8 @@ class CassandraOperationsProducer {
public void close(@Disposes CassandraOperations cassandraOperations) {
cassandraOperations.execute(DropKeyspaceSpecification.dropKeyspace(KEYSPACE_NAME));
cassandraOperations.getSession().close();
cassandraOperations.getCqlOperations()
.execute(DropKeyspaceCqlGenerator.toCql(DropKeyspaceSpecification.dropKeyspace(KEYSPACE_NAME)));
}
public void close(@Disposes Cluster cluster) {

View File

@@ -23,6 +23,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import com.datastax.driver.core.Session;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,9 +71,9 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
}
@Autowired private CassandraOperations template;
@Autowired private PersonRepository personRepository;
@Autowired CassandraOperations template;
@Autowired Session session;
@Autowired PersonRepository personRepository;
private Person walter;
private Person skyler;
@@ -144,7 +145,7 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
@Test
public void shouldFindByMappedUdt() throws InterruptedException {
template.execute("CREATE INDEX IF NOT EXISTS person_main_address ON person (mainaddress);");
template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS person_main_address ON person (mainaddress);");
// Give Cassandra some time to build the index
Thread.sleep(500);
@@ -160,7 +161,7 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
@Test
public void shouldFindByMappedUdtStringQuery() throws InterruptedException {
template.execute("CREATE INDEX IF NOT EXISTS person_main_address ON person (mainaddress);");
template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS person_main_address ON person (mainaddress);");
// Give Cassandra some time to build the index
Thread.sleep(500);
@@ -189,7 +190,8 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
assumeTrue(Version.parse(SpringVersion.getVersion()).isGreaterThanOrEqualTo(Version.parse("4.3")));
template.execute("CREATE INDEX IF NOT EXISTS person_number_of_children ON person (numberofchildren);");
template.getCqlOperations()
.execute("CREATE INDEX IF NOT EXISTS person_number_of_children ON person (numberofchildren);");
// Give Cassandra some time to build the index
Thread.sleep(500);
@@ -205,7 +207,7 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
@Test
public void shouldFindByLocalDate() throws InterruptedException {
template.execute("CREATE INDEX IF NOT EXISTS person_created_date ON person (createddate);");
template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS person_created_date ON person (createddate);");
// Give Cassandra some time to build the index
Thread.sleep(500);
@@ -239,9 +241,9 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
@Test
public void shouldUseStartsWithQuery() throws InterruptedException {
assumeTrue(CassandraVersion.get(template.getSession()).isGreaterThanOrEqualTo(Version.parse("3.4")));
assumeTrue(CassandraVersion.get(session).isGreaterThanOrEqualTo(Version.parse("3.4")));
template.execute(
template.getCqlOperations().execute(
"CREATE CUSTOM INDEX IF NOT EXISTS fn_starts_with ON person (nickname) USING 'org.apache.cassandra.index.sasi.SASIIndex';");
// Give Cassandra some time to build the index
@@ -259,9 +261,9 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
@Test
public void shouldUseContainsQuery() throws InterruptedException {
assumeTrue(CassandraVersion.get(template.getSession()).isGreaterThanOrEqualTo(Version.parse("3.4")));
assumeTrue(CassandraVersion.get(session).isGreaterThanOrEqualTo(Version.parse("3.4")));
template.execute(
template.getCqlOperations().execute(
"CREATE CUSTOM INDEX IF NOT EXISTS fn_contains ON person (nickname) USING 'org.apache.cassandra.index.sasi.SASIIndex'\n"
+ "WITH OPTIONS = { 'mode': 'CONTAINS' };");

View File

@@ -51,7 +51,7 @@ public class UserRepositoryIntegrationTests {
public void setUp() {
template.execute("CREATE INDEX IF NOT EXISTS users_address ON users (address);");
template.getCqlOperations().execute("CREATE INDEX IF NOT EXISTS users_address ON users (address);");
repository.deleteAll();
@@ -89,7 +89,8 @@ public class UserRepositoryIntegrationTests {
scott.setPassword("444");
scott.setPlace("Boston");
all = template.insert(Arrays.asList(tom, bob, alice, scott));
all = Arrays.asList(tom, bob, alice, scott);
template.batchOps().insert(all).execute();
}
public void before() {

View File

@@ -38,13 +38,14 @@ public abstract class AbstractSpringDataEmbeddedCassandraIntegrationTest
* Truncate table for all known {@link org.springframework.data.mapping.PersistentEntity entities}.
*/
public void deleteAllEntities() {
for (CassandraPersistentEntity<?> entity : template.getConverter().getMappingContext().getPersistentEntities()) {
if (entity.getType().isInterface()) {
continue;
}
template.truncate(entity.getTableName());
template.truncate(entity.getType());
}
}
}

View File

@@ -15,15 +15,17 @@
*/
package org.springframework.data.cassandra.test.integration.support;
import org.springframework.cassandra.core.SessionCallback;
import org.springframework.cassandra.core.cql.generator.CreateTableCqlGenerator;
import org.springframework.cassandra.core.keyspace.CreateTableSpecification;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.mapping.CassandraPersistentEntity;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.exceptions.DriverException;
/**
* {@link SchemaTestUtils} is a collection of reflection-based utility methods for use in unit and integration testing
@@ -43,13 +45,19 @@ public class SchemaTestUtils {
CassandraMappingContext mappingContext = operations.getConverter().getMappingContext();
CassandraPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityClass);
Session session = operations.getSession();
KeyspaceMetadata keyspace = session.getCluster().getMetadata().getKeyspace(session.getLoggedKeyspace());
if (keyspace.getTable(persistentEntity.getTableName().toCql()) == null) {
CreateTableSpecification tableSpecification = mappingContext.getCreateTableSpecificationFor(persistentEntity);
operations.execute(new CreateTableCqlGenerator(tableSpecification).toCql());
}
operations.getCqlOperations().execute(new SessionCallback<Object>() {
@Override
public Object doInSession(Session session) throws DriverException, DataAccessException {
KeyspaceMetadata keyspace = session.getCluster().getMetadata().getKeyspace(session.getLoggedKeyspace());
if (keyspace.getTable(persistentEntity.getTableName().toCql()) == null) {
CreateTableSpecification tableSpecification = mappingContext.getCreateTableSpecificationFor(persistentEntity);
operations.getCqlOperations().execute(new CreateTableCqlGenerator(tableSpecification).toCql());
}
return null;
}
});
}
/**
@@ -59,10 +67,6 @@ public class SchemaTestUtils {
* @param operations must not be {@literal null}.
*/
public static void truncate(Class<?> entityClass, CassandraOperations operations) {
CassandraMappingContext mappingContext = operations.getConverter().getMappingContext();
CassandraPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityClass);
operations.execute(QueryBuilder.truncate(persistentEntity.getTableName().toCql()));
operations.truncate(entityClass);
}
}