Apache Cassandra With Java: Introduction to UDT
Apache Cassandra User-defined types (UDTs) can attach multiple data fields, each named and typed, to a single column. The fields used to create a UDT may be any valid data type, including collections and other existing UDTs. Once created, UDTs may be used to define a column in a table. In this post, we'll explore how to use Cassandra's feature with Java.
In simple words, UDT is a type where you can get as much information as you can; for example, give e-commerce in the column family product, there is the option to create a UDT Money where the data are the currency with the value.
CREATE TYPE commerce.money (
currency text,
amount decimal
);
It fits perfectly in the OOP model; specifically, we need to create a type or a DDD aggregate in the entity model.
Before, go further in detail on Apache Cassandra UDT, it essential to highlight that Cassandra is not a relational database. Thus, denormalization is your friend, and Cassandra does not have the support to left join.
The model should follow the query-driven modeling instead of the normalization.
This tutorial will create a Company
entity to explore the UDT feature. This Company entity has five fields:
- The company's name.
- The company's cost, where it will create a Money type.
- The languages that the company speaks.
- The company's contact in the social media.
- The headquarter, a set of a Headquarter with city and country.
Cassandra is not schemeless. Thus, it is natural to create the structure before using this. So, the script below is a CQL to create the types, money
and headquarter
, then the Company
as column family.
x
CREATE TYPE IF NOT EXISTS developers.money (currency text, amount decimal);
CREATE TYPE IF NOT EXISTS developers.headquarter (city text, country text);
CREATE COLUMNFAMILY IF NOT EXISTS developers.Company (name text PRIMARY KEY, cost FROZEN<money>, languages set<text>, contacts map<text, text>, headquarters set<FROZEN<headquarter>>);
In the Cassandra integration with Java, this tutorial uses Jakarta NoSQL once it is a standard to Jakarta EE specification.
The UDT creation isn't different from a normal entity; therefore, it will use the Entity and Column annotations, similar to JPA.
x
import jakarta.nosql.mapping.Column;
import jakarta.nosql.mapping.Entity;
import java.util.Objects;
public class Headquarter {
private String city;
private String country;
//...
}
The Money
types have two fields one represents the currency and the amount. Java has a specific kind to describes coin, the Currency class. Once Cassandra does not support it, we'll create a convert to move this class data to String.
xxxxxxxxxx
public class Money {
MoneyConverter.class) (
private Currency currency;
private BigDecimal amount;
//...
}
public class MoneyConverter implements AttributeConverter<Currency, String> {
public String convertToDatabaseColumn(Currency attribute) {
return attribute.getCurrencyCode();
}
public Currency convertToEntityAttribute(String dbData) {
return Currency.getAvailableCurrencies().stream()
.filter(c -> dbData.equals(c.getCurrencyCode()))
.findAny().orElse(null);
}
}
The types of models are ready to use in the Company class. This entity will have trivial annotations such as Entity
, Id
, and Column
. Furthermore, there is an annotation to identify whose that entity is a UDT. The UDT annotation requires an attribute to define the type name.
xxxxxxxxxx
public class Company {
"name") (
private String name;
"money") (
private Money cost;
private Set<String> languages;
private Map<String, String> contacts;
"headquarter") (
private Set<Headquarter> headquarters;
//...
}
The Company
class is ready to use. The next step is storage and retrieves information using the CassandraTemplate
. As with any service, make sure that there is a Cassandra instance running. The easiest way is with docker with the command below:
xxxxxxxxxx
docker run -d --name casandra-instance -p 9042:9042 cassandra
x
public class App5 {
public static void main(String[] args) {
try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
CassandraTemplate template = container.select(CassandraTemplate.class).get();
Currency currency = Currency.getInstance(Locale.US);
Company company = Company.builder()
.withName("SouJava")
.addLanguage("Portuguese")
.addLanguage("English")
.addLanguage("Italian")
.addLanguage("Spanish")
.addHeadquarter(Headquarter.of("Salvador", "Brazil"))
.addHeadquarter(Headquarter.of("Sao Paulo", "Brazil"))
.addHeadquarter(Headquarter.of("Leiria", "Portugal"))
.add("twitter", "otaviojava")
.add("linkedin", "otaviojava")
.withCost(Money.of(currency, BigDecimal.valueOf(10_000)))
.build();
template.insert(company);
Optional<Company> soujava = template.find(Company.class, "SouJava");
System.out.println("the company is " + soujava);
}
}
private App5() {
}
}
In this tutorial, we talked about Cassandra's UDT feature and how to use it as a collection or a single field with Jakarta NoSQL.
Source: https://github.com/JNOSQL/demos/tree/master/artemis-demo-java-se/cassandra