{"id":23483782,"url":"https://github.com/codesmell/springcqltemplate","last_synced_at":"2025-10-29T21:02:31.734Z","repository":{"id":108722755,"uuid":"135745086","full_name":"CodeSmell/SpringCqlTemplate","owner":"CodeSmell","description":"Playing with ideas for updating CqlTemplate in Spring Data Cassandra","archived":false,"fork":false,"pushed_at":"2018-07-25T15:06:40.000Z","size":16,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-16T09:20:10.688Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CodeSmell.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-01T17:20:29.000Z","updated_at":"2018-07-25T15:06:41.000Z","dependencies_parsed_at":"2023-03-13T14:23:47.310Z","dependency_job_id":null,"html_url":"https://github.com/CodeSmell/SpringCqlTemplate","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeSmell%2FSpringCqlTemplate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeSmell%2FSpringCqlTemplate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeSmell%2FSpringCqlTemplate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeSmell%2FSpringCqlTemplate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CodeSmell","download_url":"https://codeload.github.com/CodeSmell/SpringCqlTemplate/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248799961,"owners_count":21163404,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-24T21:16:11.440Z","updated_at":"2025-10-29T21:02:31.668Z","avatar_url":"https://github.com/CodeSmell.png","language":"Java","readme":"# Spring Data Cassandra: CqlTemplate\n\nOur 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. \n\n* [Prepared Statements in Cassandra](https://docs.datastax.com/en/developer/java-driver/3.5/manual/statements/prepared/)\n* [FAQ for Prepared Statements in Cassandra](https://cassandra-zone.com/prepared-statements/)\n\nLike all Spring projects there are numerous ways to do this.\n\nAs we looked at the code we came across some potential enhancements for the `CqlTemplate`.\n\n## Caching PreparedStatements\nThe first was the lack of a caching mechanism. This is documented in [DATACASS-555](https://jira.spring.io/browse/DATACASS-555). \n\nRunning this query will generate a `SimplePreparedStatement` around the CQL. \n\n```\nResultSet rs = cqlTemplate.queryForResultSet(\"SELECT username FROM user WHERE id = ?\", \"WHITE\");\n```\n\nWhen 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. \n\n```\n14: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\n14:39:19.018 [main] DEBUG o.s.d.cassandra.core.cql.CqlTemplate - Executing prepared statement [com.datastax.driver.core.DefaultPreparedStatement@330c1f61]\n...\n14: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\n14: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 = ?'\n```\n\nThe 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.\n\nSpring 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.\n\nOur 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.\n\n```\n/**\n * without the change in CqlTemplate\n * would need to replace CqlTemplate with EnhancedCqlTemplate\n */\n@Bean\npublic CqlTemplate cassandraCqlTemplate() {\n   CqlTemplate ct = new CqlTemplate(cassandraSession());\n   ct.setPreparedStatementCache(MapPreparedStatementCache.create());\n   return ct;\n}\n```\n\n## Adding methods that create PreparedStatements using RegularStatement \u0026 use Consistency Level\nThe 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). \n\nThe fluent API is certainly a nicer way to write CQL.\n\n```\nSelect select = QueryBuilder.select(\"username\").from(\"user\");\n\tselect.where(QueryBuilder.eq(\"id\", QueryBuilder.bindMarker()))\n     .setConsistencyLevel(ConsistencyLevel.QUORUM);\n\n// not supported in CqlTemplate\nResultSet rs = cqlTemplate.queryForResultSet(select, \"WHITE\");\n```\n\nHowever, there is no available `queryForResultSet` method that allows us to generate a `PreparedStatement`.\nTo do that we need to tweak our code as follows:\n\n```\nSelect select = QueryBuilder.select(\"username\").from(\"user\");\n\tselect.where(QueryBuilder.eq(\"id\", QueryBuilder.bindMarker()))\n     .setConsistencyLevel(ConsistencyLevel.QUORUM);\n     \nResultSet rs = cqlTemplate.queryForResultSet(select.getQueryString(), \"WHITE\");\n```\nThis 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. \n\nThe \"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. \n\nThese new methods follows the existing pattern in the `CqlTemplate` of methods using varargs generating a `PreparedStatement`.  \n\n```\nSelect select = QueryBuilder.select(\"username\").from(\"user\");\n\tselect.where(QueryBuilder.eq(\"id\", QueryBuilder.bindMarker()))\n     .setConsistencyLevel(ConsistencyLevel.QUORUM)\n     .enableTracing();\n     \n// available only with customized CqlTemplate\nResultSet rs = cqlTemplate.queryForResultSet(select, \"WHITE\");\nrs.getExecutionInfo().getQueryTrace().getParameters().get(\"consistency_level\");\n```\n\nThis 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. \n\n## Handling this in CqlTemplate without changes \nTo 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:\n\n* manage the `PreparedStatementCache`\n* use `PreparedStatementCreator` and `PreparedStatementBinder` \n* and write the following:\n\n```\n@Autowired\nPreparedStatementCache cache;\n\n@Autowired\nCqlTemplate cqlTemplate;\n\nString username = cqlTemplate.query(\n   \tsession -\u003e cache.getPreparedStatement(session, select),\n     ps -\u003e ps.bind(\"WHITE\"),\n     this::resultSetHandler);\n```\n\nIMO, 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. \n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodesmell%2Fspringcqltemplate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodesmell%2Fspringcqltemplate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodesmell%2Fspringcqltemplate/lists"}