DONE - issue DATACASS-39: Better PreparedStatement Support

https://jira.springsource.org/browse/DATACASS-39
This commit is contained in:
David Webb
2013-11-25 22:44:02 -05:00
parent 4f6fd4884c
commit 6d020745cf
13 changed files with 405 additions and 849 deletions

View File

@@ -1,126 +0,0 @@
/*
* 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.CollectionUtils;
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;
/**
* <b>This is the primary class in core for binding many values to a Cassandra PreparedStatement.</b>
*
* <p>
* This factory will hold a cached version of the PreparedStatement, and bind many value sets to that statement
* returning a BoundStatement that can be passed to a Session.execute(Query).
* </p>
*
* @author David Webb
*
*/
public class BoundStatementFactory implements PreparedStatementCreator, CqlProvider {
private final String cql;
private PreparedStatement preparedStatement;
private List<List<?>> values = new LinkedList<List<?>>();
public BoundStatementFactory(String cql) {
this.cql = cql;
}
public void addValues(List<CqlParameterValue>... values) {
this.values.add(CollectionUtils.arrayToList(values));
}
public void addValues(Object[]... values) {
for (int i = 0; values != null && i < values.length; i++) {
this.values.add(CollectionUtils.arrayToList(values[i]));
}
}
public void replaceValues(List<CqlParameterValue>... values) {
this.values = CollectionUtils.arrayToList(values);
}
public void replaceValues(Object[]... values) {
noValues();
for (int i = 0; values != null && i < values.length; i++) {
this.values.add(CollectionUtils.arrayToList(values[i]));
}
}
public void noValues() {
this.values = new LinkedList<List<?>>();
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.CqlProvider#getCql()
*/
@Override
public String getCql() {
return this.cql;
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.PreparedStatementCreator#createPreparedStatement(com.datastax.driver.core.Session)
*/
@Override
public PreparedStatement createPreparedStatement(Session session) throws DriverException {
if (preparedStatement == null) {
preparedStatement = session.prepare(this.cql);
}
return preparedStatement;
}
/**
* Bind all values with the single CQL (PreparedStatement) and return BoundStatements read for execution.
*
* @return
* @throws DriverException
*/
public List<BoundStatement> bindValues() throws DriverException {
LinkedList<BoundStatement> boundStatements = new LinkedList<BoundStatement>();
for (List<?> list : this.values) {
// Test the type of the first value
Object v = list.get(0);
Object[] vls;
if (v instanceof CqlParameterValue) {
LinkedList<Object> valuesList = new LinkedList<Object>();
for (Object value : list) {
valuesList.add(((CqlParameterValue) value).getValue());
}
vls = valuesList.toArray();
} else {
vls = list.toArray();
}
boundStatements.add(preparedStatement.bind(vls));
}
return boundStatements;
}
}

View File

@@ -1,80 +0,0 @@
/*
* 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

@@ -48,8 +48,6 @@ public interface CassandraOperations {
*/
void execute(final String cql) throws DataAccessException;
void execute(BoundStatementFactory bsf);
/**
* Executes the supplied CQL Query Asynchronously and returns nothing.
*
@@ -400,4 +398,44 @@ public interface CassandraOperations {
*/
Session getSession();
/**
* This is an operation designed for high performance writes. The cql is used to create a PreparedStatement once, then
* all row values are bound to the single PreparedStatement and executed against the Session.
*
* <p>
* This is used internally by the other ingest() methods, but can be used if you want to write your own RowIterator.
* The Object[] length returned by the next() implementation must match the number of bind variables in the CQL.
* </p>
*
* @param cql The CQL
* @param rowIterator Implementation to provide the Object[] to be bound to the CQL.
*/
void ingest(String cql, RowIterator rowIterator);
/**
* This is an operation designed for high performance writes. The cql is used to create a PreparedStatement once, then
* all row values are bound to the single PreparedStatement and executed against the Session.
*
* <p>
* The List<?> length must match the number of bind variables in the CQL.
* </p>
*
* @param cql The CQL
* @param rows List of List<?> with data to bind to the CQL.
*/
void ingest(String cql, List<List<?>> rows);
/**
* This is an operation designed for high performance writes. The cql is used to create a PreparedStatement once, then
* all row values are bound to the single PreparedStatement and executed against the Session.
*
* <p>
* The Object[] length of the nested array must match the number of bind variables in the CQL.
* </p>
*
* @param cql The CQL
* @param rows Object array of Object array of values to bind to the CQL.
*/
void ingest(String cql, Object[][] rows);
}

View File

@@ -561,18 +561,58 @@ public class CassandraTemplate extends CassandraAccessor implements CassandraOpe
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.CassandraOperations#execute(org.springframework.cassandra.core.BoundStatementFactory)
* @see org.springframework.cassandra.core.CassandraOperations#execute(java.lang.String, org.springframework.cassandra.core.RowProvider, int)
*/
@Override
public void execute(BoundStatementFactory bsf) {
public void ingest(String cql, RowIterator rowIterator) {
bsf.createPreparedStatement(getSession());
PreparedStatement preparedStatement = getSession().prepare(cql);
List<BoundStatement> statements = bsf.bindValues();
for (BoundStatement bs : statements) {
getSession().execute(bs);
while (rowIterator.hasNext()) {
getSession().execute(preparedStatement.bind(rowIterator.next()));
}
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.CassandraOperations#execute(java.lang.String, java.util.List)
*/
@Override
public void ingest(String cql, List<List<?>> rows) {
Assert.notNull(rows);
Assert.notEmpty(rows);
Object[][] values = new Object[rows.size()][];
int i = 0;
for (List<?> row : rows) {
values[i++] = row.toArray();
}
ingest(cql, values);
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.CassandraOperations#execute(java.lang.String, java.lang.Object[][])
*/
@Override
public void ingest(String cql, final Object[][] rows) {
ingest(cql, new RowIterator() {
int index = 0;
@Override
public Object[] next() {
return rows[index++];
}
@Override
public boolean hasNext() {
return index < rows.length;
}
});
}
}

View File

@@ -1,139 +0,0 @@
/*
* 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

@@ -1,68 +0,0 @@
/*
* 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

@@ -1,207 +0,0 @@
/*
* 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
*
* @deprecated - Pattern from JDBC Template, but DW doesn't like it. Use {@link BoundStatementFactory}
*
*/
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;
private PreparedStatement preparedStatement;
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

@@ -0,0 +1,29 @@
/*
* 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;
/**
* @author David Webb
*
*/
public interface RowIterator {
Object[] next();
boolean hasNext();
}

View File

@@ -1,4 +1,4 @@
package org.springframework.cassandra.test.integration.core.cql.generator;
package org.springframework.cassandra.test.integration;
import java.io.IOException;
import java.util.UUID;
@@ -16,10 +16,14 @@ import com.datastax.driver.core.Session;
public abstract class AbstractEmbeddedCassandraIntegrationTest {
protected final static String CASSANDRA_CONFIG = "cassandra.yaml";
protected final static String CASSANDRA_HOST = "localhost";
protected final static int CASSANDRA_NATIVE_PORT = 9042;
@BeforeClass
public static void beforeClass() throws ConfigurationException, TTransportException, IOException,
InterruptedException {
EmbeddedCassandraServerHelper.startEmbeddedCassandra("cassandra.yaml");
EmbeddedCassandraServerHelper.startEmbeddedCassandra(CASSANDRA_CONFIG);
}
/**
@@ -51,7 +55,7 @@ public abstract class AbstractEmbeddedCassandraIntegrationTest {
}
public Cluster cluster() {
return Cluster.builder().addContactPoint("localhost").withPort(9042).build();
return Cluster.builder().addContactPoint(CASSANDRA_HOST).withPort(CASSANDRA_NATIVE_PORT).build();
}
@Before
@@ -69,7 +73,9 @@ public abstract class AbstractEmbeddedCassandraIntegrationTest {
session.execute("CREATE KEYSPACE " + keyspace
+ " WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 1};");
session.execute("USE " + keyspace + ";");
} // else keyspace already exists
} else {// else keyspace already exists
session = cluster.connect(keyspace);
}
}
}
}

View File

@@ -3,6 +3,7 @@ package org.springframework.cassandra.test.integration.core.cql.generator;
import static org.springframework.cassandra.test.integration.core.cql.generator.CqlTableSpecificationAssertions.assertTable;
import org.junit.Test;
import org.springframework.cassandra.test.integration.AbstractEmbeddedCassandraIntegrationTest;
import org.springframework.cassandra.test.unit.core.cql.generator.CreateTableCqlGeneratorTests.BasicTest;
import org.springframework.cassandra.test.unit.core.cql.generator.CreateTableCqlGeneratorTests.CompositePartitionKeyTest;
import org.springframework.cassandra.test.unit.core.cql.generator.CreateTableCqlGeneratorTests.CreateTableTest;

View File

@@ -0,0 +1,275 @@
/*
* 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.test.integration.core.template;
import static org.junit.Assert.assertNotNull;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import junit.framework.Assert;
import org.cassandraunit.CassandraCQLUnit;
import org.cassandraunit.dataset.cql.ClassPathCQLDataSet;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cassandra.core.CassandraOperations;
import org.springframework.cassandra.core.CassandraTemplate;
import org.springframework.cassandra.core.HostMapper;
import org.springframework.cassandra.core.PreparedStatementBinder;
import org.springframework.cassandra.core.ResultSetExtractor;
import org.springframework.cassandra.core.RingMember;
import org.springframework.cassandra.core.RowIterator;
import org.springframework.cassandra.test.integration.AbstractEmbeddedCassandraIntegrationTest;
import org.springframework.dao.DataAccessException;
import org.springframework.data.cassandra.test.integration.table.Book;
import com.datastax.driver.core.BoundStatement;
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.exceptions.DriverException;
/**
* Unit Tests for CassandraTemplate
*
* @author David Webb
*
*/
public class CassandraOperationsTest extends AbstractEmbeddedCassandraIntegrationTest {
private CassandraOperations cassandraTemplate;
private static Logger log = LoggerFactory.getLogger(CassandraOperationsTest.class);
/*
* Objects used for test data
*/
final Object[] o1 = new Object[] { "1234", "Moby Dick", "Herman Manville", new Integer(456) };
final Object[] o2 = new Object[] { "2345", "War and Peace", "Russian Dude", new Integer(456) };
final Object[] o3 = new Object[] { "3456", "Jane Ayre", "Charlotte", new Integer(456) };
/**
* This loads any test specific Cassandra objects
*/
@Rule
public CassandraCQLUnit cassandraCQLUnit = new CassandraCQLUnit(new ClassPathCQLDataSet(
"cassandraOperationsTest-cql-dataload.cql", this.keyspace), CASSANDRA_CONFIG, CASSANDRA_HOST,
CASSANDRA_NATIVE_PORT);
@Before
public void setupTemplate() {
cassandraTemplate = new CassandraTemplate(session);
}
@Test
public void ringTest() {
List<RingMember> ring = cassandraTemplate.describeRing();
/*
* There must be 1 node in the cluster if the embedded server is
* running.
*/
assertNotNull(ring);
for (RingMember h : ring) {
log.info("ringTest Host -> " + h.address);
}
}
@Test
public void hostMapperTest() {
List<MyHost> ring = (List<MyHost>) cassandraTemplate.describeRing(new HostMapper<MyHost>() {
@Override
public Collection<MyHost> mapHosts(Set<Host> host) throws DriverException {
List<MyHost> list = new LinkedList<CassandraOperationsTest.MyHost>();
for (Host h : host) {
MyHost mh = new MyHost();
mh.someName = h.getAddress().getCanonicalHostName();
list.add(mh);
}
return list;
}
});
assertNotNull(ring);
Assert.assertTrue(ring.size() > 0);
for (MyHost h : ring) {
log.info("hostMapperTest Host -> " + h.someName);
}
}
@Test
public void ingestionTestListOfList() {
String cql = "insert into book (isbn, title, author, pages) values (?, ?, ?, ?)";
List<List<?>> values = new LinkedList<List<?>>();
List<Object> l1 = new LinkedList<Object>();
l1.add("1234");
l1.add("Moby Dick");
l1.add("Herman Manville");
l1.add(new Integer(456));
values.add(l1);
List<Object> l2 = new LinkedList<Object>();
l2.add("2345");
l2.add("War and Peace");
l2.add("Russian Dude");
l2.add(new Integer(456));
values.add(l2);
// values.add(new Object[] { "3456", "Jane Ayre", "Charlotte", new Integer(456) });
cassandraTemplate.ingest(cql, values);
// Assert that the rows were inserted into Cassandra
Book b1 = getBook("1234");
Book b2 = getBook("2345");
Assert.assertEquals(b1.getIsbn(), l1.get(0));
Assert.assertEquals(b2.getIsbn(), l2.get(0));
}
@Test
public void ingestionTestObjectArray() {
String cql = "insert into book (isbn, title, author, pages) values (?, ?, ?, ?)";
Object[][] values = new Object[3][];
values[0] = o1;
values[1] = o2;
values[2] = o3;
cassandraTemplate.ingest(cql, values);
// Assert that the rows were inserted into Cassandra
Book b1 = getBook("1234");
Book b2 = getBook("2345");
Book b3 = getBook("3456");
Assert.assertEquals(b1.getIsbn(), values[0][0]);
Assert.assertEquals(b2.getTitle(), values[1][1]);
Assert.assertEquals(b3.getAuthor(), values[2][2]);
}
/**
* This is an implementation of RowIterator for the purposes of testing passing your own Impl to CassandraTemplate
*
* @author David Webb
*/
final class MyRowIterator implements RowIterator {
private Object[][] values;
public MyRowIterator(Object[][] values) {
this.values = values;
}
int index = 0;
/* (non-Javadoc)
* @see org.springframework.cassandra.core.RowIterator#next()
*/
@Override
public Object[] next() {
return values[index++];
}
/* (non-Javadoc)
* @see org.springframework.cassandra.core.RowIterator#hasNext()
*/
@Override
public boolean hasNext() {
return index < values.length;
}
}
@Test
public void ingestionTestRowIterator() {
String cql = "insert into book (isbn, title, author, pages) values (?, ?, ?, ?)";
final Object[][] v = new Object[3][];
v[0] = o1;
v[1] = o2;
v[2] = o3;
RowIterator ri = new MyRowIterator(v);
cassandraTemplate.ingest(cql, ri);
// Assert that the rows were inserted into Cassandra
Book b1 = getBook("1234");
Book b2 = getBook("2345");
Book b3 = getBook("3456");
Assert.assertEquals(b1.getIsbn(), o1[0]);
Assert.assertEquals(b2.getTitle(), o2[1]);
Assert.assertEquals(b3.getAuthor(), o3[2]);
}
public Book getBook(final String isbn) {
Book b = this.cassandraTemplate.query("select * from book where isbn = ?", new PreparedStatementBinder() {
@Override
public BoundStatement bindValues(PreparedStatement ps) throws DriverException {
return ps.bind(isbn);
}
}, new ResultSetExtractor<Book>() {
@Override
public Book extractData(ResultSet rs) throws DriverException, DataAccessException {
Book b = new Book();
Row r = rs.one();
b.setIsbn(r.getString("isbn"));
b.setTitle(r.getString("title"));
b.setAuthor(r.getString("author"));
b.setPages(r.getInt("pages"));
return b;
}
});
return b;
}
/**
* For testing a HostMapper Implementation
*/
public class MyHost {
public String someName;
}
}

View File

@@ -22,12 +22,14 @@ import com.datastax.driver.core.Cluster.Builder;
@Configuration
public class TestConfig extends AbstractCassandraConfiguration {
public static final String keyspace = "test";
/* (non-Javadoc)
* @see org.springframework.data.cassandra.config.AbstractCassandraConfiguration#getKeyspaceName()
*/
@Override
protected String getKeyspaceName() {
return "test";
return keyspace;
}
/* (non-Javadoc)

View File

@@ -1,215 +0,0 @@
/*
* 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.data.cassandra.test.integration.template;
import static org.junit.Assert.assertNotNull;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import junit.framework.Assert;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.thrift.transport.TTransportException;
import org.cassandraunit.CassandraCQLUnit;
import org.cassandraunit.DataLoader;
import org.cassandraunit.dataset.cql.ClassPathCQLDataSet;
import org.cassandraunit.dataset.yaml.ClassPathYamlDataSet;
import org.cassandraunit.utils.EmbeddedCassandraServerHelper;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cassandra.core.BoundStatementFactory;
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.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.DataType;
import com.datastax.driver.core.Host;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.exceptions.DriverException;
/**
* Unit Tests for CassandraTemplate
*
* @author David Webb
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigContextLoader.class)
public class CassandraOperationsTest {
/**
* @author David Webb
*
*/
public class MyHost {
public String someName;
}
@Autowired
private CassandraOperations cassandraTemplate;
private static Logger log = LoggerFactory.getLogger(CassandraOperationsTest.class);
private final static String CASSANDRA_CONFIG = "cassandra.yaml";
private final static String KEYSPACE_NAME = "test";
private final static String CASSANDRA_HOST = "localhost";
private final static int CASSANDRA_NATIVE_PORT = 9042;
private final static int CASSANDRA_THRIFT_PORT = 9160;
@Rule
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,
InterruptedException {
EmbeddedCassandraServerHelper.startEmbeddedCassandra(CASSANDRA_CONFIG);
/*
* Load data file to creat the test keyspace before we init the template
*/
DataLoader dataLoader = new DataLoader("Test Cluster", CASSANDRA_HOST + ":" + CASSANDRA_THRIFT_PORT);
dataLoader.load(new ClassPathYamlDataSet("cassandra-keyspace.yaml"));
}
@Test
public void ringTest() {
List<RingMember> ring = cassandraTemplate.describeRing();
/*
* There must be 1 node in the cluster if the embedded server is
* running.
*/
assertNotNull(ring);
for (RingMember h : ring) {
log.info("ringTest Host -> " + h.address);
}
}
@Test
public void hostMapperTest() {
List<MyHost> ring = (List<MyHost>) cassandraTemplate.describeRing(new HostMapper<MyHost>() {
@Override
public Collection<MyHost> mapHosts(Set<Host> host) throws DriverException {
List<MyHost> list = new LinkedList<CassandraOperationsTest.MyHost>();
for (Host h : host) {
MyHost mh = new MyHost();
mh.someName = h.getAddress().getCanonicalHostName();
list.add(mh);
}
return list;
}
});
assertNotNull(ring);
Assert.assertTrue(ring.size() > 0);
for (MyHost h : ring) {
log.info("hostMapperTest Host -> " + h.someName);
}
}
@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 boundStatementFactoryTest() {
String cql = "insert into book (isbn, title, author, pages) values (?, ?, ?, ?)";
BoundStatementFactory bsf = new BoundStatementFactory(cql);
bsf.addValues(new Object[] { "1234", "Moby Dick", "Herman Manville", new Integer(456) }, new Object[] { "2345",
"War and Peace", "Russian Dude", new Integer(456) }, new Object[] { "3456", "Jane Ayre", "Charlotte",
new Integer(456) });
cassandraTemplate.execute(bsf);
}
@After
public void clearCassandra() {
EmbeddedCassandraServerHelper.cleanEmbeddedCassandra();
}
@AfterClass
public static void stopCassandra() {
// EmbeddedCassandraServerHelper.stopEmbeddedCassandra();
}
}