I want to build a RESTful API with Java and Cassandra 2.x (on Jersey framework). I'm new to both technologies so I would like to ask you is that the correct way to integrate and share Cassandra driver.
0. Get the driver though Maven
<dependency>
<groupId>com.datastax.cassandra</groupId>
<artifactId>cassandra-driver-core</artifactId>
<version>2.0.3</version>
</dependency>
1. Wrap driver's functionality with a Client class:
package com.example.cassandra;
import com.datastax.driver.core.*;
public class Client {
private Cluster cluster;
private Session session;
public Client(String node) {
connect( node );
}
private void connect(String node) {
cluster = Cluster.builder()
.addContactPoint(node)
.build();
session = cluster.connect();
}
public ResultSet execute( String cql3 ) {
return session.execute( cql3 );
}
public void close() {
cluster.close();
}
}
2. I insatiate the client in ContextListener and share it though context attribute
package com.example.listener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import com.example.cassandra.Client;
public class ExampleContextListener implements ServletContextListener {
Client cassandraClient;
public void contextInitialized(ServletContextEvent servletContextEvent) {
ServletContext ctx = servletContextEvent.getServletContext();
cassandraClient = new Client( ctx.getInitParameter( "DBHost" ) );
ctx.setAttribute( "DB", cassandraClient );
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
cassandraClient.close();
}
}
3. Now I get the client from servlet's context and use it
Client client = (Client) context.getAttribute("DB");
client.execute("USE testspace;");
ResultSet rs = client.execute("SELECT * from users;");
for (Row row : rs ) {
output += row.getString("lname") + "|";
}
Is that the correct way to do it (both from performance and architectural point of view)?
Full example available on: https://github.com/lukaszkujawa/jersey-cassandra
The driver architecture is based on layers. At the bottom lies the driver core. This core handles everything related to the connections to a Cassandra cluster (for example, connection pool, discovering new nodes, etc.) and exposes a simple, relatively low-level API on top of which higher level layers can be built.
A modern, feature-rich and highly tunable Node. js client library for Apache Cassandra and DSE using exclusively Cassandra's binary protocol and Cassandra Query Language.
jdbc:Cassandra:URL=http://myCassandra.com;User=myUser;Password=myPassword; Click Test Connection.
I just developed what you are going to develop. What you wrote works but it's not my favourite approach. I'd rather create a Singleton (since 1 session is enough for an application). Following Joshua Bloch enum's singleton's pattern here is what I did
public enum Cassandra {
DB;
private Session session;
private Cluster cluster;
private static final Logger LOGGER = LoggerFactory.getLogger(Cassandra.class);
/**
* Connect to the cassandra database based on the connection configuration provided.
* Multiple call to this method will have no effects if a connection is already established
* @param conf the configuration for the connection
*/
public void connect(ConnectionCfg conf) {
if (cluster == null && session == null) {
cluster = Cluster.builder().withPort(conf.getPort()).withCredentials(conf.getUsername(), conf.getPassword()).addContactPoints(conf.getSeeds()).build();
session = cluster.connect(conf.getKeyspace());
}
Metadata metadata = cluster.getMetadata();
LOGGER.info("Connected to cluster: " + metadata.getClusterName() + " with partitioner: " + metadata.getPartitioner());
metadata.getAllHosts().stream().forEach((host) -> {
LOGGER.info("Cassandra datacenter: " + host.getDatacenter() + " | address: " + host.getAddress() + " | rack: " + host.getRack());
});
}
/**
* Invalidate and close the session and connection to the cassandra database
*/
public void shutdown() {
LOGGER.info("Shutting down the whole cassandra cluster");
if (null != session) {
session.close();
}
if (null != cluster) {
cluster.close();
}
}
public Session getSession() {
if (session == null) {
throw new IllegalStateException("No connection initialized");
}
return session;
}
}
And in the context listener I call connect or shutdown. Since all exceptions in new driver are unchecked my tip for you is to create your own implementation of the Jersey ExceptionMapper mapping DriverException. One more thing, think about working with PreparedStatements rather than Strings so that Cassandra parse the query only once. In my application I followed the above patterns also for the queries (an enum singleton that prepare statements when loaded first time and then expose methods to use these statements).
HTH, Carlo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With