ArangoDB: Achieving Success With a Multivalue Database
ArangoDB's multimodel capabilities simplify handling key-value, document, and graph data in one database. Jakarta NoSQL enables seamless integration.
Join the DZone community and get the full member experience.
Join For FreeHandling diverse database structures often introduces significant complexity to system architecture, especially when multiple database instances are required. This fragmentation can complicate operations, increase costs, and reduce efficiency. Multimodel databases like ArangoDB provide a unified solution to address these challenges. They simplify architecture and streamline data management by supporting multiple data models — key-value, document, and graph — within a single database instance.
Unlike relational databases, NoSQL databases do not adhere to a universal standard like SQL. Instead, they are categorized based on their storage structure. Among the popular types are:
- Key-value: Resembling a Java
Map
or a Python dictionary, this structure retrieves entire values as BLOBs using a key. - Wide-column: Similar to key-value but splits values into columns, offering more granular data retrieval.
- Document: Structured like JSON or XML, this type provides greater query flexibility.
- Graph: Enables complex relationship modeling and querying by representing entities and their connections.
A multimodel database combines these capabilities into a single system. For instance, ArangoDB supports key-value, document, and graph models, eliminating the need for separate databases.
This article demonstrates how to use ArangoDB to explore key-value and document models in Java applications using Jakarta NoSQL.
Setting Up ArangoDB
To start with ArangoDB, Docker provides a straightforward way to manage third-party services. By running the following command, you can set up an ArangoDB instance with ease:
docker run -e ARANGO_NO_AUTH=1 -d --name arangodb-instance -p 8529:8529 arangodb/arangodb
Exploring Key-Value Data
Key-value databases are ideal for simple data models. Let’s create a sample application to manage airport data using ArangoDB’s key-value capabilities. The Airport
entity will include two fields: code
(ID) and name
.
import jakarta.nosql.Column;
import jakarta.nosql.Entity;
import jakarta.nosql.Id;
import net.datafaker.Faker;
import net.datafaker.providers.base.Aviation;
import java.util.Objects;
@Entity
public class Airport {
@Id
private String code;
@Column
private String name;
public String getCode() {
return code;
}
public String getName() {
return name;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}
Airport airport = (Airport) o;
return Objects.equals(code, airport.code);
}
@Override
public int hashCode() {
return Objects.hashCode(code);
}
@Override
public String toString() {
return "Airport{" +
"code='" + code + '\'' +
", name='" + name + '\'' +
'}';
}
public static Airport of(Faker faker) {
Aviation aviation = faker.aviation();
var airport = new Airport();
airport.code = aviation.airport();
airport.name = aviation.airport();
return airport;
}
}
With the entity defined, you can use it KeyValueTemplate
to interact with the database. In this tutorial, we will explore more of the Jakarta NoSQL capability beyond the annotations using Eclipse JNoSQL; you can create the repository once Eclipse JNoSQL implements and supports Jakarta Data.
import jakarta.enterprise.inject.se.SeContainer;
import jakarta.enterprise.inject.se.SeContainerInitializer;
import net.datafaker.Faker;
import org.eclipse.jnosql.mapping.keyvalue.KeyValueTemplate;
public class App {
public static void main(String[] args) {
var faker = new Faker();
try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
KeyValueTemplate template = container.select(KeyValueTemplate.class).get();
var airport = template.put(Airport.of(faker));
System.out.println(template.get(airport.getCode(), Airport.class));
}
}
}
This program generates a random airport record, inserts it into the database, and retrieves it. If you are running locally, you can check the values in the database using your browse with the URL:
http://localhost:8529/_db/airport/_admin/aardvark/index.html#collections
Working With Document Data
Jakarta NoSQL provides rich features for working with document databases, including inheritance and hierarchical data modeling support. While NoSQL databases generally do not inherently support these features, Jakarta NoSQL bridges this gap with its API. Let’s use Jakarta NoSQL with ArangoDB to manage cloud provider data, including two specializations: Amazon Web Services (AWS) and Azure.
Jakarta NoSQL uses the @DiscriminatorColumn
, @DiscriminatorValue
, and @Inheritance
annotations to manage inheritance in document databases.
- The
@DiscriminatorColumn
annotation specifies the field used to identify an entity type in the database document. - The
@DiscriminatorValue
annotation defines the specific value for each subclass, ensuring they are correctly identified. - The
@Inheritance
annotation indicates that the class hierarchy will be mapped into the database.
First, define the base class for cloud providers:
@Entity
@Inheritance
@DiscriminatorColumn("type")
public class CloudProvider {
@Id
protected String id;
@Column
protected String region;
}
Next, we introduce the specialized cloud provider classes. These examples showcase how Jakarta NoSQL leverages annotations @DiscriminatorValue
to distinguish between different document types. Each specialization — AWS and Azure — inherits from the base CloudProvider
class, while also defining attributes unique to each provider. Additionally, factory methods (of
) are provided to generate sample data for demonstration purposes.
AWSCloudProvider
import jakarta.nosql.Column;
import jakarta.nosql.DiscriminatorValue;
import jakarta.nosql.Entity;
import net.datafaker.Faker;
import java.util.UUID;
@Entity
@DiscriminatorValue("AWS")
public class AWSCloudProvider extends CloudProvider {
@Column
private String accountId;
public String getAccountId() {
return accountId;
}
@Override
public String toString() {
return "AWSCloudProvider{" +
"accountId='" + accountId + '\'' +
", id='" + id + '\'' +
", region='" + region + '\'' +
'}';
}
public static AWSCloudProvider of(Faker faker) {
var aws = faker.aws();
var cloudProvider = new AWSCloudProvider();
cloudProvider.region = aws.region();
cloudProvider.region = aws.region();
cloudProvider.id = UUID.randomUUID().toString();
cloudProvider.accountId = aws.accountId();
return cloudProvider;
}
}
AzureCloudProvider
package org.soujava.demos.arangodb.document;
import jakarta.nosql.Column;
import jakarta.nosql.DiscriminatorValue;
import jakarta.nosql.Entity;
import net.datafaker.Faker;
import java.util.UUID;
@Entity
@DiscriminatorValue("AZURE")
public class AzureCloudProvider extends CloudProvider {
@Column
private String tenantId;
public String getTenantId() {
return tenantId;
}
@Override
public String toString() {
return "AzureCloudProvider{" +
"tenantId='" + tenantId + '\'' +
", id='" + id + '\'' +
", region='" + region + '\'' +
'}';
}
public static AzureCloudProvider of(Faker faker) {
var azure = faker.azure();
var cloudProvider = new AzureCloudProvider();
cloudProvider.region = azure.region();
cloudProvider.region = azure.region();
cloudProvider.id = UUID.randomUUID().toString();
cloudProvider.tenantId = azure.tenantId();
return cloudProvider;
}
}
Finally, let’s see how to use the DocumentTemplate
to interact with the database. This code demonstrates creating instances of AWS and Azure providers, inserting them into the database, and retrieving them. The implementation leverages Jakarta NoSQL’s API to handle data persistence, ensuring that inheritance and discriminator logic are applied seamlessly during storage and retrieval.
import jakarta.enterprise.inject.se.SeContainer;
import jakarta.enterprise.inject.se.SeContainerInitializer;
import net.datafaker.Faker;
import org.eclipse.jnosql.mapping.document.DocumentTemplate;
import java.util.List;
import java.util.logging.Logger;
public class App {
private static final Logger LOGGER = Logger.getLogger(App.class.getName());
public static void main(String[] args) {
var faker = new Faker();
LOGGER.info("Starting the application");
try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
var template = container.select(DocumentTemplate.class).get();
LOGGER.info("Creating 10 documents");
for (int index = 0; index < 5; index++) {
template.insert(List.of(AWSCloudProvider.of(faker), AzureCloudProvider.of(faker)));
}
System.out.println("The cloud providers here");
template.select(CloudProvider.class).stream().forEach(System.out::println);
System.out.println("The AWS cloud providers here");
template.select(AWSCloudProvider.class).stream().forEach(System.out::println);
System.out.println("The Azure cloud providers here");
template.select(AzureCloudProvider.class).stream().forEach(System.out::println);
}
}
private App() {
}
}
Conclusion
ArangoDB’s multimodel capabilities make it a versatile choice for modern applications. Combining key-value and document models in a single database simplifies your data architecture while retaining flexibility. With Jakarta NoSQL, integrating ArangoDB into Java applications becomes seamless, leveraging familiar annotations and programming paradigms.
For the complete code, visit the GitHub Repository.
Opinions expressed by DZone contributors are their own.
Comments