CompanyAugust 24, 2014

Introducing DataStax Java Driver 2.1

Oliver Michallat
Oliver Michallat
Introducing DataStax Java Driver 2.1

We are pleased to announce the latest version of our Java driver, released in time for Cassandra 2.1.

This release brings support for Cassandra 2.1, while remaining compatible with 1.2 and 2.0. It also introduces a new object mapping API, which simplifies the conversion of query results to custom Java classes. Finally, it includes several improvements (for a full list, refer to the changelog).

Without further ado, let's see these new features in action:

User Defined Types and tuples

As previously described on this blog, Cassandra 2.1 introduces User Defined Types, which are named groups of related properties:

1

2

3

4

5

6

7

8

9

10

CREATE TYPE address (
    street text,
    city text,
    zip int
);

 

CREATE TABLE user_profiles (
    email text PRIMARY KEY,
    address address
);

From the driver's perspective, UDT values can be retrieved like any other type. They are implemented as a map-like object, with the usual getters and setters:

1

2

3

4

5

Row row = session.execute("SELECT * FROM user_profiles").one();
UDTValue address = row.getUDTValue("address");

 

String street = address.getString("street"); // by field name
int zip       = address.getInt(2);           // by index

You can also get hold of the data type representing a particular UDT; it is useful when you want to create new values:

1

2

3

4

5

6

7

8

9

10

11

12

// Get the type from an existing value:
UserType addressType = address.getType();
// Or from the cluster metadata:
UserType addressType = cluster.getMetadata().getKeyspace("ks").getUserType("address");

 

UDTValue address2 = addressType.newValue()
                               .setString("street", "1600 Pennsylvania Ave NW")
                               .setString("city", "Washington")
                               .setInt("zip", 20500);

 

session.execute("INSERT INTO user_profiles (email, address) VALUES (?, ?)",
                "xyz@example.com", address2);

One thing that immediately comes to mind is that we'd rather use our own Address class in our code. This is exactly what the object mapper is here for, as we'll find out shortly.

Also new in Cassandra 2.1, tuples are essentially anonymous UDTs: collections of unnamed fields with predefined types.

1

2

3

4

5

CREATE TABLE points_of_interest (
    id int PRIMARY KEY,
    name text,
    coordinates tuple<float,float>
);

Querying works mostly like UDTs, except that fields can only be accessed by index:

1

2

3

4

Row row = session.execute("SELECT * FROM points_of_interest").one();
TupleValue coordinates = row.getTupleValue("coordinates");
float latitude  = coordinates.getFloat(0);
float longitude = coordinates.getFloat(1);

New tuple values can be created directly based on the types of the field:

1

2

TupleType coordinatesType = TupleType.of(DataType.cfloat(), DataType.cfloat());
TupleValue newCoordinates = coordinatesType.newValue(48.858222F, 2.2945F);

Simple object mapper

Most Java applications use custom Java classes to represent their data (for example UserProfile and Address in our first example). Converting back and forth between those classes and the driver's own types (Row and TupleValue) involves some boilerplate code and can be automated.

The goal of the object mapper is to generate most of that boilerplate for you. To specify the target tables and UDTs in Cassandra, decorate your Java classes with annotations:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

@UDT(keyspace = "ks", name = "address")
public class Address {
    private String street;
    private String city;
    private int zip;

 

    // getters and setters omitted...
}

 

@Table(keyspace = "ks", name = "user_profiles")
public class UserProfile {
    @PartitionKey
    private String email;
    private Address address;

 

    // getters and setters omitted...
}

You can then retrieve a mapper that handles basic CRUD operations (which come in both synchronous and asynchronous flavors):

1

2

3

4

5

6

MappingManager manager = new MappingManager(session);
Mapper mapper = manager.mapper(UserProfile.class);

 

UserProfile myProfile = mapper.get("xyz@example.com");
ListenableFuture saveFuture = mapper.saveAsync(anotherProfile);
mapper.delete("xyz@example.com");

For more complex queries, the mapper can also generate an "accessor" object from an interface annotated with the queries to perform:

1

2

3

4

5

6

7

8

9

10

11

12

13

@Accessor
interface ProfileAccessor {
    @Query("SELECT * FROM user_profiles LIMIT :max")
    Result firstN(@Param("max") int limit);
}

 

ProfileAccessor accessor = manager.createAccessor(ProfileAccessor.class);
Result profiles = accessor.firstN(10);

 

// Result is like ResultSet, but specialized for a mapped class:
for (UserProfile profile : profiles) {
    System.out.println(profile.getAddress().getZip());
}

The object mapper is deliberately simple: its primary goal is to replace boilerplate code, not to hide Cassandra from the developer. Therefore it avoids complex features like lazy-loading or entity proxies.

Upgrading to 2.1

This new version of the driver is available from the Maven repositories (note that the object mapper is published as a separate artifact), and as a packaged binary. Refer to the upgrade guide if you are upgrading from a previous version.

While we strive to preserve backwards-compatibility, version 2.1 introduces a few internal API changes that will be transparent to most users, and two user API changes (all documented here).

Thank you for using and supporting Apache Cassandra and DataStax.

Share

One-stop Data API for Production GenAI

Astra DB gives JavaScript developers a complete data API and out-of-the-box integrations that make it easier to build production RAG apps with high relevancy and low latency.