Introduction to Polymorphism With Database Engines in NoSQL Using Jakarta NoSQL

Polymorphism, a fundamental concept in object-oriented programming, allows objects of different types to be treated as instances of a common superclass. This flexibility is essential for creating systems that can be easily extended and maintained. While traditional SQL databases in combination with Jakarta Persistence (JPA) can handle polymorphic data, NoSQL databases offer distinct advantages. Unlike SQL databases, which require strict schema definitions, NoSQL databases take a schema-less approach, inherently supporting dynamic and flexible data structures. This flexibility becomes especially appealing when integrated with Jakarta NoSQL, a tool that provides robust support for defining and managing polymorphic fields through custom converters.

In many enterprise applications, there is a common need to manage different types of data objects. For example, an e-commerce platform may handle various payment methods such as credit cards, digital wallets, and bank transfers, each with specific attributes. Similarly, asset management systems in large corporations deal with different types of assets like real estate, machinery, and intellectual property, each with unique properties. Healthcare systems must accommodate various data types, from personal information to medical records and test results. Utilizing NoSQL databases with polymorphic fields can store and manage these diverse data types cohesively. The schema-less nature of NoSQL databases also makes it easier to adapt to changing requirements than relational databases.

This tutorial will show how to use Jakarta NoSQL to manage polymorphic fields using custom converters. We will include sample code for integrating REST services using Helidon and Oracle NoSQL. This example will demonstrate the practical application of polymorphism in efficiently managing various data types in a schema-less NoSQL database environment.

Using Polymorphism With NoSQL and Jakarta NoSQL

This tutorial will explore the NoSQL and schema-less capabilities in the Java world using Oracle NoSQL, Java Helidon, and Rest API. We will create a Machine entity where it will provide an engine field that we will convert to JSON. Thanks to the natural flexibility of Oracle NoSQL and its schema-less design, this approach works seamlessly.

The first step is creating the Helidon project, where you can use the Helidon Starter: Helidon Starter.

After creating a Microprofile-compliant project, the next step is to include the Oracle NoSQL driver: Oracle NoSQL Driver.

In the properties file, since we will run locally, we need two properties: one to define the host connection and the second to define the database name:

Properties files

 

jnosql.document.database=machines
jnosql.oracle.nosql.host=http://localhost:8080

Also, update the port to use 8181:

Properties files

 

server.port=8181

The next step is to configure and run Oracle NoSQL. To make it easier, we will use Docker, but it is important to note that Oracle NoSQL also supports cloud deployment on Oracle infrastructure:

Shell

 

docker run -d --name oracle-instance -p 8080:8080 ghcr.io/oracle/nosql:latest-ce

By using Docker, we simplify the setup process and ensure that our Oracle NoSQL instance is running in a controlled environment. This setup provides a practical approach for development and testing purposes, highlighting the flexibility of deploying Oracle NoSQL in different environments, including cloud infrastructure.

After setting up the configuration and database, the next step involves defining the Entity and creating the Converter implementation. In this example, we will demonstrate the seamless integration of Jakarta NoSQL and Jakarta JSON-B, showing how different Jakarta specifications can work together effectively.

The initial step is to define the Machine entity, which incorporates a polymorphic Engine field.

Java

 


@Entity
@JsonbVisibility(FieldAccessStrategy.class)
public class Machine {

    @Id
    private String id;

    @Column
    @Convert(EngineConverter.class)
    private Engine engine;

    @Column
    private String manufacturer;

    @Column
    private int year;

    // Getters and setters
}

Next, we define the Engine class, specifying the types and implementations using JsonbTypeInfo to handle polymorphism:

Java

 

@JsonbTypeInfo(
        key = "type",
        value = {
                @JsonbSubtype(alias = "gas", type = GasEngine.class),
                @JsonbSubtype(alias = "electric", type = ElectricEngine.class)
        }
)
@JsonbVisibility(FieldAccessStrategy.class)
public abstract class Engine {
    // Common engine attributes

    // Getters and setters
}

The engine converter might change by the provider; it can work as String, Map<String, Object>, BSON, etc.

After setting up the configuration and database, the next step is to create the application and database bridge. Eclipse JNoSQL integrates two specifications, Jakarta NoSQL and Jakarta Data, using a single interface to achieve this. Annotations handle the necessary steps.

First, we define the MachineRepository interface:

Java

 

@Repository
public interface MachineRepository extends BasicRepository<Machine, String> {

    @Query("from Machine where engine.type = :type")
    List<Machine> findByType(@Param("type") String type);
}

This repository interface allows us to search elements directly within the JSON data without any issues, and you can extend it further without creating a rigid structure. 

Next, we define the controller to expose this resource:

Java

 


@Path("/machines")
@ApplicationScoped
public class MachineResource {

    private static final Logger LOGGER = Logger.getLogger(MachineResource.class.getName());

    public static final Order<Machine> ORDER_MANUFACTURER = Order.by(Sort.asc("manufacturer"));

    private final MachineRepository repository;

    @Inject
    public MachineResource(@Database(DatabaseType.DOCUMENT) MachineRepository repository) {
        this.repository = repository;
    }

    @GET
    public List<Machine> getMachines(@QueryParam("page") @DefaultValue("1") int page, @QueryParam("page_size") @DefaultValue("10") int pageSize) {
        LOGGER.info("Get machines from page " + page + " with page size " + pageSize);
        Page<Machine> machines = this.repository.findAll(PageRequest.ofPage(page).size(pageSize), ORDER_MANUFACTURER);
        return machines.content();
    }

    @GET
    @Path("gas")
    public List<Machine> getGasMachines() {
        return this.repository.findByType("gas");
    }

    @GET
    @Path("electric")
    public List<Machine> getElectricMachines() {
        return this.repository.findByType("electric");
    }

    @GET
    @Path("{id}")
    public Machine get(@PathParam("id") String id) {
        LOGGER.info("Get machine by id " + id);
        return this.repository.findById(id)
                .orElseThrow(() -> new WebApplicationException("Machine not found with id: " + id, Response.Status.NOT_FOUND));
    }

    @PUT
    public void save(Machine machine) {
        LOGGER.info("Saving a machine " + machine);
        this.repository.save(machine);
    }
}

This controller exposes endpoints for managing machines, including getting machines by type and pagination.

Finally, execute the application and insert data using the following curl commands:

Shell

 

curl --location --request PUT 'http://localhost:8181/machines' \
--header 'Content-Type: application/json' \
--data '{
"id": "1",
"model": "Thunderbolt V8",
"engine": {
"type": "gas",
"horsepower": 450
},
"manufacturer": "Mustang",
"year": 2021,
"weight": 1600.0
}'

curl --location --request PUT 'http://localhost:8181/machines' \
--header 'Content-Type: application/json' \
--data '{
    "id": "2",
    "model": "Eagle Eye EV",
    "engine": {
        "type": "electric",
        "horsepower": 300
    },
    "manufacturer": "Tesla",
    "year": 2022,
    "weight": 1400.0
}'

curl --location --request PUT 'http://localhost:8181/machines' \
--header 'Content-Type: application/json' \
--data '{
    "id": "3",
    "model": "Road Runner GT",
    "engine": {
        "type": "gas",
        "horsepower": 400
    },
    "manufacturer": "Chevrolet",
    "year": 2020,
    "weight": 1700.0
}'

curl --location --request PUT 'http://localhost:8181/machines' \
--header 'Content-Type: application/json' \
--data '{
    "id": "4",
    "model": "Solaris X",
    "engine": {
        "type": "electric",
        "horsepower": 350
    },
    "manufacturer": "Nissan",
    "year": 2023,
    "weight": 1350.0
}'

curl --location --request PUT 'http://localhost:8181/machines' \
--header 'Content-Type: application/json' \
--data '{
    "id": "5",
    "model": "Fusion Hybrid 2024",
    "engine": {
        "type": "electric",
        "horsepower": 320
    },
    "manufacturer": "Toyota",
    "year": 2024,
    "weight": 1450.0
}'

This setup allows you to search by type within the JSON, and pagination is easily implemented. For more on pagination techniques, refer to this article.

With some data inserted, you can explore and understand how the search works:

  • Get machines with pagination
  • Get all machines
  • Get gas machines

Final Thoughts

Integrating polymorphism with NoSQL databases using Jakarta NoSQL and Jakarta JSON-B offers flexibility and efficiency in managing diverse data types. By leveraging NoSQL’s schema-less nature, this approach simplifies development and enhances application adaptability. For the complete example and source code, visit soujava/helidon-oracle-json-types.

Source:
https://dzone.com/articles/intro-to-polymorphism-with-db-engines-in-nosql