diff --git a/spring-cql/pom.xml b/spring-cql/pom.xml index b6f9de6c0..343dff8a0 100644 --- a/spring-cql/pom.xml +++ b/spring-cql/pom.xml @@ -114,6 +114,11 @@ guava 15.0 + + org.liquibase + liquibase-core + 3.1.1 + diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/CachedPreparedStatementCreator.java b/spring-cql/src/main/java/org/springframework/cassandra/core/CachedPreparedStatementCreator.java new file mode 100644 index 000000000..dbdd7c752 --- /dev/null +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/CachedPreparedStatementCreator.java @@ -0,0 +1,86 @@ +/* + * Copyright 2013-2014 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.Map; +import java.util.concurrent.ConcurrentHashMap; + +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; + +/** + * This Prepared Statement Creator maintains a cache of all prepared statements for the duration of this life of the + * container. + * + * When preparing statements with Cassandra, each Statement should be prepared once and only once due to the overhead of + * preparing the statement. + * + * @author David Webb + * + */ +public class CachedPreparedStatementCreator implements PreparedStatementCreator { + + private static final Logger log = LoggerFactory.getLogger(CachedPreparedStatementCreator.class); + + private final String cql; + + private static final Map> psMap = new ConcurrentHashMap>(); + + /** + * Create a PreparedStatementCreator from the provided CQL. + * + * @param cql + */ + public CachedPreparedStatementCreator(String cql) { + Assert.notNull(cql, "CQL is required to create a PreparedStatement"); + this.cql = cql; + } + + public String getCql() { + return this.cql; + } + + @Override + public PreparedStatement createPreparedStatement(Session session) throws DriverException { + + StringBuilder keyspaceCQLKey = new StringBuilder().append(session.getLoggedKeyspace()).append("|").append(this.cql); + + log.debug(String.format("Cachable PreparedStatement in Keyspace [%s]", session.getLoggedKeyspace())); + + Map sessionMap = psMap.get(session); + if (sessionMap == null) { + sessionMap = new ConcurrentHashMap(); + psMap.put(session, sessionMap); + } + + PreparedStatement pstmt = sessionMap.get(keyspaceCQLKey.toString()); + if (pstmt == null) { + log.debug("No Cached PreparedStatement found...Creating and Caching"); + pstmt = session.prepare(this.cql); + sessionMap.put(keyspaceCQLKey.toString(), pstmt); + } else { + log.debug("Found cached PreparedStatement"); + } + + return pstmt; + } + +} diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/CqlOperations.java b/spring-cql/src/main/java/org/springframework/cassandra/core/CqlOperations.java index 6b71f77d0..0c0a0781d 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/CqlOperations.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/CqlOperations.java @@ -737,7 +737,7 @@ public interface CqlOperations { List> processListOfMap(ResultSet resultSet) throws DataAccessException; /** - * Converts the CQL provided into a {@link SimplePreparedStatementCreator}. This can only be used for CQL + * Converts the CQL provided into a {@link CachedPreparedStatementCreator}. This can only be used for CQL * Statements that do not have data binding. The results of the PreparedStatement are processed with * PreparedStatementCallback implementation provided by the Application Code. * @@ -762,7 +762,7 @@ public interface CqlOperations { T execute(PreparedStatementCreator psc, PreparedStatementCallback action) throws DataAccessException; /** - * Converts the CQL provided into a {@link SimplePreparedStatementCreator}. Then, the PreparedStatementBinder will + * Converts the CQL provided into a {@link CachedPreparedStatementCreator}. 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. @@ -776,7 +776,7 @@ public interface CqlOperations { T query(String cql, PreparedStatementBinder psb, ResultSetExtractor rse) throws DataAccessException; /** - * Converts the CQL provided into a {@link SimplePreparedStatementCreator}. Then, the PreparedStatementBinder will + * Converts the CQL provided into a {@link CachedPreparedStatementCreator}. 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. @@ -792,7 +792,7 @@ public interface CqlOperations { throws DataAccessException; /** - * Converts the CQL provided into a {@link SimplePreparedStatementCreator}. Then, the PreparedStatementBinder will + * Converts the CQL provided into a {@link CachedPreparedStatementCreator}. 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. * @@ -804,7 +804,7 @@ public interface CqlOperations { void query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch) throws DataAccessException; /** - * Converts the CQL provided into a {@link SimplePreparedStatementCreator}. Then, the PreparedStatementBinder will + * Converts the CQL provided into a {@link CachedPreparedStatementCreator}. 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. * @@ -818,7 +818,7 @@ public interface CqlOperations { throws DataAccessException; /** - * Converts the CQL provided into a {@link SimplePreparedStatementCreator}. Then, the PreparedStatementBinder will + * Converts the CQL provided into a {@link CachedPreparedStatementCreator}. 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 for each Row * returned. @@ -832,7 +832,7 @@ public interface CqlOperations { List query(String cql, PreparedStatementBinder psb, RowMapper rowMapper) throws DataAccessException; /** - * Converts the CQL provided into a {@link SimplePreparedStatementCreator}. Then, the PreparedStatementBinder will + * Converts the CQL provided into a {@link CachedPreparedStatementCreator}. 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 for each Row * returned. diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/CqlTemplate.java b/spring-cql/src/main/java/org/springframework/cassandra/core/CqlTemplate.java index 8d56be6b0..3d92d931b 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/CqlTemplate.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/CqlTemplate.java @@ -15,6 +15,8 @@ */ package org.springframework.cassandra.core; +import static org.springframework.cassandra.core.cql.CqlIdentifier.cqlId; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -72,8 +74,6 @@ import com.datastax.driver.core.querybuilder.Select; import com.datastax.driver.core.querybuilder.Truncate; import com.datastax.driver.core.querybuilder.Update; -import static org.springframework.cassandra.core.cql.CqlIdentifier.cqlId; - /** * This is the Central class in the Cassandra core package. 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 @@ -843,7 +843,7 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { @Override public T execute(String cql, PreparedStatementCallback action) { - return execute(new SimplePreparedStatementCreator(cql), action); + return execute(new CachedPreparedStatementCreator(cql), action); } @Override @@ -882,7 +882,7 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { @Override public T query(String cql, PreparedStatementBinder psb, ResultSetExtractor rse, QueryOptions options) throws DataAccessException { - return query(new SimplePreparedStatementCreator(cql), psb, rse, options); + return query(new CachedPreparedStatementCreator(cql), psb, rse, options); } @Override @@ -893,7 +893,7 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { @Override public void query(String cql, PreparedStatementBinder psb, RowCallbackHandler rch, QueryOptions options) throws DataAccessException { - query(new SimplePreparedStatementCreator(cql), psb, rch, options); + query(new CachedPreparedStatementCreator(cql), psb, rch, options); } @Override @@ -904,7 +904,7 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { @Override public List query(String cql, PreparedStatementBinder psb, RowMapper rowMapper, QueryOptions options) throws DataAccessException { - return query(new SimplePreparedStatementCreator(cql), psb, rowMapper, options); + return query(new CachedPreparedStatementCreator(cql), psb, rowMapper, options); } @Override @@ -915,11 +915,13 @@ public class CqlTemplate extends CassandraAccessor implements CqlOperations { @Override public void ingest(String cql, RowIterator rowIterator, WriteOptions options) { - PreparedStatement preparedStatement = getSession().prepare(cql); + CachedPreparedStatementCreator cpsc = new CachedPreparedStatementCreator(cql); + + PreparedStatement preparedStatement = cpsc.createPreparedStatement(getSession()); addPreparedStatementOptions(preparedStatement, options); while (rowIterator.hasNext()) { - getSession().execute(preparedStatement.bind(rowIterator.next())); + getSession().executeAsync(preparedStatement.bind(rowIterator.next())); } } diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/PreparedStatementCreatorImpl.java b/spring-cql/src/main/java/org/springframework/cassandra/core/PreparedStatementCreatorImpl.java deleted file mode 100644 index 524020d49..000000000 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/PreparedStatementCreatorImpl.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2013-2014 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 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 PreparedStatementCreatorImpl implements PreparedStatementCreator, PreparedStatementBinder { - - private final String cql; - private List values; - - public PreparedStatementCreatorImpl(String cql) { - this.cql = cql; - } - - public PreparedStatementCreatorImpl(String cql, List values) { - this.cql = cql; - this.values = values; - } - - /* (non-Javadoc) - * @see org.springframework.cassandra.core.PreparedStatementSetter#setValues(com.datastax.driver.core.PreparedStatement) - */ - @Override - public BoundStatement bindValues(PreparedStatement ps) throws DriverException { - // Nothing to set if there are no values - if (values == null) { - return new BoundStatement(ps); - } - - return ps.bind(values.toArray()); - - } - - 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 { - return session.prepare(this.cql); - } - -} diff --git a/spring-cql/src/main/java/org/springframework/cassandra/core/SimplePreparedStatementCreator.java b/spring-cql/src/main/java/org/springframework/cassandra/core/SimplePreparedStatementCreator.java index cbaf64374..4eb35f011 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/core/SimplePreparedStatementCreator.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/core/SimplePreparedStatementCreator.java @@ -22,6 +22,12 @@ import com.datastax.driver.core.Session; import com.datastax.driver.core.exceptions.DriverException; /** + * This Prepared Statement Creator simply prepares a statement from the CQL string. This should not be used in + * Production systems with high volume reads and writes. Use {@link CachedPreparedStatementCreator} + * + * When preparing statements with Cassandra, each Statement should be prepared once and only once due to the overhead of + * preparing the statement. + * * @author David Webb * */ @@ -43,9 +49,6 @@ public class SimplePreparedStatementCreator implements PreparedStatementCreator 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 { return session.prepare(this.cql); diff --git a/spring-cql/src/test/resources/logback-test.xml b/spring-cql/src/test/resources/logback-test.xml index 453120668..24e8a279d 100644 --- a/spring-cql/src/test/resources/logback-test.xml +++ b/spring-cql/src/test/resources/logback-test.xml @@ -10,7 +10,7 @@ - +