Achieving Inheritance in NoSQL Databases With Java Using Eclipse JNoSQL
NoSQL databases provide a flexible and scalable option for storing and retrieving data in database management. However, they can need help with object-oriented programming paradigms, such as inheritance, which is a fundamental concept in languages like Java. This article explores the impedance mismatch when dealing with inheritance in NoSQL databases.
The Inheritance Challenge in NoSQL Databases
The term “impedance mismatch” refers to the disconnect between the object-oriented world of programming languages like Java and NoSQL databases’ tabular, document-oriented, or graph-based structures. One area where this mismatch is particularly evident is in handling inheritance.
In Java, inheritance allows you to create a hierarchy of classes, where a subclass inherits properties and behaviors from its parent class. This concept is deeply ingrained in Java programming and is often used to model real-world relationships. However, NoSQL databases have no joins, and the inheritance structure needs to be handled differently.
Jakarta Persistence (JPA) and Inheritance Strategies
Before diving into more advanced solutions, it’s worth mentioning that there are strategies to simulate inheritance in relational databases in the world of Jakarta Persistence (formerly known as JPA). These strategies include:
- JOINED inheritance strategy: In this approach, fields specific to a subclass are mapped to a separate table from the fields common to the parent class. A join operation is performed to instantiate the subclass when needed.
- SINGLE_TABLE inheritance strategy: This strategy uses a single table representing the entire class hierarchy. Discriminator columns are used to differentiate between different subclasses.
- TABLE_PER_CLASS inheritance strategy: Each concrete entity class in the hierarchy corresponds to its table in the database.
These strategies work well in relational databases but are not directly applicable to NoSQL databases, primarily because NoSQL databases do not support traditional joins.
Live Code Session: Java SE, Eclipse JNoSQL, and MongoDB
In this live code session, we will create a Java SE project using MongoDB as our NoSQL database. We’ll focus on managing game characters, specifically Mario and Sonic characters, using Eclipse JNoSQL. You can run MongoDB locally using Docker or in the cloud with MongoDB Atlas. We’ll start with the database setup and then proceed to the Java code implementation.
Setting Up MongoDB Locally
To run MongoDB locally, you can use Docker with the following command:
docker run -d --name mongodb-instance -p 27017:27017 mongo
Alternatively, you can choose to execute it in the cloud by following the instructions provided by MongoDB Atlas.
With the MongoDB database up and running, let’s create our Java project.
Creating the Java Project
We’ll create a Java SE project using Maven and the maven-archetype-quickstart archetype. This project will utilize the following technologies and dependencies:
- Jakarta CDI
- Jakarta JSONP
- Eclipse MicroProfile
- Eclipse JNoSQL database
Maven Dependencies
Add the following dependencies to your project’s pom.xml file:
<dependencies>
<dependency>
<groupId>org.jboss.weld.se</groupId>
<artifactId>weld-se-shaded</artifactId>
<version>${weld.se.core.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>yasson</artifactId>
<version>3.0.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.smallrye.config</groupId>
<artifactId>smallrye-config-core</artifactId>
<version>3.2.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile.config</groupId>
<artifactId>microprofile-config-api</artifactId>
<version>3.0.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jnosql.databases</groupId>
<artifactId>jnosql-mongodb</artifactId>
<version>${jnosql.version}</version>
</dependency>
<dependency>
<groupId>net.datafaker</groupId>
<artifactId>datafaker</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>
Make sure to replace ${jnosql.version}
with the appropriate version of Eclipse JNoSQL you intend to use.
In the next section, we will proceed with implementing our Java code.
Implementing Our Java Code
Our GameCharacter
class will serve as the parent class for all game characters and will hold the common attributes shared among them. We’ll use inheritance and discriminator columns to distinguish between Sonic’s and Mario’s characters. Here’s the initial definition of the GameCharacter
class:
@Entity
@DiscriminatorColumn("type")
@Inheritance
public abstract class GameCharacter {
@Id
@Convert(UUIDConverter.class)
protected UUID id;
@Column
protected String character;
@Column
protected String game;
public abstract GameType getType();
}
In this code:
- We annotate the class with
@Entity
to indicate that it is a persistent entity in our MongoDB database. - We use
@DiscriminatorColumn("type")
to specify that a discriminator column named “type” will be used to differentiate between subclasses. @Inheritance
indicates that this class is part of an inheritance hierarchy.
The GameCharacter
class has a unique identifier (id
), attributes for character name (character
) and game name (game
), and an abstract method getType()
, which its subclasses will implement to specify the character type.
Specialization Classes: Sonic and Mario
Now, let’s create the specialization classes for Sonic and Mario entities. These classes will extend the GameCharacter class and provide additional attributes specific to each character type. We’ll use @DiscriminatorValue
to define the values the “type”
discriminator column can take for each subclass.
@Entity
@DiscriminatorValue("SONIC")
public class Sonic extends GameCharacter {
@Column
private String zone;
@Override
public GameType getType() {
return GameType.SONIC;
}
}
In the Sonic class:
- We annotate it with
@Entity
to indicate it’s a persistent entity. @DiscriminatorValue("SONIC")
specifies that the“type”
discriminator column will have the value“SONIC”
for Sonic entities.- We add an attribute zone-specific to Sonic characters.
- The
getType()
method returnsGameType.SONIC
, indicating that this is a Sonic character.
@Entity
@DiscriminatorValue("MARIO")
public class Mario extends GameCharacter {
@Column
private String locations;
@Override
public GameType getType() {
return GameType.MARIO;
}
}
Similarly, in the Mario class:
- We annotate it with
@Entity
to indicate it’s a persistent entity. @DiscriminatorValue("MARIO")
specifies that the “type” discriminator column will have the value“MARIO”
for Mario entities.- We add an attribute
locations
specific to Mario characters. - The
getType()
method returnsGameType.MARIO
, indicating that this is a Mario character.
With this modeling approach, you can easily distinguish between Sonic and Mario characters in your MongoDB database using the discriminator column “type.”
We will create our first database integration with MongoDB using Eclipse JNoSQL. To simplify, we will generate data using the Data Faker library. Our Java application will insert Mario and Sonic characters into the database and perform basic operations.
Application Code
Here’s the main application code that generates and inserts data into the MongoDB database:
public class App {
public static void main(String[] args) {
try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
DocumentTemplate template = container.select(DocumentTemplate.class).get();
DataFaker faker = new DataFaker();
Mario mario = Mario.of(faker.generateMarioData());
Sonic sonic = Sonic.of(faker.generateSonicData());
// Insert Mario and Sonic characters into the database
template.insert(List.of(mario, sonic));
// Count the total number of GameCharacter documents
long count = template.count(GameCharacter.class);
System.out.println("Total of GameCharacter: " + count);
// Find all Mario characters in the database
List<Mario> marioCharacters = template.select(Mario.class).getResultList();
System.out.println("Find all Mario characters: " + marioCharacters);
// Find all Sonic characters in the database
List<Sonic> sonicCharacters = template.select(Sonic.class).getResultList();
System.out.println("Find all Sonic characters: " + sonicCharacters);
}
}
}
In this code:
- We use the
SeContainer
to manage our CDI container and initialize theDocumentTemplate
from Eclipse JNoSQL. - We create instances of Mario and Sonic characters using data generated by the
DataFaker
class. - We insert these characters into the MongoDB database using the
template.insert()
method. - We count the total number of
GameCharacter
documents in the database. - We retrieve and display all Mario and Sonic characters from the database.
Resulting Database Structure
As a result of running this code, you will see data in your MongoDB database similar to the following structure:
[
{
"_id": "39b8901c-669c-49db-ac42-c1cabdcbb6ed",
"character": "Bowser",
"game": "Super Mario Bros.",
"locations": "Mount Volbono",
"type": "MARIO"
},
{
"_id": "f60e1ada-bfd9-4da7-8228-6a7f870e3dc8",
"character": "Perfect Chaos",
"game": "Sonic Rivals 2",
"type": "SONIC",
"zone": "Emerald Hill Zone"
}
]
As shown in the database structure, each document contains a unique identifier (_id
), character name (character
), game name (game
), and a discriminator column type
to differentiate between Mario and Sonic characters. You will see more characters in your MongoDB database depending on your generated data.
This integration demonstrates how to insert, count, and retrieve game characters using Eclipse JNoSQL and MongoDB. You can extend and enhance this application to manage and manipulate your game character data as needed.
We will create repositories for managing game characters using Eclipse JNoSQL. We will have a Console repository for general game characters and a SonicRepository
specifically for Sonic characters. These repositories will allow us to interact with the database and perform various operations easily.
Let’s define the repositories for our game characters.
Console Repository
@Repository
public interface Console extends PageableRepository<GameCharacter, UUID> {
}
The Console
repository extends PageableRepository
and is used for general game characters. It provides common CRUD operations and pagination support.
Sonic Repository
@Repository
public interface SonicRepository extends PageableRepository<Sonic, UUID> {
}
The SonicRepository
extends PageableRepository
but is specifically designed for Sonic characters. It inherits common CRUD operations and pagination from the parent repository.
Main Application Code
Now, let’s modify our main application code to use these repositories.
For Console Repository
public static void main(String[] args) {
Faker faker = new Faker();
try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
Console repository = container.select(Console.class).get();
for (int index = 0; index < 5; index++) {
Mario mario = Mario.of(faker);
Sonic sonic = Sonic.of(faker);
repository.saveAll(List.of(mario, sonic));
}
long count = repository.count();
System.out.println("Total of GameCharacter: " + count);
System.out.println("Find all game characters: " + repository.findAll().toList());
}
System.exit(0);
}
In this code, we use the Console repository to save both Mario and Sonic characters, demonstrating its ability to manage general game characters.
For Sonic Repository
public static void main(String[] args) {
Faker faker = new Faker();
try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
SonicRepository repository = container.select(SonicRepository.class).get();
for (int index = 0; index < 5; index++) {
Sonic sonic = Sonic.of(faker);
repository.save(sonic);
}
long count = repository.count();
System.out.println("Total of Sonic characters: " + count);
System.out.println("Find all Sonic characters: " + repository.findAll().toList());
}
System.exit(0);
}
This code uses the SonicRepository
to save Sonic characters specifically. It showcases how to work with a repository dedicated to a particular character type.
With these repositories, you can easily manage, query, and filter game characters based on their type, simplifying the code and making it more organized.
Conclusion
In this article, we explored the seamless integration of MongoDB with Java using the Eclipse JNoSQL framework for efficient game character management. We delved into the intricacies of modeling game characters, addressing challenges related to inheritance in NoSQL databases while maintaining compatibility with Java's object-oriented principles. By employing discriminator columns, we could categorize characters and store them within the MongoDB database, creating a well-structured and extensible solution.
Through our Java application, we demonstrated how to generate sample game character data using the Data Faker library and efficiently insert it into MongoDB. We performed essential operations, such as counting the number of game characters and retrieving specific character types. Moreover, we introduced the concept of repositories in Eclipse JNoSQL, showcasing their value in simplifying data management and enabling focused queries based on character types. This article provides a solid foundation for harnessing the power of Eclipse JNoSQL and MongoDB to streamline NoSQL database interactions in Java applications, making it easier to manage and manipulate diverse data sets.
Source code