Merge branch 'DATACASS-32' of https://github.com/shvid/spring-data-cassandra into DATACASS-32

This commit is contained in:
Matthew Adams
2013-11-22 19:12:05 -06:00
13 changed files with 955 additions and 43 deletions

View File

@@ -74,13 +74,14 @@ javadoc {
ext.tmpDir = file("${buildDir}/api-work")
configure(options) {
stylesheetFile = file("${srcDir}/spring-javadoc.css")
overview = "${srcDir}/overview.html"
//stylesheetFile = file("${srcDir}/spring-javadoc.css")
//overview = "${srcDir}/overview.html"
docFilesSubDirs = true
outputLevel = org.gradle.external.javadoc.JavadocOutputLevel.QUIET
breakIterator = true
showFromProtected()
groups = [
'Spring Cassandra' : ['org.springframework.cassandra*'],
'Spring Data Cassandra' : ['org.springframework.data.cassandra*'],
]

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2011-2013 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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.DriverException;
/**
* Created a PreparedStatement and retrieved the PreparedStatement from cache if the statement has been prepared
* previously. In general, this creator should be used over the {@link SimplePreparedStatementCreator} as it provides
* better performance.
*
* <p>
* There is overhead in Cassandra when Preparing a Statement. This is negligible on a single data center configuration,
* but when your cluster spans multiple data centers, preparing the same statement over and over again is not necessary
* and causes performance issues in high throughput use cases.
* </p>
*
* @author David Webb
*
*/
public class CachedPreparedStatementCreator implements PreparedStatementCreator, CqlProvider {
private static Logger log = LoggerFactory.getLogger(CachedPreparedStatementCreator.class);
private final String cql;
private PreparedStatement cache;
/**
* Create a CachedPreparedStatementCreator from the provided CQL.
*
* @param cql
*/
public CachedPreparedStatementCreator(String cql) {
Assert.notNull(cql, "CQL is required to create a PreparedStatement");
this.cql = cql;
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.PreparedStatementCreator#createPreparedStatement(com.datastax.driver.core.Session)
*/
@Override
public PreparedStatement createPreparedStatement(Session session) throws DriverException {
if (cache == null) {
log.debug("PreparedStatement cache is null, preparing new Statement");
cache = session.prepare(getCql());
} else {
log.debug("Using cached PreparedStatement");
}
return cache;
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.CqlProvider#getCql()
*/
@Override
public String getCql() {
return this.cql;
}
}

View File

@@ -22,6 +22,7 @@ import java.util.Map;
import org.springframework.dao.DataAccessException;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Session;
/**
* Operations for interacting with Cassandra at the lowest level. This interface provides Exception Translation.
@@ -36,7 +37,7 @@ public interface CassandraOperations {
* SessionCallback can decide whether or not to <code>execute()</code> or <code>executeAsync()</code> the operation.
*
* @param sessionCallback
* @return
* @return Type<T> defined in the SessionCallback
*/
<T> T execute(SessionCallback<T> sessionCallback) throws DataAccessException;
@@ -48,19 +49,19 @@ public interface CassandraOperations {
void execute(final String cql) throws DataAccessException;
/**
* Executes the supplied CQL Query Asynchrously and returns nothing.
* Executes the supplied CQL Query Asynchronously and returns nothing.
*
* @param cql
* @param cql The CQL Statement to execute
*/
void executeAsynchronously(final String cql) throws DataAccessException;
/**
* Executes the provided CQL Query, and extracts the results with the ResultSetExtractor
* Executes the provided CQL Query, and extracts the results with the ResultSetExtractor.
*
* @param cql The Query
* @param rse The implementation for extracting the results
* @param rse The implementation for extracting the ResultSet
*
* @return
* @return Type <T> specified in the ResultSetExtractor
* @throws DataAccessException
*/
<T> T query(final String cql, ResultSetExtractor<T> rse) throws DataAccessException;
@@ -69,72 +70,332 @@ public interface CassandraOperations {
* Executes the provided CQL Query asynchronously, and extracts the results with the ResultSetFutureExtractor
*
* @param cql The Query
* @param rse The implementation for extracting the results
* @param rse The implementation for extracting the future results
* @return
* @throws DataAccessException
*/
<T> T queryAsynchronously(final String cql, ResultSetFutureExtractor<T> rse) throws DataAccessException;
/**
* Executes the provided CQL Query, and then processes the results with the <code>RowCallbackHandler</code>.
*
* @param cql The Query
* @param rch The implementation for processing the rows returned.
* @throws DataAccessException
*/
void query(final String cql, RowCallbackHandler rch) throws DataAccessException;
/**
* Processes the ResultSet through the RowCallbackHandler and return nothing. This is used internal to the Template
* for core operations, but is made available through Operations in the event you have a ResultSet to process. The
* ResultsSet could come from a ResultSetFuture after an asynchronous query.
*
* @param resultSet Results to process
* @param rch RowCallbackHandler with the processing implementation
* @throws DataAccessException
*/
void process(ResultSet resultSet, RowCallbackHandler rch) throws DataAccessException;
/**
* Executes the provided CQL Query, and maps all Rows returned with the supplied RowMapper.
*
* @param cql The Query
* @param rowMapper The implementation for mapping all rows
* @return List of <T> processed by the RowMapper
* @throws DataAccessException
*/
<T> List<T> query(final String cql, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Processes the ResultSet through the RowMapper and returns the List of mapped Rows. This is used internal to the
* Template for core operations, but is made available through Operations in the event you have a ResultSet to
* process. The ResultsSet could come from a ResultSetFuture after an asynchronous query.
*
* @param resultSet Results to process
* @param rowMapper RowMapper with the processing implementation
* @return List of <T> generated by the RowMapper
* @throws DataAccessException
*/
<T> List<T> process(ResultSet resultSet, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Executes the provided CQL Query, and maps <b>ONE</b> Row returned with the supplied RowMapper.
*
* <p>
* This expects only ONE row to be returned. More than one Row will cause an Exception to be thrown.
* </p>
*
* @param cql The Query
* @param rowMapper The implementation for convert the Row to <T>
* @return Object<T>
* @throws DataAccessException
*/
<T> T queryForObject(final String cql, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Process a ResultSet through a RowMapper. This is used internal to the Template for core operations, but is made
* available through Operations in the event you have a ResultSet to process. The ResultsSet could come from a
* ResultSetFuture after an asynchronous query.
*
* @param resultSet
* @param rowMapper
* @return
* @throws DataAccessException
*/
<T> T processOne(ResultSet resultSet, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Executes the provided query and tries to return the first column of the first Row as a Class<T>.
*
* @param cql The Query
* @param requiredType Valid Class that Cassandra Data Types can be converted to.
* @return The Object<T> - item [0,0] in the result table of the query.
* @throws DataAccessException
*/
<T> T queryForObject(final String cql, Class<T> requiredType) throws DataAccessException;
/**
* Process a ResultSet, trying to convert the first columns of the first Row to Class<T>. This is used internal to the
* Template for core operations, but is made available through Operations in the event you have a ResultSet to
* process. The ResultsSet could come from a ResultSetFuture after an asynchronous query.
*
* @param resultSet
* @param requiredType
* @return
* @throws DataAccessException
*/
<T> T processOne(ResultSet resultSet, Class<T> requiredType) throws DataAccessException;
/**
* Executes the provided CQL Query and maps <b>ONE</b> Row to a basic Map of Strings and Objects. If more than one Row
* is returned from the Query, an exception will be thrown.
*
* @param cql The Query
* @return Map representing the results of the Query
* @throws DataAccessException
*/
Map<String, Object> queryForMap(final String cql) throws DataAccessException;
/**
* Process a ResultSet with <b>ONE</b> Row and convert to a Map. This is used internal to the Template for core
* operations, but is made available through Operations in the event you have a ResultSet to process. The ResultsSet
* could come from a ResultSetFuture after an asynchronous query.
*
* @param resultSet
* @return
* @throws DataAccessException
*/
Map<String, Object> processMap(ResultSet resultSet) throws DataAccessException;
/**
* Executes the provided CQL and returns all values in the first column of the Results as a List of the Type in the
* second argument.
*
* @param cql The Query
* @param elementType Type to cast the data values to
* @return List of elementType
* @throws DataAccessException
*/
<T> List<T> queryForList(final String cql, Class<T> elementType) throws DataAccessException;
/**
* Process a ResultSet and convert the first column of the results to a List. This is used internal to the Template
* for core operations, but is made available through Operations in the event you have a ResultSet to process. The
* ResultsSet could come from a ResultSetFuture after an asynchronous query.
*
* @param resultSet
* @param elementType
* @return
* @throws DataAccessException
*/
<T> List<T> processList(ResultSet resultSet, Class<T> elementType) throws DataAccessException;
/**
* Executes the provided CQL and converts the results to a basic List of Maps. Each element in the List represents a
* Row returned from the Query. Each Row's columns are put into the map as column/value.
*
* @param cql The Query
* @return List of Maps with the query results
* @throws DataAccessException
*/
List<Map<String, Object>> queryForListOfMap(final String cql) throws DataAccessException;
/**
* Process a ResultSet and convert it to a List of Maps with column/value. This is used internal to the Template for
* core operations, but is made available through Operations in the event you have a ResultSet to process. The
* ResultsSet could come from a ResultSetFuture after an asynchronous query.
*
* @param resultSet
* @return
* @throws DataAccessException
*/
List<Map<String, Object>> processListOfMap(ResultSet resultSet) throws DataAccessException;
/**
* Converts the CQL provided into a {@link SimplePreparedStatementCreator}. <b>This can only be used for CQL
* Statements that do not have data binding.</b> The results of the PreparedStatement are processed with
* PreparedStatementCallback implementation provided by the Application Code.
*
* @param cql The CQL Statement to Execute
* @param action What to do with the results of the PreparedStatement
* @return Type<T> as determined by the supplied Callback.
* @throws DataAccessException
*/
<T> T execute(String cql, PreparedStatementCallback<T> action) throws DataAccessException;
/**
* Uses the provided PreparedStatementCreator to prepare a new Session call, then executes the statement and processes
* the statement using the provided Callback. <b>This can only be used for CQL Statements that do not have data
* binding.</b> The results of the PreparedStatement are processed with PreparedStatementCallback implementation
* provided by the Application Code.
*
* @param psc The implementation to create the PreparedStatement
* @param action What to do with the results of the PreparedStatement
* @return Type<T> as determined by the supplied Callback.
* @throws DataAccessException
*/
<T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException;
<T> T query(final String cql, PreparedStatementBinder pss, ResultSetExtractor<T> rse) throws DataAccessException;
/**
* Converts the CQL provided into a {@link SimplePreparedStatementCreator}. Then, the PreparedStatementBinder will
* bind its values to the bind variables in the provided CQL String. The results of the PreparedStatement are
* processed with the ResultSetExtractor implementation provided by the Application Code. The can return any object,
* including a List of Objects to support the ResultSet processing.
*
* @param cql The Query to Prepare
* @param psb The Binding implementation
* @param rse The implementation for extracting the results of the query.
* @return Type<T> generated by the ResultSetExtractor
* @throws DataAccessException
*/
<T> T query(final String cql, PreparedStatementBinder psb, ResultSetExtractor<T> rse) throws DataAccessException;
void query(final String cql, PreparedStatementBinder pss, RowCallbackHandler rch) throws DataAccessException;
/**
* Converts the CQL provided into a {@link SimplePreparedStatementCreator}. Then, the PreparedStatementBinder will
* bind its values to the bind variables in the provided CQL String. The results of the PreparedStatement are
* processed with the RowCallbackHandler implementation provided and nothing is returned.
*
* @param cql The Query to Prepare
* @param psb The Binding implementation
* @param rch The RowCallbackHandler for processing the ResultSet
* @throws DataAccessException
*/
void query(final String cql, PreparedStatementBinder psb, RowCallbackHandler rch) throws DataAccessException;
<T> List<T> query(final String cql, PreparedStatementBinder pss, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Converts the CQL provided into a {@link SimplePreparedStatementCreator}. Then, the PreparedStatementBinder will
* bind its values to the bind variables in the provided CQL String. The results of the PreparedStatement are
* processed with the RowMapper implementation provided and a List is returned with elements of Type <T> for each Row
* returned.
*
* @param cql The Query to Prepare
* @param psb The Binding implementation
* @param rowMapper The implementation for Mapping a Row to Type <T>
* @return List of <T> for each Row returned from the Query.
* @throws DataAccessException
*/
<T> List<T> query(final String cql, PreparedStatementBinder psb, RowMapper<T> rowMapper) throws DataAccessException;
/**
* Uses the provided PreparedStatementCreator to prepare a new Session call. <b>This can only be used for CQL
* Statements that do not have data binding.</b> The results of the PreparedStatement are processed with
* ResultSetExtractor implementation provided by the Application Code.
*
* @param psc The implementation to create the PreparedStatement
* @param rse Implementation for extracting from the ResultSet
* @return Type <T> which is the output of the ResultSetExtractor
* @throws DataAccessException
*/
<T> T query(PreparedStatementCreator psc, ResultSetExtractor<T> rse) throws DataAccessException;
/**
* Uses the provided PreparedStatementCreator to prepare a new Session call. <b>This can only be used for CQL
* Statements that do not have data binding.</b> The results of the PreparedStatement are processed with
* RowCallbackHandler and nothing is returned.
*
* @param psc The implementation to create the PreparedStatement
* @param rch The implementation to process Results
* @throws DataAccessException
*/
void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException;
/**
* Uses the provided PreparedStatementCreator to prepare a new Session call. <b>This can only be used for CQL
* Statements that do not have data binding.</b> The results of the PreparedStatement are processed with RowMapper
* implementation provided and a List is returned with elements of Type <T> for each Row returned.
*
* @param psc The implementation to create the PreparedStatement
* @param rowMapper The implementation for mapping each Row returned.
* @return List of Type <T> mapped from each Row in the Results
* @throws DataAccessException
*/
<T> List<T> query(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException;
<T> T query(PreparedStatementCreator psc, final PreparedStatementBinder pss, final ResultSetExtractor<T> rse)
throws DataAccessException;
void query(PreparedStatementCreator psc, final PreparedStatementBinder pss, final RowCallbackHandler rch)
throws DataAccessException;
<T> List<T> query(PreparedStatementCreator psc, final PreparedStatementBinder pss, final RowMapper<T> rowMapper)
/**
* Uses the provided PreparedStatementCreator to prepare a new Session call. Binds the values from the
* PreparedStatementBinder to the available bind variables. The results of the PreparedStatement are processed with
* ResultSetExtractor implementation provided by the Application Code.
*
* @param psc The implementation to create the PreparedStatement
* @param psb The implementation to bind variables to values
* @param rse Implementation for extracting from the ResultSet
* @return Type <T> which is the output of the ResultSetExtractor
* @throws DataAccessException
*/
<T> T query(PreparedStatementCreator psc, final PreparedStatementBinder psb, final ResultSetExtractor<T> rse)
throws DataAccessException;
/**
* Describe the current Ring
* Uses the provided PreparedStatementCreator to prepare a new Session call. Binds the values from the
* PreparedStatementBinder to the available bind variables. The results of the PreparedStatement are processed with
* RowCallbackHandler and nothing is returned.
*
* @param psc The implementation to create the PreparedStatement
* @param psb The implementation to bind variables to values
* @param rch The implementation to process Results
* @return Type <T> which is the output of the ResultSetExtractor
* @throws DataAccessException
*/
void query(PreparedStatementCreator psc, final PreparedStatementBinder psb, final RowCallbackHandler rch)
throws DataAccessException;
/**
* Uses the provided PreparedStatementCreator to prepare a new Session call. Binds the values from the
* PreparedStatementBinder to the available bind variables. The results of the PreparedStatement are processed with
* RowMapper implementation provided and a List is returned with elements of Type <T> for each Row returned.
*
* @param psc The implementation to create the PreparedStatement
* @param psb The implementation to bind variables to values
* @param rowMapper The implementation for mapping each Row returned.
* @return Type <T> which is the output of the ResultSetExtractor
* @throws DataAccessException
*/
<T> List<T> query(PreparedStatementCreator psc, final PreparedStatementBinder psb, final RowMapper<T> rowMapper)
throws DataAccessException;
/**
* Describe the current Ring. This uses the provided {@link RingMemberHostMapper} to provide the basics of the
* Cassandra Ring topology.
*
* @return The list of ring tokens that are active in the cluster
*/
List<RingMember> describeRing() throws DataAccessException;
/**
* Describe the current Ring. Application code must provide its own {@link HostMapper} implementation to process the
* lists of hosts returned by the Cassandra Cluster Metadata.
*
* @param hostMapper The implementation to use for host mapping.
* @return Collection generated by the provided HostMapper.
* @throws DataAccessException
*/
<T> Collection<T> describeRing(HostMapper<T> hostMapper) throws DataAccessException;
/**
* Get the current Session used for operations in the implementing class.
*
* @return The DataStax Driver Session Object
*/
Session getSession();
}

View File

@@ -40,8 +40,15 @@ import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.DriverException;
/**
* The CassandraTemplate is a Spring convenience wrapper for low level and explicit operations on the Cassandra
* Database. For working with POJOs, use the {@link CassandraDataTemplate}
* <b>This is the Central class in the Cassandra core package.</b> It simplifies the use of Cassandra and helps to avoid
* common errors. It executes the core Cassandra workflow, leaving application code to provide CQL and result
* extraction. This class execute CQL Queries, provides different ways to extract/map results, and provides Exception
* translation to the generic, more informative exception hierarchy defined in the <code>org.springframework.dao</code>
* package.
*
* <p>
* For working with POJOs, use the {@link CassandraDataTemplate}.
* </p>
*
* @author David Webb
* @author Matthew Adams
@@ -458,7 +465,7 @@ public class CassandraTemplate extends CassandraAccessor implements CassandraOpe
/* (non-Javadoc)
* @see org.springframework.cassandra.core.CassandraOperations#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementSetter, org.springframework.cassandra.core.ResultSetExtractor)
*/
public <T> T query(PreparedStatementCreator psc, final PreparedStatementBinder pss, final ResultSetExtractor<T> rse)
public <T> T query(PreparedStatementCreator psc, final PreparedStatementBinder psb, final ResultSetExtractor<T> rse)
throws DataAccessException {
Assert.notNull(rse, "ResultSetExtractor must not be null");
@@ -468,8 +475,8 @@ public class CassandraTemplate extends CassandraAccessor implements CassandraOpe
public T doInPreparedStatement(PreparedStatement ps) throws DriverException {
ResultSet rs = null;
BoundStatement bs = null;
if (pss != null) {
bs = pss.bindValues(ps);
if (psb != null) {
bs = psb.bindValues(ps);
} else {
bs = ps.bind();
}
@@ -483,31 +490,31 @@ public class CassandraTemplate extends CassandraAccessor implements CassandraOpe
* @see org.springframework.cassandra.core.CassandraOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementSetter, org.springframework.cassandra.core.ResultSetExtractor)
*/
@Override
public <T> T query(String cql, PreparedStatementBinder pss, ResultSetExtractor<T> rse) throws DataAccessException {
return query(new SimplePreparedStatementCreator(cql), pss, rse);
public <T> T query(String cql, PreparedStatementBinder psb, ResultSetExtractor<T> rse) throws DataAccessException {
return query(new SimplePreparedStatementCreator(cql), psb, rse);
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.CassandraOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementSetter, org.springframework.cassandra.core.RowCallbackHandler)
*/
@Override
public void query(String cql, PreparedStatementBinder pss, RowCallbackHandler rch) throws DataAccessException {
query(new SimplePreparedStatementCreator(cql), pss, rch);
public void query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch) throws DataAccessException {
query(new SimplePreparedStatementCreator(cql), psb, rch);
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.CassandraOperations#query(java.lang.String, org.springframework.cassandra.core.PreparedStatementSetter, org.springframework.cassandra.core.RowMapper)
*/
@Override
public <T> List<T> query(String cql, PreparedStatementBinder pss, RowMapper<T> rowMapper) throws DataAccessException {
return query(new SimplePreparedStatementCreator(cql), pss, rowMapper);
public <T> List<T> query(String cql, PreparedStatementBinder psb, RowMapper<T> rowMapper) throws DataAccessException {
return query(new SimplePreparedStatementCreator(cql), psb, rowMapper);
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.CassandraOperations#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowCallbackHandler)
*/
@Override
public void query(PreparedStatementCreator psc, final PreparedStatementBinder pss, final RowCallbackHandler rch)
public void query(PreparedStatementCreator psc, final PreparedStatementBinder psb, final RowCallbackHandler rch)
throws DataAccessException {
Assert.notNull(rch, "RowCallbackHandler must not be null");
logger.debug("Executing prepared CQL query");
@@ -516,8 +523,8 @@ public class CassandraTemplate extends CassandraAccessor implements CassandraOpe
public Object doInPreparedStatement(PreparedStatement ps) throws DriverException {
ResultSet rs = null;
BoundStatement bs = null;
if (pss != null) {
bs = pss.bindValues(ps);
if (psb != null) {
bs = psb.bindValues(ps);
} else {
bs = ps.bind();
}
@@ -532,7 +539,7 @@ public class CassandraTemplate extends CassandraAccessor implements CassandraOpe
* @see org.springframework.cassandra.core.CassandraOperations#query(org.springframework.cassandra.core.PreparedStatementCreator, org.springframework.cassandra.core.PreparedStatementBinder, org.springframework.cassandra.core.RowMapper)
*/
@Override
public <T> List<T> query(PreparedStatementCreator psc, final PreparedStatementBinder pss, final RowMapper<T> rowMapper)
public <T> List<T> query(PreparedStatementCreator psc, final PreparedStatementBinder psb, final RowMapper<T> rowMapper)
throws DataAccessException {
Assert.notNull(rowMapper, "RowMapper must not be null");
logger.debug("Executing prepared CQL query");
@@ -541,8 +548,8 @@ public class CassandraTemplate extends CassandraAccessor implements CassandraOpe
public List<T> doInPreparedStatement(PreparedStatement ps) throws DriverException {
ResultSet rs = null;
BoundStatement bs = null;
if (pss != null) {
bs = pss.bindValues(ps);
if (psb != null) {
bs = psb.bindValues(ps);
} else {
bs = ps.bind();
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2011-2013 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.LinkedList;
import java.util.List;
import org.springframework.util.Assert;
import com.datastax.driver.core.DataType;
/**
* @author David Webb
*
*/
public class CqlParameter {
/** The name of the parameter, if any */
private String name;
/** SQL type constant from {@link DataType} */
private final DataType type;
/** The scale to apply in case of a NUMERIC or DECIMAL type, if any */
private Integer scale;
/**
* Create a new anonymous CqlParameter, supplying the SQL type.
*
* @param type Cassandra Data Type of the parameter according to {@link DataType}
*/
public CqlParameter(DataType type) {
this.type = type;
}
/**
* Create a new anonymous CqlParameter, supplying the SQL type.
*
* @param type Cassandra Data Type of the parameter according to {@link DataType}
* @param scale the number of digits after the decimal point
*/
public CqlParameter(DataType type, int scale) {
this.type = type;
this.scale = scale;
}
/**
* Create a new CqlParameter, supplying name and SQL type.
*
* @param name name of the parameter, as used in input and output maps
* @param type Cassandra Data Type of the parameter according to {@link DataType}
*/
public CqlParameter(String name, DataType type) {
this.name = name;
this.type = type;
}
/**
* Create a new CqlParameter, supplying name and SQL type.
*
* @param name name of the parameter, as used in input and output maps
* @param type Cassandra Data Type of the parameter according to {@link DataType}
* @param scale the number of digits after the decimal point (for DECIMAL and NUMERIC types)
*/
public CqlParameter(String name, DataType type, int scale) {
this.name = name;
this.type = type;
this.scale = scale;
}
/**
* Copy constructor.
*
* @param otherParam the CqlParameter object to copy from
*/
public CqlParameter(CqlParameter otherParam) {
Assert.notNull(otherParam, "CqlParameter object must not be null");
this.name = otherParam.name;
this.type = otherParam.type;
this.scale = otherParam.scale;
}
/**
* Return the name of the parameter.
*/
public String getName() {
return this.name;
}
/**
* Return the SQL type of the parameter.
*/
public DataType getType() {
return this.type;
}
/**
* Return the scale of the parameter, if any.
*/
public Integer getScale() {
return this.scale;
}
/**
* Return whether this parameter holds input values that should be set before execution even if they are {@code null}.
* <p>
* This implementation always returns {@code true}.
*/
public boolean isInputValueProvided() {
return true;
}
/**
* Convert a list of JDBC types, as defined in {@code java.sql.Types}, to a List of CqlParameter objects as used in
* this package.
*/
public static List<CqlParameter> sqlTypesToAnonymousParameterList(DataType[] types) {
List<CqlParameter> result = new LinkedList<CqlParameter>();
if (types != null) {
for (DataType type : types) {
result.add(new CqlParameter(type));
}
}
return result;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2011-2013 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 com.datastax.driver.core.DataType;
/**
* @author David Webb
*
*/
public class CqlParameterValue extends CqlParameter {
private final Object value;
/**
* Create a new CqlParameterValue, supplying the Cassandra DataType.
*
* @param type Cassandra Data Type of the parameter according to {@link DataType}
* @param value the value object
*/
public CqlParameterValue(DataType type, Object value) {
super(type);
this.value = value;
}
/**
* Create a new CqlParameterValue, supplying the Cassandra DataType.
*
* @param type Cassandra Data Type of the parameter according to {@link DataType}
* @param scale the number of digits after the decimal point (for DECIMAL and NUMERIC types)
* @param value the value object
*/
public CqlParameterValue(DataType type, int scale, Object value) {
super(type, scale);
this.value = value;
}
/**
* Create a new CqlParameterValue based on the given CqlParameter declaration.
*
* @param declaredParam the declared CqlParameter to define a value for
* @param value the value object
*/
public CqlParameterValue(CqlParameter declaredParam, Object value) {
super(declaredParam);
this.value = value;
}
/**
* Return the value object that this parameter value holds.
*/
public Object getValue() {
return this.value;
}
}

View File

@@ -0,0 +1,202 @@
/*
* Copyright 2011-2013 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.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.util.Assert;
import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.DriverException;
/**
* @author David Webb
*
*/
public class PreparedStatementCreatorFactory {
/**
* The CQL, which won't change when the parameters change
*/
private final String cql;
/** List of CqlParameter objects. May not be {@code null}. */
private final List<CqlParameter> declaredParameters;
/**
* Create a new factory.
*/
public PreparedStatementCreatorFactory(String cql) {
this.cql = cql;
this.declaredParameters = new LinkedList<CqlParameter>();
}
/**
* Create a new factory with the given CQL and parameters.
*
* @param cql CQL
* @param declaredParameters list of {@link CqlParameter} objects
* @see CqlParameter
*/
public PreparedStatementCreatorFactory(String cql, List<CqlParameter> declaredParameters) {
this.cql = cql;
this.declaredParameters = declaredParameters;
}
/**
* Return a new PreparedStatementBinder for the given parameters.
*
* @param params list of parameters (may be {@code null})
*/
public PreparedStatementBinder newPreparedStatementBinder(List<CqlParameterValue> params) {
return new PreparedStatementCreatorImpl(params != null ? params : Collections.emptyList());
}
/**
* Return a new PreparedStatementBinder for the given parameters.
*
* @param params the parameter array (may be {@code null})
*/
public PreparedStatementBinder newPreparedStatementBinder(Object[] params) {
return new PreparedStatementCreatorImpl(params != null ? Arrays.asList(params) : Collections.emptyList());
}
/**
* Return a new PreparedStatementCreator for the given parameters.
*
* @param params list of parameters (may be {@code null})
*/
public PreparedStatementCreator newPreparedStatementCreator(List<CqlParameterValue> params) {
return new PreparedStatementCreatorImpl(params != null ? params : Collections.emptyList());
}
/**
* Return a new PreparedStatementCreator for the given parameters.
*
* @param params the parameter array (may be {@code null})
*/
public PreparedStatementCreator newPreparedStatementCreator(Object[] params) {
return new PreparedStatementCreatorImpl(params != null ? Arrays.asList(params) : Collections.emptyList());
}
/**
* Return a new PreparedStatementCreator for the given parameters.
*
* @param sqlToUse the actual SQL statement to use (if different from the factory's, for example because of named
* parameter expanding)
* @param params the parameter array (may be {@code null})
*/
public PreparedStatementCreator newPreparedStatementCreator(String sqlToUse, Object[] params) {
return new PreparedStatementCreatorImpl(sqlToUse, params != null ? Arrays.asList(params) : Collections.emptyList());
}
/**
* PreparedStatementCreator implementation returned by this class.
*/
private class PreparedStatementCreatorImpl implements PreparedStatementCreator, PreparedStatementBinder, CqlProvider {
private final String actualCql;
private final List<?> parameters;
public PreparedStatementCreatorImpl(List<?> parameters) {
this(cql, parameters);
}
/**
* @param actualCql
* @param parameters
*/
public PreparedStatementCreatorImpl(String actualCql, List<?> parameters) {
this.actualCql = actualCql;
Assert.notNull(parameters, "Parameters List must not be null");
this.parameters = parameters;
if (this.parameters.size() != declaredParameters.size()) {
Set<String> names = new HashSet<String>();
for (int i = 0; i < parameters.size(); i++) {
Object param = parameters.get(i);
if (param instanceof CqlParameterValue) {
names.add(((CqlParameterValue) param).getName());
} else {
names.add("Parameter #" + i);
}
}
if (names.size() != declaredParameters.size()) {
throw new InvalidDataAccessApiUsageException("CQL [" + cql + "]: given " + names.size()
+ " parameters but expected " + declaredParameters.size());
}
}
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.PreparedStatementCreator#createPreparedStatement(com.datastax.driver.core.Session)
*/
@Override
public PreparedStatement createPreparedStatement(Session session) throws DriverException {
return session.prepare(this.actualCql);
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.PreparedStatementBinder#bindValues(com.datastax.driver.core.PreparedStatement)
*/
@Override
public BoundStatement bindValues(PreparedStatement ps) throws DriverException {
if (this.parameters == null || this.parameters.size() == 0) {
return ps.bind();
}
// Test the type of the first value
Object v = this.parameters.get(0);
Object[] values;
if (v instanceof CqlParameterValue) {
LinkedList<Object> valuesList = new LinkedList<Object>();
for (Object value : this.parameters) {
valuesList.add(((CqlParameterValue) value).getValue());
}
values = valuesList.toArray();
} else {
values = this.parameters.toArray();
}
return ps.bind(values);
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.CqlProvider#getCql()
*/
@Override
public String getCql() {
return cql;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("PreparedStatementCreatorFactory.PreparedStatementCreatorImpl: cql=[");
sb.append(cql).append("]; parameters=").append(this.parameters);
return sb.toString();
}
}
}

View File

@@ -33,7 +33,7 @@ public abstract class AbstractCassandraConverter implements CassandraConverter,
protected EntityInstantiators instantiators = new EntityInstantiators();
/**
* Creates a new {@link AbstractMongoConverter} using the given {@link GenericConversionService}.
* Creates a new {@link AbstractCassandraConverter} using the given {@link GenericConversionService}.
*
* @param conversionService
*/

View File

@@ -77,5 +77,4 @@ public class TestConfig extends AbstractCassandraConfiguration {
return template;
}
}

View File

@@ -90,4 +90,15 @@ public class Book {
this.pages = pages;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("isbn -> " + isbn).append("\n");
sb.append("tile -> " + title).append("\n");
sb.append("author -> " + author).append("\n");
sb.append("pages -> " + pages).append("\n");
return sb.toString();
}
}

View File

@@ -41,15 +41,31 @@ import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.interceptor.DefaultKeyGenerator;
import org.springframework.cassandra.core.CachedPreparedStatementCreator;
import org.springframework.cassandra.core.CassandraOperations;
import org.springframework.cassandra.core.CqlParameter;
import org.springframework.cassandra.core.CqlParameterValue;
import org.springframework.cassandra.core.HostMapper;
import org.springframework.cassandra.core.PreparedStatementBinder;
import org.springframework.cassandra.core.PreparedStatementCreatorFactory;
import org.springframework.cassandra.core.ResultSetExtractor;
import org.springframework.cassandra.core.RingMember;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.test.integration.config.TestConfig;
import org.springframework.data.cassandra.test.integration.table.Book;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.Host;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.exceptions.DriverException;
/**
@@ -84,8 +100,9 @@ public class CassandraOperationsTest {
private final static int CASSANDRA_THRIFT_PORT = 9160;
@Rule
public CassandraCQLUnit cassandraCQLUnit = new CassandraCQLUnit(new ClassPathCQLDataSet("cql-dataload.cql",
KEYSPACE_NAME), CASSANDRA_CONFIG, CASSANDRA_HOST, CASSANDRA_NATIVE_PORT);
public CassandraCQLUnit cassandraCQLUnit = new CassandraCQLUnit(new ClassPathCQLDataSet(
"cassandraOperationsTest-cql-dataload.cql", KEYSPACE_NAME), CASSANDRA_CONFIG, CASSANDRA_HOST,
CASSANDRA_NATIVE_PORT);
@BeforeClass
public static void startCassandra() throws IOException, TTransportException, ConfigurationException,
@@ -146,6 +163,129 @@ public class CassandraOperationsTest {
}
@Test
public void preparedStatementFactoryTest() {
String cql = "select * from book where isbn = ?";
List<CqlParameter> parameters = new LinkedList<CqlParameter>();
parameters.add(new CqlParameter("isbn", DataType.text()));
PreparedStatementCreatorFactory factory = new PreparedStatementCreatorFactory(cql, parameters);
List<CqlParameterValue> values = new LinkedList<CqlParameterValue>();
values.add(new CqlParameterValue(DataType.text(), "999999999"));
Book b = cassandraTemplate.query(factory.newPreparedStatementCreator(values),
factory.newPreparedStatementBinder(values), new ResultSetExtractor<Book>() {
@Override
public Book extractData(ResultSet rs) throws DriverException, DataAccessException {
Row r = rs.one();
Book b = new Book();
b.setIsbn(r.getString("isbn"));
b.setTitle(r.getString("title"));
b.setAuthor(r.getString("author"));
b.setPages(r.getInt("pages"));
return b;
}
});
log.info(b.toString());
}
// @Test
public void cachedPreparedStatementTest() {
log.info(echoString("Hello"));
log.info(echoString("Hello"));
String cql = "select * from book where isbn = ?";
CachedPreparedStatementCreator cpsc = new CachedPreparedStatementCreator(cql);
Book b = cassandraTemplate.query(cpsc, new PreparedStatementBinder() {
@Override
public BoundStatement bindValues(PreparedStatement ps) throws DriverException {
return ps.bind("999999999");
}
}, new ResultSetExtractor<Book>() {
@Override
public Book extractData(ResultSet rs) throws DriverException, DataAccessException {
Row r = rs.one();
Book b = new Book();
b.setIsbn(r.getString("isbn"));
b.setTitle(r.getString("title"));
b.setAuthor(r.getString("author"));
b.setPages(r.getInt("pages"));
return b;
}
});
assertNotNull(b);
log.info(b.toString());
try {
DefaultKeyGenerator generator = new DefaultKeyGenerator();
// TODO Why does method have to be public to work? Options?
Object cacheKey = generator.generate(CachedPreparedStatementCreator.class,
CachedPreparedStatementCreator.class.getMethod("getCachedPreparedStatement", Session.class, String.class),
cassandraTemplate.getSession(), cql);
log.info("cacheKey -> " + cacheKey);
// ConcurrentMapCache cache = (ConcurrentMapCache) cacheManager.getCache("sdc-pstmts");
// ConcurrentMap cacheMap = cache.getNativeCache();
// assertNotNull(cacheMap);
// log.info("CacheMap.size() -> " + cacheMap.size());
// ValueWrapper vw = cache.get(cacheKey);
// PreparedStatement pstmt = (PreparedStatement) vw.get();
// assertNotNull(pstmt);
// log.info(pstmt.getQueryString());
// assertEquals(pstmt.getQueryString(), cql);
} catch (NoSuchMethodException e) {
log.error("Failed to find method", e);
}
CachedPreparedStatementCreator cpsc2 = new CachedPreparedStatementCreator(cql);
Book b2 = cassandraTemplate.query(cpsc2, new PreparedStatementBinder() {
@Override
public BoundStatement bindValues(PreparedStatement ps) throws DriverException {
return ps.bind("999999999");
}
}, new ResultSetExtractor<Book>() {
@Override
public Book extractData(ResultSet rs) throws DriverException, DataAccessException {
Row r = rs.one();
Book b = new Book();
b.setIsbn(r.getString("isbn"));
b.setTitle(r.getString("title"));
b.setAuthor(r.getString("author"));
b.setPages(r.getInt("pages"));
return b;
}
});
assertNotNull(b2);
log.info(b2.toString());
}
@Cacheable("sdc-pstmts")
public String echoString(String s) {
log.info("In EchoString");
return s;
}
@After
public void clearCassandra() {
EmbeddedCassandraServerHelper.cleanEmbeddedCassandra();
@@ -154,6 +294,6 @@ public class CassandraOperationsTest {
@AfterClass
public static void stopCassandra() {
EmbeddedCassandraServerHelper.stopEmbeddedCassandra();
// EmbeddedCassandraServerHelper.stopEmbeddedCassandra();
}
}

View File

@@ -0,0 +1,3 @@
create table book (isbn text, title text, author text, pages int, PRIMARY KEY (isbn));
create table book_alt (isbn text, title text, author text, pages int, PRIMARY KEY (isbn));
insert into book (isbn, title, author, pages) values ('999999999', 'Book of Nines', 'Nine Nine', 999);

View File

@@ -1,2 +1,3 @@
create table book (isbn text, title text, author text, pages int, PRIMARY KEY (isbn));
create table book_alt (isbn text, title text, author text, pages int, PRIMARY KEY (isbn));
create table book_alt (isbn text, title text, author text, pages int, PRIMARY KEY (isbn));
/*insert into book (isbn, title, author, pages) values ('999999999', 'Book of Nines', 'Nine Nine', 999);*/