The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

Using the Cassandra Client

Apache Cassandra® is a free and open-source, distributed, wide column store, NoSQL database management system designed to handle large amounts of data across many commodity servers, providing high availability with no single point of failure.

In this guide, we will see how you can get your REST services to use a Cassandra database.

此扩展程序由第三方开发,是 Quarkus 平台的一部分。

先决条件

完成这个指南,你需要:

  • 大概15分钟

  • 编辑器

  • JDK 17+ installed with JAVA_HOME configured appropriately

  • Apache Maven 3.9.9

  • 如果你愿意的话,还可以选择使用Quarkus CLI

  • 如果你想构建原生可执行程序,可以选择安装Mandrel或者GraalVM,并正确配置(或者使用Docker在容器中进行构建)

  • A running Apache Cassandra, DataStax Enterprise (DSE) or DataStax Astra database; or alternatively, a fresh Docker installation.

应用结构

This quickstart guide shows how to build a REST application using the Cassandra Quarkus extension, which allows you to connect to an Apache Cassandra, DataStax Enterprise (DSE) or DataStax Astra database, using the DataStax Java driver.

This guide will also use the DataStax Object Mapper – a powerful Java-to-CQL mapping framework that greatly simplifies your application’s data access layer code by sparing you the hassle of writing your CQL queries by hand.

The application built in this quickstart guide is quite simple: the user can add elements in a list using a form, and the items list is updated. All the information between the browser and the server is formatted as JSON, and the elements are stored in the Cassandra database.

解决方案

我们建议您按照下一节的说明逐步创建应用程序。然而,您可以直接转到已完成的示例。

The solution is located in the quickstart directory of the Cassandra Quarkus extension GitHub repository.

Creating a Blank Maven Project

First, create a new Maven project and copy the pom.xml file that is present in the quickstart directory.

The pom.xml is importing all the Quarkus extensions and dependencies you need.

Creating the Data Model and Data Access Objects

在这个例子中,我们将创建一个应用程序来管理fruit列表。

First, let’s create our data model – represented by the Fruit class – as follows:

@Entity
@PropertyStrategy(mutable = false)
public class Fruit {

    @PartitionKey
    private final String name;

    private final String description;

    public Fruit(String name, String description) {
      this.name = name;
      this.description = description;
    }
  // getters, hashCode, equals, toString methods omitted for brevity
}

As stated above, we are using the DataStax Object Mapper. In other words, we are not going to write our CQL queries manually; instead, we will annotate our data model with a few annotations, and the mapper will generate proper CQL queries underneath.

This is why the Fruit class is annotated with @Entity: this annotation marks it as an entity class that is mapped to a Cassandra table. Its instances are meant to be automatically persisted into, and retrieved from, the Cassandra database. Here, the table name will be inferred from the class name: fruit.

Also, the name field represents a Cassandra partition key, and so we are annotating it with @PartitionKey – another annotation from the Object Mapper library.

Entity classes are normally required to have a default no-arg constructor, unless they are annotated with @PropertyStrategy(mutable = false), which is the case here.

The next step is to create a DAO (Data Access Object) interface that will manage instances of Fruit entities:

@Dao
public interface FruitDao {
  @Update
  void update(Fruit fruit);

  @Select
  PagingIterable<Fruit> findAll();
}

This interface exposes operations that will be used in our REST service. Again, the annotation @Dao comes from the DataStax Object Mapper, which will also automatically generate an implementation of this interface for you.

Note also the special return type of the findAll method, PagingIterable: it’s the base type of result sets returned by the driver.

Finally, let’s create a Mapper interface:

@Mapper
public interface FruitMapper {
  @DaoFactory
  FruitDao fruitDao();
}

The @Mapper annotation is yet another annotation recognized by the DataStax Object Mapper. A mapper is responsible for constructing DAO instances – in this case, out mapper is constructing an instance of our only DAO, FruitDao.

Think of the mapper interface as a factory for DAO beans. If you intend to construct and inject a specific DAO bean in your own code, then you first must add a @DaoFactory method for it in a @Mapper interface.

@DaoFactory method names are irrelevant.

@DaoFactory methods should return beans of the following types:

  • Any @Dao-annotated interface, e.g. FruitDao;

  • A CompletionStage of any @Dao-annotated interface, e.g. CompletionStage<FruitDao>.

  • A Uni of any @Dao-annotated interface, e.g. Uni<FruitDao>.

Uni is a type from the Mutiny library, which is the reactive programming library used by Quarkus. This will be explained in more detail in the "Reactive Programming" section below.

Generating the DAO and mapper implementations

As you probably guessed already, we are not going to implement the interfaces above. Instead, the Object Mapper will generate such implementations for us.

The Object Mapper is composed of 2 pieces:

  1. A (compile-time) annotation processor that scans the classpath for classes annotated with @Mapper, @Dao or @Entity, and generates code and CQL queries for them; and

  2. A runtime module that contains the logic to execute the generated queries.

Therefore, enabling the Object Mapper requires two steps:

  1. Declare the cassandra-quarkus-mapper-processor annotation processor. With Maven, this is done by modifying the compiler plugin configuration in the project’s pom.xml file as follows:

<plugin>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.10.1</version>
  <configuration>
    <source>${java.version}</source>
    <target>${java.version}</target>
    <annotationProcessorPaths>
      <path>
        <groupId>com.datastax.oss.quarkus</groupId>
        <artifactId>cassandra-quarkus-mapper-processor</artifactId>
        <version>${cassandra-quarkus.version}</version>
      </path>
    </annotationProcessorPaths>
  </configuration>
</plugin>

With Gradle, this is done by adding the following line to the build.gradle file:

annotationProcessor "com.datastax.oss.quarkus:cassandra-quarkus-mapper-processor:${cassandra-quarkus.version}"
Verify that you are enabling the right annotation processor! The Cassandra driver ships with its Object Mapper annotation processor, called java-driver-mapper-processor. But the Cassandra Quarkus extension also ships with its own annotation processor: cassandra-quarkus-mapper-processor, which has more capabilities than the driver’s. This annotation processor is the only one suitable for use in a Quarkus application, so check that this is the one in use. Also, never use both annotation processors together.
  1. Declare the java-driver-mapper-runtime dependency in compile scope in the project’s pom.xml file as follows:

<dependency>
  <groupId>com.datastax.oss</groupId>
  <artifactId>java-driver-mapper-runtime</artifactId>
</dependency>
Although this module is called "runtime", it must be declared in compile scope.

If your project is correctly set up, you should now be able to compile it without errors, and you should see the generated code in the target/generated-sources/annotations directory (if you are using Maven). It’s not required to get familiar with the generated code though, as it is mostly internal machinery to interact with the database.

Creating a service & JSON REST endpoint

Now let’s create a FruitService that will be the business layer of our application and store/load the fruits from the Cassandra database.

@ApplicationScoped
public class FruitService {

  @Inject FruitDao dao;

  public void save(Fruit fruit) {
    dao.update(fruit);
  }

  public List<Fruit> getAll() {
    return dao.findAll().all();
  }
}

Note how the service is being injected a FruitDao instance. This DAO instance is injected automatically, thanks to the generated implementations.

The Cassandra Quarkus extension allows you to inject any of the following beans in your own components:

  • All @Mapper-annotated interfaces in your project.

  • You can also inject a CompletionStage or Uni of any @Mapper-annotated interface.

  • Any bean returned by a @DaoFactory method (see above for possible bean types).

  • The QuarkusCqlSession bean: this application-scoped, singleton bean is your main entry point to the Cassandra client; it is a specialized Cassandra driver session instance with a few methods tailored especially for Quarkus. Read its javadocs carefully!

  • You can also inject CompletionStage<QuarkusCqlSession> or Uni<QuarkusCqlSession>.

In our example, both FruitMapper and FruitDao could be injected anywhere. We chose to inject FruitDao in FruitService.

The last missing piece is the REST API that will expose GET and POST methods:

@Path("/fruits")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class FruitResource {

  @Inject FruitService fruitService;

  @GET
  public List<FruitDto> getAll() {
    return fruitService.getAll().stream().map(this::convertToDto).collect(Collectors.toList());
  }

  @POST
  public void add(FruitDto fruit) {
    fruitService.save(convertFromDto(fruit));
  }

  private FruitDto convertToDto(Fruit fruit) {
    return new FruitDto(fruit.getName(), fruit.getDescription());
  }

  private Fruit convertFromDto(FruitDto fruitDto) {
    return new Fruit(fruitDto.getName(), fruitDto.getDescription());
  }
}

Notice how FruitResource is being injected a FruitService instance automatically.

It is generally not recommended using the same entity object between the REST API and the data access layer. These layers should indeed be decoupled and use distinct APIs in order to allow each API to evolve independently of the other. This is the reason why our REST API is using a different object: the FruitDto class – the word DTO stands for "Data Transfer Object". This DTO object will be automatically converted to and from JSON in HTTP messages:

public class FruitDto {

  private String name;
  private String description;

  public FruitDto() {}

  public FruitDto(String name, String description) {
    this.name = name;
    this.description = description;
  }
  // getters and setters omitted for brevity
}

The translation to and from JSON is done automatically by the Quarkus REST (formerly RESTEasy Reactive) extension, which is included in this guide’s pom.xml file. If you want to add it manually to your application, add the below snippet to your application’s pom.xml file:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-rest-jackson</artifactId>
</dependency>
DTO classes used by the JSON serialization layer are required to have a default no-arg constructor.

The conversion from DTO to JSON is handled automatically for us, but we still must convert from Fruit to FruitDto and vice versa. This must be done manually, which is why we have two conversion methods declared in FruitResource: convertToDto and convertFromDto.

In our example, Fruit and FruitDto are very similar, so you might wonder why not use Fruit everywhere. In real life cases though, it’s not uncommon to see DTOs and entities having very different structures.

Connecting to the Cassandra Database

Connecting to Apache Cassandra or DataStax Enterprise (DSE)

The main properties to configure are: contact-points, to access the Cassandra database; local-datacenter, which is required by the driver; and – optionally – the keyspace to bind to.

示例配置应如下所示:

quarkus.cassandra.contact-points={cassandra_ip}:9042
quarkus.cassandra.local-datacenter={dc_name}
quarkus.cassandra.keyspace={keyspace}

In this example, we are using a single instance running on localhost, and the keyspace containing our data is k1:

quarkus.cassandra.contact-points=127.0.0.1:9042
quarkus.cassandra.local-datacenter=datacenter1
quarkus.cassandra.keyspace=k1

If your cluster requires plain text authentication, you must also provide two more settings: username and password.

quarkus.cassandra.auth.username=john
quarkus.cassandra.auth.password=s3cr3t

Connecting to a DataStax Astra Cloud Database

When connecting to DataStax Astra, instead of providing a contact point and a datacenter, you should provide a so-called secure connect bundle, which should point to a valid path to an Astra secure connect bundle file. You can download your secure connect bundle from the Astra web console.

You will also need to provide a username and password, since authentication is always required on Astra clusters.

A sample configuration for DataStax Astra should look like this:

quarkus.cassandra.cloud.secure-connect-bundle=/path/to/secure-connect-bundle.zip
quarkus.cassandra.auth.username=john
quarkus.cassandra.auth.password=s3cr3t
quarkus.cassandra.keyspace=k1

Advanced Driver Configuration

You can configure other Java driver settings using application.conf or application.json files. They need to be located in the classpath of your application. All settings will be passed automatically to the underlying driver configuration mechanism. Settings defined in application.properties with the quarkus.cassandra prefix will have priority over settings defined in application.conf or application.json.

To see the full list of settings, please refer to the driver settings reference.

Running a Local Cassandra Database

By default, the Cassandra client is configured to access a local Cassandra database on port 9042 (the default Cassandra port).

Make sure that the setting quarkus.cassandra.local-datacenter matches the datacenter of your Cassandra cluster.
If you don’t know the name of your local datacenter, this value can be found by running the following CQL query: SELECT data_center FROM system.local.

If you want to use Docker to run a Cassandra database, you can use the following command to launch one in the background:

docker run --name local-cassandra-instance -p 9042:9042 -d cassandra

Next you need to create the keyspace and table that will be used by your application. If you are using Docker, run the following commands:

docker exec -it local-cassandra-instance cqlsh -e "CREATE KEYSPACE IF NOT EXISTS k1 WITH replication = {'class':'SimpleStrategy', 'replication_factor':1}"
docker exec -it local-cassandra-instance cqlsh -e "CREATE TABLE IF NOT EXISTS k1.fruit(name text PRIMARY KEY, description text)"

You can also use the CQLSH utility to interactively interrogate your database:

docker exec -it local-cassandra-instance cqlsh

Testing the REST API

In the project root directory:

  • Run mvn clean package and then java -jar ./target/cassandra-quarkus-quickstart-*-runner.jar to start the application;

  • Or better yet, run the application in dev mode: mvn clean quarkus:dev.

Now you can use curl commands to interact with the underlying REST API.

To create a fruit:

curl --header "Content-Type: application/json" \
  --request POST \
  --data '{"name":"apple","description":"red and tasty"}' \
  http://localhost:8080/fruits

To retrieve fruits:

curl -X GET http://localhost:8080/fruits

Creating a Frontend

Now let’s add a simple web page to interact with our FruitResource.

Quarkus automatically serves static resources located under the META-INF/resources directory. In the src/main/resources/META-INF/resources directory, add a fruits.html file with the contents from this file in it.

现在你可以与你的REST服务进行交互:

  • If you haven’t done yet, start your application with mvn clean quarkus:dev;

  • Point your browser to http://localhost:8080/fruits.html;

  • Add new fruits to the list via the form.

Reactive Programming with the Cassandra Client

The QuarkusCqlSession interface gives you access to a series of reactive methods that integrate seamlessly with Quarkus and its reactive framework, Mutiny.

If you are not familiar with Mutiny, please check Mutiny - an intuitive reactive programming library.

Let’s rewrite our application using reactive programming with Mutiny.

First, let’s declare another DAO interface that works in a reactive way:

@Dao
public interface ReactiveFruitDao {

  @Update
  Uni<Void> updateAsync(Fruit fruit);

  @Select
  MutinyMappedReactiveResultSet<Fruit> findAll();
}

Note the usage of MutinyMappedReactiveResultSet - it is a specialized Mutiny type converted from the original Publisher returned by the driver, which also exposes a few extra methods, e.g. to obtain the query execution info. If you don’t need anything in that interface, you can also simply declare your method to return Multi: Multi<Fruit> findAll(),

Similarly, the method updateAsync returns a Uni - it is automatically converted from the original result set returned by the driver.

The Cassandra driver uses the Reactive Streams Publisher API for reactive calls. The Quarkus framework however uses Mutiny. Because of that, the CqlQuarkusSession interface transparently converts the Publisher instances returned by the driver into the reactive type Multi. CqlQuarkusSession is also capable of converting a Publisher into a Uni – in this case, the publisher is expected to emit at most one row, then complete. This is suitable for write queries (they return no rows), or for read queries guaranteed to return one row at most (count queries, for example).

Next, we need to adapt the FruitMapper to construct a ReactiveFruitDao instance:

@Mapper
public interface FruitMapper {
  // the existing method omitted

  @DaoFactory
  ReactiveFruitDao reactiveFruitDao();
}

Now, we can create a ReactiveFruitService that leverages our reactive DAO:

@ApplicationScoped
public class ReactiveFruitService {

  @Inject ReactiveFruitDao fruitDao;

  public Uni<Void> add(Fruit fruit) {
    return fruitDao.update(fruit);
  }

  public Multi<Fruit> getAll() {
    return fruitDao.findAll();
  }
}

Finally, we can create a ReactiveFruitResource:

@Path("/reactive-fruits")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ReactiveFruitResource {

  @Inject ReactiveFruitService service;

  @GET
  public Multi<FruitDto> getAll() {
    return service.getAll().map(this::convertToDto);
  }

  @POST
  public Uni<Void> add(FruitDto fruitDto) {
    return service.add(convertFromDto(fruitDto));
  }

  private FruitDto convertToDto(Fruit fruit) {
    return new FruitDto(fruit.getName(), fruit.getDescription());
  }

  private Fruit convertFromDto(FruitDto fruitDto) {
    return new Fruit(fruitDto.getName(), fruitDto.getDescription());
  }
}

The above resource is exposing a new endpoint, reactive-fruits. Its capabilities are identical to the ones that we created before with FruitResource, but everything is handled in a reactive fashion, without any blocking operation.

The getAll() method above returns Multi, and the add() method returns Uni. These types are the same Mutiny types that we met before; they are automatically recognized by the Quarkus reactive REST API, so we don’t need to convert them into JSON ourselves.

Quarkus REST natively supports the Mutiny reactive types e.g. Uni and Multi.

This dependency is already included in this guide’s pom.xml, but if you are starting a new project from scratch, make sure to include it.

Testing the Reactive REST API

Run the application in dev mode as explained above, then you can use curl commands to interact with the underlying REST API.

To create a fruit using the reactive REST endpoint:

curl --header "Content-Type: application/json" \
  --request POST \
  --data '{"name":"banana","description":"yellow and sweet"}' \
  http://localhost:8080/reactive-fruits

To retrieve fruits with the reactive REST endpoint:

curl -X GET http://localhost:8080/reactive-fruits

Creating a Reactive Frontend

Now let’s add a simple web page to interact with our ReactiveFruitResource. In the src/main/resources/META-INF/resources directory, add a reactive-fruits.html file with the contents from this file in it.

You can now interact with your reactive REST service:

健康检查

If you are using the Quarkus SmallRye Health extension, then the Cassandra client will automatically add a readiness health check to validate the connection to the Cassandra cluster. This extension is already included in this guide’s pom.xml, but if you need to include it manually in your application, add the following:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-smallrye-health</artifactId>
</dependency>

When health checks are available, you can access the /health/ready endpoint of your application and have information about the connection validation status.

Running in dev mode with mvn clean quarkus:dev, if you point your browser to http://localhost:8080/health/ready you should see an output similar to the following one:

{
    "status": "UP",
    "checks": [
        {
            "name": "DataStax Apache Cassandra Driver health check",
            "status": "UP",
            "data": {
                "cqlVersion": "3.4.4",
                "releaseVersion": "3.11.7",
                "clusterName": "Test Cluster",
                "datacenter": "datacenter1",
                "numberOfNodes": 1
            }
        }
    ]
}
If you need health checks globally enabled in your application, but don’t want to activate Cassandra health checks, you can disable Cassandra health checks by setting the quarkus.cassandra.health.enabled property to false in your application.properties.

指标

The Cassandra Quarkus client can provide metrics about the Cassandra session and about individual Cassandra nodes. It supports both Micrometer and MicroProfile.

The first step to enable metrics is to add a few additional dependencies depending on the metrics framework you plan to use.

Enabling Metrics with Micrometer

Micrometer is the recommended metrics framework in Quarkus applications.

To enable Micrometer metrics in your application, you need to add the following to your pom.xml.

<dependency>
  <groupId>com.datastax.oss</groupId>
  <artifactId>java-driver-metrics-micrometer</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-micrometer-registry-prometheus</artifactId>
</dependency>

This guide uses Micrometer, so the above dependencies are already included in this guide’s pom.xml.

Enabling Metrics with MicroProfile Metrics

Remove any dependency to Micrometer from your pom.xml, then add the following ones instead:

<dependency>
  <groupId>com.datastax.oss</groupId>
  <artifactId>java-driver-metrics-microprofile</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-smallrye-metrics</artifactId>
</dependency>

Enabling Cassandra Metrics

Even when metrics are enabled in your application, the Cassandra client will not report any metrics, unless you opt in for this feature. So your next step is to enable Cassandra metrics in your application.properties file.

quarkus.cassandra.metrics.enabled=true

That’s it!

The final (and optional) step is to customize which specific Cassandra metrics you would like the Cassandra client to track. Several metrics can be tracked; if you skip this step, a default set of useful metrics will be automatically tracked.

For the full list of available metric names, please refer to the driver settings reference page; search for the advanced.metrics section. Also, Cassandra driver metrics are covered in detail in the driver manual.

If you do wish to customize which metrics to track, you should use the following properties:

  • quarkus.cassandra.metrics.session.enabled should contain the session-level metrics to enable (metrics that are global to the session).

  • quarkus.cassandra.metrics.node.enabled should contain the node-level metrics to enable (metrics for which each node contacted by the Cassandra client gets its own metric value).

Both properties accept a comma-separated list of valid metric names.

For example, let’s assume that you wish to enable the following three Cassandra metrics:

  • Session-level: session.connected-nodes and session.bytes-sent;

  • Node-level: node.pool.open-connections.

Then you should add the following settings to your application.properties:

quarkus.cassandra.metrics.enabled=true
quarkus.cassandra.metrics.session.enabled=connected-nodes,bytes-sent
quarkus.cassandra.metrics.node.enabled=pool.open-connections

This guide’s application.properties file has already many metrics enabled; you can use its metrics list as a good starting point for exposing useful Cassandra metrics in your application.

When metrics are properly enabled, metric reports for all enabled metrics are available at the /metrics REST endpoint of your application.

Running in dev mode with mvn clean quarkus:dev, if you point your browser to http://localhost:8080/metrics you should see a list of metrics; search for metrics whose names contain cassandra.

For Cassandra metrics to show up, the Cassandra client needs to be initialized and connected; if you are using lazy initialization (see below), you won’t see any Cassandra metrics until your application actually connects and hits the database for the first time.

Running in native mode

If you installed GraalVM, you can build a native image using:

mvn clean package -Dnative

Beware that native compilation can take a significant amount of time! Once the compilation is done, you can run the native executable as follows:

./target/cassandra-quarkus-quickstart-*-runner

然后你可以使用的浏览器访问 <a href="http://localhost:8080/fruits.html" class="bare">http://localhost:8080/fruits.html</a>; 来使用你的应用程序。

Choosing between eager and lazy initialization

As explained above, this extension allows you to inject many types of beans:

  • A simple bean like QuarkusCqlSession or FruitDao;

  • The asynchronous version of that bean, for example CompletionStage<QuarkusCqlSession> or `CompletionStage<FruitDao>;

  • The reactive version of that bean, for example Uni<QuarkusCqlSession> or Uni<FruitDao>.

The most straightforward approach is obviously to inject the bean directly. This should work just fine for most applications. However, the QuarkusCqlSession bean, and all DAO beans that depend on it, might take some time to initialize before they can be used for the first time, and this process is blocking.

Fortunately, it is possible to control when the initialization should happen: the quarkus.cassandra.init.eager-init parameter determines if the QuarkusCqlSession bean should be initialized on its first access (lazy) or when the application is starting (eager). The default value of this parameter is false, meaning the init process is lazy: the QuarkusCqlSession bean will be initialized lazily on its first access – for example, when there is a first REST request that needs to interact with the Cassandra database.

Using lazy initialization speeds up your application startup time, and avoids startup failures if the Cassandra database is not available. However, it could also prove dangerous if your code is fully non-blocking, for example if it uses reactive routes. Indeed, the lazy initialization could accidentally happen on a thread that is not allowed to block, such as a Vert.x event loop thread. Therefore, setting quarkus.cassandra.init.eager-init to false and injecting QuarkusCqlSession should be avoided in these contexts.

If you want to use Vert.x (or any other non-blocking framework) and keep the lazy initialization behavior, you should instead inject only a CompletionStage or a Uni of the desired bean. When injecting these beans, the initialization process will be triggered lazily, but it will happen in the background, in a non-blocking way, leveraging the Vert.x event loop. This way you don’t risk blocking the Vert.x thread.

Alternatively, you can set quarkus.cassandra.init.eager-init to true: in this case the session bean and all DAO beans will be initialized eagerly during application startup, on the Quarkus main thread. This would eliminate any risk of blocking a Vert.x thread, at the cost of making your startup time (much) longer.

解决方案

Accessing a Cassandra database from a client application is easy with Quarkus and the Cassandra extension, which provides configuration and native support for the DataStax Java driver for Apache Cassandra.

Related content