https://github.com/codesmell/springcqltemplate
Playing with ideas for updating CqlTemplate in Spring Data Cassandra
https://github.com/codesmell/springcqltemplate
Last synced: 4 months ago
JSON representation
Playing with ideas for updating CqlTemplate in Spring Data Cassandra
- Host: GitHub
- URL: https://github.com/codesmell/springcqltemplate
- Owner: CodeSmell
- Created: 2018-06-01T17:20:29.000Z (almost 8 years ago)
- Default Branch: master
- Last Pushed: 2018-07-25T15:06:40.000Z (over 7 years ago)
- Last Synced: 2025-02-16T09:20:10.688Z (about 1 year ago)
- Language: Java
- Homepage:
- Size: 15.6 KB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Spring Data Cassandra: CqlTemplate
Our team was working with [Spring Data Cassandra](https://projects.spring.io/spring-data-cassandra/). We had queries that are run often so we began adding support for the `PreparedStatement` in our DAO.
* [Prepared Statements in Cassandra](https://docs.datastax.com/en/developer/java-driver/3.5/manual/statements/prepared/)
* [FAQ for Prepared Statements in Cassandra](https://cassandra-zone.com/prepared-statements/)
Like all Spring projects there are numerous ways to do this.
As we looked at the code we came across some potential enhancements for the `CqlTemplate`.
## Caching PreparedStatements
The first was the lack of a caching mechanism. This is documented in [DATACASS-555](https://jira.spring.io/browse/DATACASS-555).
Running this query will generate a `SimplePreparedStatement` around the CQL.
```
ResultSet rs = cqlTemplate.queryForResultSet("SELECT username FROM user WHERE id = ?", "WHITE");
```
When this code is run multiple times a warning is generated by the DataStax driver. The warning is letting the team know that there is no need to continually re-prepare the statement. Instead it should be cached for repeated use.
```
14:39:18.994 [main] DEBUG o.s.d.cassandra.core.cql.CqlTemplate - Preparing statement [SELECT username FROM user WHERE id = ?] using org.springframework.data.cassandra.core.cql.SimplePreparedStatementCreator@60a2630a
14:39:19.018 [main] DEBUG o.s.d.cassandra.core.cql.CqlTemplate - Executing prepared statement [com.datastax.driver.core.DefaultPreparedStatement@330c1f61]
...
14:39:22.091 [main] DEBUG o.s.d.cassandra.core.cql.CqlTemplate - Preparing statement [SELECT username FROM user WHERE id = ?] using org.springframework.data.cassandra.core.cql.SimplePreparedStatementCreator@60a2630a
14:39:22.094 [cluster-worker-1] WARN com.datastax.driver.core.Cluster - Re-preparing already prepared query is generally an anti-pattern and will likely affect performance. Consider preparing the statement only once. Query='SELECT username FROM user WHERE id = ?'
```
The FAQ article above notes that re-preparing a query will not result in re-parsing the CQL part of the statement. However it does recalculate an MD5 digest for each call.
Spring Data Cassandra provides an interface for caching a `PreparedStatement`. The `PreparedStatementCache` [API](https://docs.spring.io/spring-data/cassandra/docs/current/api/org/springframework/data/cassandra/core/cql/support/PreparedStatementCache.html) describes the interface. However, the `CqlTemplate` currently does not use this interface, nor was a PR to add it accepted.
Our team made some tweaks so that we could provide an implementation of the `PreparedStatementCache`. This attribute/variable can be set when the CqlTemplate (or for now the custom subclass) is created through a setter. We are currently using the cache implementation provided in the Spring Data Cassandra project.
```
/**
* without the change in CqlTemplate
* would need to replace CqlTemplate with EnhancedCqlTemplate
*/
@Bean
public CqlTemplate cassandraCqlTemplate() {
CqlTemplate ct = new CqlTemplate(cassandraSession());
ct.setPreparedStatementCache(MapPreparedStatementCache.create());
return ct;
}
```
## Adding methods that create PreparedStatements using RegularStatement & use Consistency Level
The second potential enhancement was overloading the `query` and `execute` methods so that the `CqlTemplate` would create and cache a `PreparedStatement` when it was provided with a DataStax `RegularStatement` [API](https://docs.datastax.com/en/drivers/java/3.4/com/datastax/driver/core/querybuilder/BuiltStatement.html). This provides the basis for the fluent API to build CQL. This is documented in [DATACASS-556](https://jira.spring.io/browse/DATACASS-556).
The fluent API is certainly a nicer way to write CQL.
```
Select select = QueryBuilder.select("username").from("user");
select.where(QueryBuilder.eq("id", QueryBuilder.bindMarker()))
.setConsistencyLevel(ConsistencyLevel.QUORUM);
// not supported in CqlTemplate
ResultSet rs = cqlTemplate.queryForResultSet(select, "WHITE");
```
However, there is no available `queryForResultSet` method that allows us to generate a `PreparedStatement`.
To do that we need to tweak our code as follows:
```
Select select = QueryBuilder.select("username").from("user");
select.where(QueryBuilder.eq("id", QueryBuilder.bindMarker()))
.setConsistencyLevel(ConsistencyLevel.QUORUM);
ResultSet rs = cqlTemplate.queryForResultSet(select.getQueryString(), "WHITE");
```
This approach (using `getQueryString`) will result in the consistency level for the query being ignored. The default set for the `CqlTemplate`, `Cluster` or the default for Cassandra will be used instead.
The "enhanced" `CqlTemplate` adds some helper methods (which unfortunately means having to add methods to the `CqlOperations` interface) so that the user of the API can use the DataStax fluent API, Prepared Statements and get the consistency level set on the statement to work as expected.
These new methods follows the existing pattern in the `CqlTemplate` of methods using varargs generating a `PreparedStatement`.
```
Select select = QueryBuilder.select("username").from("user");
select.where(QueryBuilder.eq("id", QueryBuilder.bindMarker()))
.setConsistencyLevel(ConsistencyLevel.QUORUM)
.enableTracing();
// available only with customized CqlTemplate
ResultSet rs = cqlTemplate.queryForResultSet(select, "WHITE");
rs.getExecutionInfo().getQueryTrace().getParameters().get("consistency_level");
```
This code above will generate a `PreparedStatement` using the `RegularStatement`. This also allows the user of the API to set the consistency level at the request level. It also allows the user to turn tracing on and have access to the trace parameters.
## Handling this in CqlTemplate without changes
To get the same capability (caching, DataStax fluent API, Prepared Statements, and consistency levels) in the current `CqlTemplate` one would have to do the following in the DAO code:
* manage the `PreparedStatementCache`
* use `PreparedStatementCreator` and `PreparedStatementBinder`
* and write the following:
```
@Autowired
PreparedStatementCache cache;
@Autowired
CqlTemplate cqlTemplate;
String username = cqlTemplate.query(
session -> cache.getPreparedStatement(session, select),
ps -> ps.bind("WHITE"),
this::resultSetHandler);
```
IMO, this doesn't seem as simple or as clean as it could be for the user of the framework. Why should these all be managed in the user's code when it is relatively simple to let the framework do it. Further, there isn't an option in the `CqlTemplate` that will return a `ResultSet` allowing the user to access the trace parameters.