BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Simplifying Persistence Integration with Jakarta EE Data

Simplifying Persistence Integration with Jakarta EE Data

Key Takeaways

  • Jakarta Data simplifies data integration in Java enterprise apps, promoting polyglot persistence and flexible codebases.
  • This polyglot persistence approach allows enterprises to choose the most suitable data storage solution for each use case without sacrificing interoperability.
  • An example application demonstrates how Jakarta Data can be used for different database styles: a relational database using annotations defined in the Jakarta Persistence specification, a document-based NoSQL database utilizing annotations defined in the Jakarta NoSQL specification, and an Eclipse Store application with Jakarta NoSQL annotations.
  • Jakarta Data may be integrated with supporting Jakarta EE specifications to create more robust and scalable architectures.
  • The Repository interface, defined in Jakarta Data, may be used for Jakarta NoSQL and Jakarta Persistence applications.

In the ever-evolving world of Java enterprise applications, efficient data integration and persistence are crucial for building robust and scalable systems. The Jakarta Data specification helps with data handling. This framework simplifies data integration, supports polyglot persistence, and unifies Jakarta EE technologies. Seamlessly interacting with diverse database styles allows developers to focus on core business logic and accelerates application development. Join us to explore the capabilities of this new Jakarta EE specification, its benefits, and real-world applications in modern enterprise architectures.

It's crucial to emphasize that the specification, currently in development, still needs to be finalized and integrated within the Jakarta EE Platform. This specification, set for a GA release in November 2023, represents a promising step in data handling for Java enterprise applications. As it undergoes further development and refinement, its potential to simplify data integration, support polyglot persistence, and unify Jakarta EE technologies becomes more evident.

Our exploration of this Jakarta EE specification will provide insights into its capabilities, benefits, and real-world applications in modern enterprise architectures. Developers can anticipate leveraging its versatility and open-source nature once it achieves its final release, making it a useful tool for building cutting-edge applications, ranging from relational databases to document-based NoSQL solutions.

The Evolution of Databases and the Rise of Polyglot Persistence

The history of databases has played a pivotal role in shaping the software industry, evolving from simplistic data storage systems to sophisticated engines that drive modern applications.

In the early days, databases were expensive to operate, leading to a focus on minimizing storage costs. This emphasis gave birth to the normalization process, aimed at reducing redundancy and optimizing data storage.

As technology has advanced, storage costs have decreased, but new challenges have emerged. Today, the software industry demands fast applications with high throughput and low latency, driving the need for a diverse range of database solutions.

In the present landscape, developers are no longer confined to a one-size-fits-all approach for data storage. Instead, they can choose from various database styles that suit their application's unique requirements. This phenomenon has led to polyglot persistence, where different microservices within an application stack may adopt distinct database technologies.

Relational databases excel in handling structured data with complex relationships, NoSQL databases offer unparalleled scalability for unstructured data, and Distributed SQL, also known as NewSQL, databases combine the best of both worlds. There are now over 400 database solutions available in the market.

The plethora of database choices presents a significant challenge for application developers. How can they effectively manage and integrate data from multiple sources with unique characteristics and access patterns? This is where a unique solution emerges, offering a standardized API to effortlessly navigate the complexity of polyglot persistence. By abstracting away the intricacies of various database technologies, this solution empowers developers to focus on business logic and data modeling, unburdened by the complexities of different persistence solutions.

In the next section, we will explore how this API addresses the challenges of modern data integration, enabling enterprises to embrace polyglot persistence while ensuring seamless and efficient interactions between microservices and their diverse data sources.

Why Do We Need Jakarta Data?

In the fast-paced world of enterprise application development, seamless integration and efficient management of data persistence have become crucial factors for success. The Jakarta Data specification was introduced to address these challenges and elevate the persistence layer in Jakarta EE. This specification aims to streamline integration, enhance flexibility, and reduce cognitive load while working with multiple databases.

Figure 1: Integration, where a Java Developer can use several database engines such as SQL, NoSQL, etc

Business Perspective

In today's competitive market, businesses need agile and scalable solutions to handle diverse data requirements effectively. A standardized approach to persistence is crucial, allowing enterprises to focus on their core business logic rather than getting bogged down by complex data integration tasks. This solution simplifies development, speeds up time-to-market, and reduces maintenance costs by providing a unified way to work with various data sources.

Enable Polyglot Persistence in Enterprise Architecture

Modern applications often rely on multiple data storage solutions, such as relational databases, NoSQL databases, and cloud-based storage. Maintaining multiple persistence technologies can be challenging and lead to inconsistent data access patterns across the application stack. A solution addresses this issue by providing a vendor-neutral and extensible API, enabling developers to seamlessly work with different data stores without being tied to a specific vendor or technology. This polyglot persistence approach allows enterprises to choose the most suitable data storage solution for each use case without sacrificing interoperability.

Guarantee Isolation and Less Cognitive Load

Developers frequently encounter complexities when dealing with database systems, query languages, and data access patterns. A specification abstracts away these intricacies, providing a unified and intuitive API. By doing so, developers can focus on writing business logic without worrying about the underlying data persistence details. This abstraction fosters better code organization, minimizes the potential for errors, and enhances code maintainability. Additionally, it ensures isolation between the application logic and the persistence layer, allowing changes to one layer without impacting the other, making the development process smoother and more manageable.

The introduction of this specification marks a significant step forward in simplifying data integration in the persistence layer for Jakarta EE applications. By addressing the needs of businesses, enabling polyglot persistence, and reducing cognitive load for developers, it empowers enterprises to build robust and scalable applications efficiently. In the upcoming sections, we will delve deeper into the technical aspects of this specification and explore its implementation strategies, highlighting how it can revolutionize how we manage data in modern enterprise applications. Stay tuned for more insights on this exciting development!

The next section will examine this specification in action by demonstrating its capabilities in a real-world microservice scenario. Through a hands-on example, we will showcase how this innovative API simplifies data integration and enhances flexibility within a microservices architecture. By witnessing the seamless interaction between various microservices and their respective data sources, you will understand how this specification streamlines the persistence layer, fostering efficient and scalable enterprise application development.

Show Me the Code

This section will showcase how a specific specification simplifies data integration in a polyglot persistence environment by building three applications for a quirky beer factory, all running on Open Liberty as the Jakarta EE 10/11 provider.

Each application will represent a different database style: a relational database using annotations defined in the Jakarta Persistence specification, a document-based NoSQL database utilizing annotations defined in the Jakarta NoSQL specification with either Couchbase or MongoDB, and an Eclipse Store application with Jakarta NoSQL annotations. By leveraging this specification with Open Liberty's implementation, we can demonstrate how the applications seamlessly interact with their respective databases, handling the serialization process for NoSQL databases while benefiting from Eclipse Store's direct interaction with the object structure. This solution allows developers to effortlessly navigate the complexities of polyglot persistence, enabling smooth data management and retrieval across diverse database technologies in the modern enterprise landscape.

Figure 2: The three applications that use a relational database with PostgreSQL, Eclipse Store, and Document NoSQL with Couchbase and MongoDB

Relational Database (Jakarta Persistence Annotations):

In the first application, we will utilize a relational database, and for seamless integration, we'll employ Jakarta Persistence annotations. These annotations will allow us to map the Beer and Address classes to corresponding tables in the relational database. The chosen specification will handle data access and manipulation, including the serialization process for the relational database, where it will convert the object-oriented data into a structured format suitable for relational storage. Additionally, it will define a one-to-one relationship between each user and their respective beer, implementing the normalization process to reduce redundancy and improve data integrity.

With this approach to managing the serialization and interactions between the application and the relational database, developers can work efficiently with the familiar object-oriented paradigm while ensuring data is effectively persisted and normalized in the relational structure.

Figure 3: Jakarta Data with Jakarta Persistence serialization process

The Beer class is annotated with @Entity, signifying a Jakarta Persistence entity mapped to a database table. We define attributes like name, style, hop, yeast, and malt with @Column annotations, representing individual columns in the table. To establish a one-to-one relationship with the Address entity, we use the @OneToOne with @JoinColumn annotations to link the address_id column in the beer table to the id column in the address table. With cascade = CascadeType.ALL, changes in the Beer entity are cascaded to the related Address entity to ensure data consistency.

Similarly, the Address class is annotated with @Entity, representing another Jakarta Persistence entity mapped to a separate database table. The id field is designated as the primary key using @Id with @GeneratedValue set to GenerationType.AUTO for automatic ID generation. We use @Column annotations to map city and country attributes to individual columns in the address table. By utilizing Jakarta Persistence annotations, developers can create a one-to-one relationship between a beer and an address while leveraging normalization to ensure efficient data management and retrieval within the second application.

@Entity
public class Beer {

    @Id
    private String id;

    @Column
    private String name;

    @Column
    private String style;
    @Column
    private String hop;
    @Column
    private String yeast;
    @Column
    private String malt;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "address_id", referencedColumnName = "id")
    private Address address;

}



@Entity
@JsonbVisibility(FieldVisibilityStrategy.class)
public class Address {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column
    private String city;

    @Column
    private String country;

}

Document NoSQL Database (Jakarta NoSQL Annotations with Couchbase or MongoDB):

In the second application, we'll focus on a document-based NoSQL database, offering more flexibility for unstructured data. We can choose between Couchbase or MongoDB as the underlying NoSQL solution. Using relevant NoSQL annotations, we'll define the mappings for the Beer and Address classes, and the NoSQL database will handle the serialization process, converting the data to JSON format for storage. The chosen specification will simplify data persistence and retrieval, ensuring that the application can effortlessly interact with the selected NoSQL database, regardless of the vendor.

Figure 4: NoSQL Document and serialization flow

This application will integrate with a document-based NoSQL database using Eclipse JNoSQL, which provides seamless support for NoSQL databases like Couchbase and MongoDB. The main difference from the previous one-to-one relationship approach is that we will now model the Address class as a subdocument within the Beer class instead of maintaining a separate one-to-one relationship.

The Beer class remains annotated with @Entity in this updated model, indicating its mapping to a beer document in the NoSQL database. The Beer entity now includes the Address object as a field, annotated with @Column, representing a subdocument within the beer document. This way, the details within the address document are directly embedded within the beer document, eliminating the need for a separate address collection or one-to-one relationship.

When persisting data in a Couchbase NoSQL database, the storage structure will be in JSON format, while for MongoDB, it will be in BJSON (Binary JSON) format. Both JSON and BJSON are flexible and efficient data storage formats, making it easy to store and retrieve nested documents, such as the address subdocument within the Beer entity.

With Eclipse JNoSQL handling the integration, developers can focus on building the application's business logic without worrying about the intricacies of NoSQL database interactions. By leveraging subdocuments and flexible storage formats, Eclipse JNoSQL empowers developers to work efficiently with document-based NoSQL databases to enhance data retrieval and management within the application. This approach enables developers to embrace the unique advantages of NoSQL databases while maintaining a straightforward and effective data modeling strategy.

@Entity("beer")
public class Beer {

    @Id
    private String id;
    @Column
    private String name;

    @Column
    private String style;
    @Column
    private String hop;
    @Column
    private  String yeast;
    @Column
    private String malt;

    @Column
    private Address address;
    @Column
    private  String user;

}

@Entity
public class Address {

    @Column
    private String city;

    @Column
    private String country;

}

Eclipse Store Application (Jakarta NoSQL Annotations):

For the third application, we'll leverage the unique capabilities of Eclipse Store, a specialized NoSQL database with in-memory storage and object serialization features. Unlike the previous databases, Eclipse Store works directly with the object structure itself, eliminating the need for an additional serialization process. Relevant annotations will still be used to map the Beer and Address interfaces to Eclipse Store. However, Jakarta Data will handle interactions directly with the object structure, making data persistence and retrieval seamless and efficient. Developers can fully capitalize on Eclipse Store's in-memory storage benefits without worrying about serialization complexities.

Figure 5: Eclipse Store is the thinnest persistence layer of the three applications

In Eclipse Store by Microstream, the modeling approach for the Beer and Address entities will be similar to Eclipse JNoSQL, Couchbase, and MongoDB. However, the serialization process will significantly differ. While the document-based NoSQL databases (Couchbase and MongoDB) store data in JSON or BJSON format, Eclipse Store uniquely stores the data using the actual Java classes.

This approach reduces latency and saves computing power, eliminating the overhead associated with serialization and deserialization. Eclipse Store optimizes data storage and retrieval by working directly with the Java classes, offering fast application performance and efficient data management.

By adopting Eclipse Store, developers can enjoy the benefits of a unique and streamlined data storage strategy, making it an attractive choice for applications focusing on low latency and high performance. The modeling structure remains consistent with the other document-based NoSQL databases. At the same time, the storage format sets Eclipse Store apart as a powerful option for efficient data persistence and retrieval in the second application.

The Same Code with Resource and Repository

In all three applications, there are common architectural components that facilitate data integration and interaction with the respective databases. The provider is an adapter for the existing technologies, such as Jakarta Persistence for relational databases and Jakarta NoSQL for document-based NoSQL databases. Despite the differences in modeling and serialization, the classes themselves remain consistent across the applications.

The BeerRepository interface is shared among the three applications and extends PageableRepository. It uses the Pageable and Page interfaces, enabling pagination for data retrieval. This repository interface utilizes query-by-method, following a convention, to create queries that the provider will transparently translate into specific database queries. By leveraging annotations in the entity classes, the provider handles the mapping between the Java classes and the underlying database, making it seamless for the Java user.

@Repository
public interface BeerRepository extends PageableRepository<Beer, String> {

    Page<Beer> findByHopOrderByName(String hope, Pageable pageable);
    Page<Beer> findByMaltOrderByName(String malt, Pageable pageable);
    Page<Beer> findByMaltAndHopOrderByName(String malt, String hop, Pageable pageable);
}

In the BeerResource class, common HTTP verbs such as GET, POST, and DELETE are used to handle API interactions. While the focus is not on creating the entire API, the resource class demonstrates the capabilities of the Jakarta Persistence layer. The BeerResource class is annotated with @ApplicationScoped, @Path, @Produces, and @Consumes annotations to specify the resource endpoint, response, and request media types. The class contains methods for finding beers based on different parameters (e.g., hop, malt, or both), creating new beers, deleting beers by ID, and generating random beer data for testing purposes.

@ApplicationScoped
@Path("beers")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class BeerResource {

    private final BeerRepository repository;

    @Inject
    public BeerResource(BeerRepository repository) {
        this.repository = repository;
    }

    @Deprecated
    BeerResource() {
        this(null);
    }


    @GET
    public List<Beer> findByAll(@BeanParam BeerParam param){

        if(param.isMaltAndHopQuery()){
            return this.repository.findByMaltAndHopOrderByName(param.malt(), param.hop(), param.page()).content();
        }
        else if(param.isHopQuery()) {
            return this.repository.findByHopOrderByName(param.hop(), param.page()).content();
        }
        else if(param.isMaltQuery()) {
            return this.repository.findByMaltOrderByName(param.malt(), param.page()).content();
        }
        return this.repository.findAll(param.page().sortBy(Sort.asc("name"))).content();
    }

    @POST
    public void create(Beer beer){
        this.repository.save(beer);
    }

    @DELETE
    @Path("{id}")
    public void deleteById(@PathParam("id") String id){
        this.repository.deleteById(id);
    }

    @Path("random")
    @POST
    public void random() {
        var faker = new Faker();
        for (int index = 0; index < 1_000; index++) {
            var beer = Beer.of(faker);
            this.repository.save(beer);
        }
    }

}

The architecture layers, including the Resource and Repository, are shared across all the adapters, ensuring consistency and code reusability. The differences occur in the modeling layer, where each adapter implements the necessary annotations and serialization process for their respective databases. While isolating entities from database-specific classes could increase isolation, it may also lead to more layers and additional code. Hence, the current shared approach strikes a balance between isolation and maintainability.

Figure 6: Architecture design where a developer can isolate the entity domain without impacting the data modeling to which database engine

Overall, the provider and the shared architectural components enable developers to efficiently work with different database technologies while maintaining a consistent and standardized data integration and API interaction approach. The ability to handle diverse database styles with single classes and annotations streamlines application development and fosters seamless polyglot persistence in modern enterprise applications.

Conclusion

The introduction of this specification marks an attempt at simplifying data integration and persistence in modern Java enterprise applications. This proposed specification offers a standardized API that combines with other Jakarta EE technologies, such as Jakarta Persistence, Jakarta NoSQL, Jakarta Contexts and Dependency Injection (CDI), and Jakarta Validation. The flexibility and adaptability of this approach allow developers to embrace polyglot persistence, harnessing the strengths of various database styles while maintaining a unified codebase.

Throughout this article, we explored three applications, each utilizing a different database style, ranging from relational to document-based NoSQL databases. The capability to work with these diverse database technologies demonstrated its versatility and efficiency, streamlining data access and management for developers. By abstracting away the complexities of serialization and interaction with different databases, this approach ensures that developers can focus on their core business logic, unburdened by the intricacies of database-specific code.

One of the unique strengths of this approach is its seamless integration with other Jakarta EE specifications, fostering a cohesive and robust enterprise architecture and enabling developers to easily build robust and scalable applications, addressing various data needs.

The future of this approach looks promising, with plans for inclusion in Jakarta EE 11. As an open-source project, developers and Java enthusiasts have the opportunity to contribute to the discussion about the future of Java persistence. By actively participating in the open-source community, developers can shape the direction of this approach and Java's persistence landscape, ensuring it remains relevant and robust for the evolving needs of modern enterprise applications.

To explore further and get hands-on experience with this approach, the code samples from this article are available on our GitHub repository.

Additionally, for those interested in actively contributing or learning more about its development, project meetings, and source code, we invite you to visit the relevant specifications.

About the Author

Rate this Article

Adoption
Style

BT