Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation, Jakarta Data 1.0 #7468

Open
1 of 2 tasks
njr-11 opened this issue Aug 14, 2024 · 0 comments
Open
1 of 2 tasks

Documentation, Jakarta Data 1.0 #7468

njr-11 opened this issue Aug 14, 2024 · 0 comments
Assignees

Comments

@njr-11
Copy link

njr-11 commented Aug 14, 2024

Feature epic details

Operating systems

Does the documentation apply to all operating systems?

  • Yes
  • No; specify operating systems: ______

Main page


Jakarta Data

Overview of Jakarta Data

Jakarta Data is a domain-driven model for relational and non-relational data access,
where you define simple Java objects to represent data and you define interfaces for operations on data.
You inject these interfaces into your application and use them to access data.
The implementation is supplied for you by a Jakarta Data Provider.

Concepts in Jakarta Data

Jakarta Data simplifies data access by allowing you to

  • represent data with simple Java objects (entities), and
  • define interfaces (repositories) with methods that perform operations on entities.

Jakarta Data Providers

Jakarta Data Providers supply an implementation of a repository, which can be injected into applications via CDI (Contexts and Dependency Injection).
Jakarta Data Providers can be third-party products or can be provided by the application server.
Open Liberty includes a built-in Jakarta Data Provider for relational database access
that is backed by the built-in Jakarta Persistence Provider, EclipseLink.

Some third-party Jakarta Data providers include Hibernate ORM (for relational data)and
Eclipse JNoSQL (for various types of non-relational data).

Entity Model

Jakarta Data reuses the entity models of Jakarta Persistence
and Jakarta NoSQL.

  • Jakarta Persistence entities for relational data.
  • Jakarta NoSQL entities for non-relational (NoSQL) data.

Jakarta Data Providers might define additional entity models, possibly by defining their own entity annotations,
or by defining conventions for supporting unannotated entities, or by some other vendor-specific model.

Jakarta Persistence Entity Example

@Entity
public class Car {
    @Id
    public String vin; 

    public String make;

    public String model;

    public int modelYear;

    public int odometer;

    public float price;
}

Static Metamodel

An entity can optionally have a static metamodel,
typically generated from the entity class by development tooling, which can be used by the repository interface
as well as the application in defining and invoking data access operations in a more type-safe manner.

By convention, static metamodel interface classes typically begin with the underscore (_) character, and is followed by the entity class name.

Static Metamodel Example

@StaticMetamodel(Car.class)
public interface _Car {
    String MAKE = "make";
    String MODEL = "model";
    String MODELYEAR = "modelYear";
    String ODOMETER = "odometer";
    String PRICE = "price";
    String VIN = "vin";

    TextAttribute<Car> make = new TextAttributeRecord<>(MAKE);
    TextAttribute<Car> model = new TextAttributeRecord<>(MODEL);
    SortableAttribute<Car> modelYear = new SortableAttributeRecord<>(MODELYEAR);
    SortableAttribute<Car> odometer = new SortableAttributeRecord<>(ODOMETER);
    SortableAttribute<Car> price = new SortableAttributeRecord<>(PRICE);
    TextAttribute<Car> vin = new TextAttributeRecord<>(VIN);
}

Repositories

Repository interfaces must be annotated with the Repository
annotation and can optionally inherit from any of the following repository supertypes that are defined by Jakarta Data:

The repository supertypes offer a variety of pre-defined methods for commonly-used data access operations.
The first type parameter to the repository supertypes specifies a primary Entity class that can be assumed for repository methods that do not otherwise specify an Entity class.
The second type parameter to the repository supertypes specifies the type of the Id attribute that can be assumed for repository methods that do not otherwise specify an Id type.

Repository Interface Example

@Repository
public interface Cars extends BasicRepository<Car, String> {
    @Insert
    Car add(Car car);

    @OrderBy(_Car.ODOMETER)
    @OrderBy(_Car.VIN)
    List<Car> findByModelYearGreaterThanEqualAndPriceBetween(int minYear,
                                                             float minPrice,
                                                             float maxPrice);

    @Find
    Page<Car> search(@By(_Car.MAKE) String manufacturer,
                     @By(_Car.MODEL) String model,
                     @By(_Car.MODELYEAR) int year,
                     PageRequest pageRequest,
                     Order<Car> sortBy);

    @Query("UPDATE Car SET price = ?2 WHERE vin = ?1")
    boolean setPrice(String vehicleIdNum, float newPrice);

    @Query("SELECT price FROM Car WHERE vin = :vehicleId AND price > 0")
    Optional<Float> getPrice(@Param("vehicleId") String vin);

    @Delete
    boolean remove(@By(ID) String vehicleIdNum);
}

Repository Interface Usage Example

@Path("/cars")
@ApplicationScoped
public class ExampleResource {
    @Inject
    Cars cars;

    @GET
    @Path("/make/{make}/model/{model}/year/{year}/page/{pageNum}")
    @Produces(MediaType.TEXT_PLAIN)
    public String search(
            @PathParam("make") String make,
            @PathParam("model") String model,
            @PathParam("year") int year,
            @PathParam("pageNum") long pageNum) {

        PageRequest pageRequest = PageRequest.ofPage(pageNum).size(10);

        Order<Car> mostToLeastExpensive = Order.by(
                _Car.price.desc(),
                _Car.vin.asc());

        Page<Car> page = cars.search(make, model, year, 
                                     pageRequest, 
                                     mostToLeastExpensive);

        return page.stream()
                .map(c -> c.modelYear + " " + c.make + " " + c.model + " " +
                          c.odometer + " miles $" + c.price + " #" + c.vin)
                .collect(Collectors.toList())
                .toString();
    }
}

Linking a Repository to a Provider

When there is only one Jakarta Data Provider that handles the type of entity, it is unnecessary for the Repository to specify which Jakarta Data Provider to use.

However, when multiple Jakarta Data providers coexist in a system and handle the same entity type,
the Repository must specify a provider name to disambiguate which Jakarta Data Provider is to be used.

The built-in Jakarta Data Provider for Liberty uses the provider name, Liberty.
It is included in the data-1.0 feature

Third-party Jakarta Data Providers can be used in Liberty by alternatively including the
dataContainer-1.0 feature
which will only provide the Jakarta Data API (without the build-in Jakarta Data Provider).

Hibernate ORM Provider Name Example

@Repository(provider = "Hibernate", dataStore = "MyPersistenceUnit")
public interface Employee extends CrudRepository<Employee, Long> {
}

Eclipse JNoSQL Provider Name Example

@Repository(provider = "Eclipse JNoSQL") //TODO update https://github.com/eclipse/jnosql/pull/561
public interface Cars extends BasicRepository<Car, String> {
}

Built-in Provider Name Example

@Repository(provider = "Liberty", dataStore = "java:app/env/jdbc/MyDataSourceRef")
public interface Products extends CrudRepository<Product, String> {
}

Subpage of Jakarta Data


Built-in Jakarta Data Provider

Overview of Built-in Jakarta Data Provider

Open Liberty includes a built-in Jakarta Data Provider for relational database access that is backed by the built-in Jakarta Persistence Provider, EclipseLink.
The built-in provider is available when you enable the data-1.0 feature
alongside the persistence-3.2 feature
or the jdbc-4.3 feature
or the jdbc-4.2 feature.

Entity Model

The built-in provider accepts Jakarta Persistence entities or Java Records as entities.

Provider Name

When multiple Jakarta Data providers supporting the Jakarta Persistence entities coexist in the system,
use the provider name of Liberty to explicitly request the built-in Jakarta Data Provider.

@Repository(provider = "Liberty")
public interface Products extends BasicRepository<Product, String> {
}

Choosing the Database

The built-in Jakarta Data Provider, which uses the built-in Jakarta Persistence Provider, EclipseLink, can connect to the same databases that EclipseLink supports.
Individual capabilities within Jakarta Data and the entity model are limited by the extent to which EcilpseLink supports each database.

The Default Database

If you do not specify otherwise, you end up with the Jakarta EE Default DataSource, which is java:comp/DefaultDataSource, as the data store for your repository.
The Jakarta EE Default DataSource is not configured by default in Liberty.
To configure it, include configuration of a <dataSource> with id of DefaultDataSource in your server configuration.

Repository interface:

@Repository //Default dataStore = "java:comp/DefaultDataSource"
public interface Cars extends BasicRepository<Cars, String> {
  // Query methods
}

Server configuration:

<dataSource id="DefaultDataSource">
  <jdbcDriver libraryRef="PostgresLib"/>
  <properties.postgresql databaseName="exampledb" serverName="localhost" portNumber="5432"
    user="${env.DB_USER}" password="${env.DB_PASS}"/>
</dataSource>

<library id="PostgresLib">
  <fileset dir="${server.config.dir}/lib/postgres" includes="*.jar"/>
</library>

EclipseLink will attempt to create the necessary database tables automatically, which is convenient for development,
but is not efficient for running in production because checking for table existence incurs a lot of overhead and
database admin privileges might be required for table creation.

Refer to the sections on Specifying a DatabaseStore and Specifying a Persistence Unit Reference
for information about how to control the table creation settings.

Specifying a DataSource

To specify which DataSource a repository will use, set the dataStore attribute of the @Repository annotation to one of:

  • JNDI name of a resource reference to a DataSource. For example, java:app/env/jdbc/MyDataSourceRef.
  • JNDI name of a DataSource. This could be from the name value of a @DataSourceDefinition or from the jndiName value of a dataSource from server configuration.
  • The id of a dataSource from server configuration.

When using JNDI names or resource reference names that are scoped to a namespace like java:comp or java:module,
you must ensure that the scope is accessible to the location of the repository interface within the application.
For example, a DataSource in the java:comp scope of an EJB is never accessible to a repository interface
because although a repository interface might exist in an EJB module, the repository interface is not part of any specific EJB.

A good practice when using scoped names is to use java:app to allow the repository interface to be defined in any module of the application.

EclipseLink will attempt to create the necessary database tables automatically, which is convenient for development,
but is not efficient for running in production because checking for table existence incurs a lot of overhead and database admin privileges might be required for table creation.

Refer to the sections on Specifying a DatabaseStore and Specifying a Persistence Unit Reference
for information about how to control the table creation settings.

DataSource id Example

This example shows a repository interface that points to the id of a <dataSource> in server configuration.

Repository interface:

@Repository(dataStore = "ExampleDataSourceID")
public interface Cars extends BasicRepository<Cars, String> {
    // Query methods
}

Server configuration:

<dataSource id="ExampleDataSourceID">
  <jdbcDriver libraryRef="OracleLib"/>
  <properties.oracle URL="jdbc:oracle:thin:@localhost:1521:exampledb"
    user="${env.DB_USER}" password="${env.DB_PASS}"/>
</dataSource>

<library id="OracleLib">
  <fileset dir="${server.config.dir}/lib/ojdbc" includes="*.jar"/>
</library>

DataSource jndiName Example

This example shows a repository interface that points to the jndiName of a <dataSource> in server configuration.

Repository interface:

@Repository(dataStore = "jdbc/ExampleDataSource")
public interface Cars extends BasicRepository<Car, String> {
    // Query methods
}

Server configuration:

<dataSource jndiName="jdbc/ExampleDataSource">
  <jdbcDriver libraryRef="DB2Lib"/>
  <properties.db2.jcc databaseName="exampledb" serverName="localhost" portNumber="50000"
    user="${env.DB_USER}" password="${env.DB_PASS}"/>
</dataSource>

<library id="DB2Lib">
  <fileset dir="${server.config.dir}/lib/jcc" includes="*.jar"/>
</library>

DataSource Resource Reference Example

This example shows a repository interface that points to a resource reference to a <dataSource> in server configuration.

Repository interface:

@Repository(dataStore = "java:app/env/jdbc/ExampleDataSourceRef")
public interface Cars extends BasicRepository<Car, String> {
    // Query methods
}

Jakarta EE component (defines the resource reference):

@ApplicationScoped
@Path("/cars")
public class ExampleResource {
    @Resource(name = "java:app/env/jdbc/ExampleDataSourceRef",
              lookup = "jdbc/ExampleDataSource")
    DataSource ds;

    @Inject
    Cars cars;

    // ...
}

Server configuration:

<dataSource jndiName="jdbc/ExampleDataSource">
  <jdbcDriver libraryRef="SQLServerLib"/>
  <properties.microsoft.sqlserver databaseName="exampledb" serverName="localhost" portNumber="1433"/>
  <containerAuthData user="${env.DB_USER}" password="${env.DB_PASS}"/>
</dataSource>

<library id="SQLServerLib">
  <fileset dir="${server.config.dir}/lib/sqlserver" includes="*.jar"/>
</library>

DataSourceDefinition Example

This example shows a repository interface that points to the name of a DataSourceDefinition.

Repository interface:

@Repository(dataStore = "java:comp/jdbc/ExampleDataSourceDef")
public interface Cars extends BasicRepository<Car, String> {
    // Query methods
}

Web component or EJB component (defines the DataSourceDefinition):

@ApplicationScoped
@DataSourceDefinition(name = "java:comp/jdbc/ExampleDataSourceDef",
                      className = "org.apache.derby.jdbc.EmbeddedXADataSource",
                      databaseName = "memory:exampledb",
                      user = "${env.DB_USER}",
                      password = "${env.DB_PASS}",
                      properties = "createDatabase=create")
@Path("/cars")
public class ExampleResource {
    @Inject
    Cars cars;

    // ...
}

Specifying a Persistence Unit Reference

Defining a Persistence Unit gives you more control over the Jakarta Persistence configuration and EcilpseLink specific settings.
To specify which Persistence Unit a repository will use, set the dataStore attribute of the Repository annotation to a Persistence Unit reference.

The following sections provide an example of configuring and using a Persistence Unit reference.

Repository interface

@Repository(dataStore = "java:app/env/persistence/ExamplePersistenceUnitRef")
public interface Cars extends BasicRepository<Car, String> {
    // Query methods
}

Jakarta EE component

The persistence unit reference can be defined in a Jakarta EE component. This example also defines a data source resource reference which is used by the persistence unit.

@ApplicationScoped
@Path("/cars")
public class ExampleResource {
    @PersistenceUnit(name = "java:app/env/persistence/ExamplePersistenceUnitRef",
                     unitName = "ExamplePersistenceUnit")
    EntityManagerFactory emf;

    @Resource(name = "java:app/env/jdbc/ExampleDataSourceRef",
              lookup = "jdbc/ExampleDataSource")
    DataSource ds;

    @Inject
    Cars cars;

    // ...
}

Persistence Unit

The persistence unit is defined according to the Jakarta Persistence specification.
It selects which DataSource to use and determines which entity classes are accessible to repositories.

Here is an example element in persistence.xml defining a persistence unit that is used by the prior examples,

<persistence-unit name="ExamplePersistenceUnit">
  <jta-data-source>java:app/env/jdbc/ExampleDataSourceRef</jta-data-source>
  <class>example.data.entity.Car</class>
  <properties>
    <property name="jakarta.persistence.schema-generation.database.action" value="create"/>
    <property name="eclipselink.logging.parameters" value="true"/>
  </properties>
</persistence-unit>

The jakarta.persistence.schema-generation.database.action property, which is from the Jakarta Persistence specification,
allows you to request the automatic creation and/or removal of database tables.
Values for this setting include: create, drop, drop-and-create, none.

DataSource in server configuration:

<dataSource jndiName="jdbc/ExampleDataSource">
  <jdbcDriver libraryRef="PostgresLib"/>
  <properties.postgresql databaseName="exampledb" serverName="localhost" portNumber="5432"/>
  <containerAuthData user="${env.DB_USER}" password="${env.DB_PASS}"/>
</dataSource>

<library id="PostgresLib">
  <fileset dir="${server.config.dir}/lib/postgres" includes="*.jar"/>
</library>

When using JNDI names or resource reference names that are scoped to a namespace like java:comp or java:module,
you must ensure that the scope is accessible to the location of the repository interface within the application.
For example, a Persistence Unit reference in the java:comp scope of an EJB is never accessible to a repository interface
because although a repository interface might exist in an EJB module, the repository interface is not part of any specific EJB.

A good practice when using scoped names is to use java:app to allow the repository interface to be defined in any module of the application.

Specifying a DatabaseStore

A databaseStore is an abstraction on top of a dataSource which gives you the ability to configure additional aspects related to Jakarta Persistence,
such as table creation and removal, without needing to provide persistence units.

Point the dataStore attribute of a Repository annotation at the id of a databaseStore element from server configuration.

Repository interface:

@Repository(dataStore = "ExampleDatabaseStore")
public interface Cars extends BasicRepository<Car, String> {
    // Query methods
}

Server configuration:

<databaseStore id="ExampleDatabaseStore"
               dataSourceRef="ExampleDataSource"
               createTables="false"
               schema="JAKARTA"
               tablePrefix="DATA">
  <authData user="${env.DB_USER}" password="${env.DB_PASS}"/>
</databaseStore>

<dataSource id="ExampleDataSource">
  <jdbcDriver libraryRef="PostgresLib"/>
  <properties.postgresql databaseName="exampledb" serverName="localhost" portNumber="5432"/>
</dataSource>

<library id="PostgresLib">
  <fileset dir="${server.config.dir}/lib/postgres" includes="*.jar"/>
</library>

Subpage of Built-in Jakarta Data Provider


Java Records as Entities

The built-in Jakarta Data Provider allows Java records to be entities.
These entities are not annotated and instead rely on a couple of basic conventions to be interpreted as entities.
The built-in Jakarta Data Provider substitutes a Jakarta Persistence entity for the Java Record.
Thus the entity model for Java Record entities is in effect a subset of the Jakarta Persistence entity model,
with the difference that it is defined by a Java Record rather than entity annotations.

Record Components

The record components are the attributes of the entity.

Record components can be of any of the Basic Types that are specified by Jakarta Data.
These include types such as int, Integer, String, UUID, byte[], LocalDateTime, Instant, and enum types.
Some additional types for collections and embeddables can also be used.

Determining the Id

Precedence for determining the Id attribute (which is required), from highest to lowest:

  • record component name is "id", ignoring case.
  • record component name ends with "_id", ignoring case.
  • record component name ends with "Id" or "ID".
  • record component type is UUID with any name.

Use the same basic types for Ids that you can use with Jakarta Persistence entities.
You can also form composite Ids by using a type that is another Java record whose record components are basic types for Ids that you can use with Jakarta Persistence entities.

Determining the Version

Precedence for determining the version attribute (optional), from highest to lowest:

  • record component name is "version", ignoring case.
  • record component name is "_version", ignoring case.

Nullable Attributes

Record components with primitive types are non-nullable. Record component with other types are nullable.

Collection Attributes

Record component types that are instances of java.util.Collection are considered element collections.

Relation Attributes

Record components with other types (including Java record) are considered embeddables.
Like Java record entities, these embeddables are not annotated.

Record Entity Example

public record Rectangle(
                String id,
                Point position,
                int height,
                int width,
                long version) {

    public static record Point(
                    int x,
                    int y) {
    }
}

The static metamodel for a record entities is optional and follows the same rules and conventions as with other types of entities.
The static metamodel is typically generated by tooling.

Static Metamodel Example

@StaticMetamodel(Rectangle.class)
public interface _Rectangle {
    String HEIGHT = "height";
    String ID = "id";
    String POSITION = "position";
    String POSITION_X = "position.x";
    String POSITION_Y = "position.y";
    String VERSION = "version";
    String WIDTH = "width";

    SortableAttribute<Rectangle> height = new SortableAttributeRecord<>(HEIGHT);
    TextAttribute<Rectangle> id = new TextAttributeRecord<>(ID);
    Attribute<Rectangle> position = new AttributeRecord<>(POSITION);
    SortableAttribute<Rectangle> position_x = new SortableAttributeRecord<>(POSITION_X);
    SortableAttribute<Rectangle> position_y = new SortableAttributeRecord<>(POSITION_Y);
    SortableAttribute<Rectangle> version = new SortableAttributeRecord<>(VERSION);
    SortableAttribute<Rectangle> width = new SortableAttributeRecord<>(WIDTH);
}

Identifying When a Record is an Entity

A Repository that defines queries and other operations on Java Record entities must indicate its use of the Java Record as an entity in one of the following ways:

  1. Inherit from a built-in repository superinterface and specify the Java Record class as its first type parameter.

    Repository with Superinterface Example

    @Repository
    public interface Rectangles extends CrudRepository<Rectangle, String> {
    }
  2. Instead of inheriting from a built-in repository superinterface, define one or more life cycle methods (annotated with Insert, Delete, Save, or Update, all of which specify the same Java record type as the type of the parameter or as the element type of the collection or array that is the parameter.

    Repository with Life Cycle Methods Example

    @Repository
    public interface Rectangles {
        @Insert
        Rectangle[] create(Rectangle... r);
    
        @Update
        Rectangle modify(Rectangle r);
    
        @Delete
        void remove(List<Rectangle> r);
    }

Example Table for a Record Entity

...

Limitations of Record Entities

Jakarta Persistence does not allow Java records to be entities. The built-in Jakarta Data provider makes this possible by substituting a generated Jakarta Persistence entity class for the record. The generated Jakarta Persistence entity class has a name that beings with the name of the record, followed by Entity. For example, if the Java record entity is named Product, a Jakarta Persistence entity named ProductEntity is generated and is used internally. The application must not define an entity of its own that collides with the generated name.

Java record entities offer a simple way to define entities at the expense of the richer programming model that is available to Jakarta Persistence entities. Where additional function is needed, use Jakarta Persistence entities instead.


Subpage of Built-in Jakarta Data Provider


Pagination and Page Requests

Jakarta Data offers two forms of pagination: Offset-based Pagination and Cursor-based Pagination. The built-in Jakarta Data provider supports both types.

Repository methods that perform pagination must accept a PageRequest parameter and return either Page or CursoredPage. Repository methods for cursor-based pagination must return a CursoredPage of the entity type. Repository methods for offset-based pagination must return a Page, which is typically of the entity type, but can alternatively be a Page of a record type where the record components are named to match a subset of the entity attributes, or can alternatively be a Page of a single entity attribute type. The latter requires annotating the repository method to have a Query annotation that supplies a JPQL query that selects a specific entity attribute of that type. Cursor-based pagination does not support returning cursored pages of anything other than the entity type because entity attribute values are used to construct the cursors relative to which next and previous pages are requested.

Sort Criteria for Pagination

For pagination to work correctly, sort criteria must define a consistent ordering of results. Sort criteria can be supplied to a repository method at run time via an Order, Sort, or Sort[] parameter. Sort criteria can be supplied by the repository during development time via the OrderBy annotation, the OrderBy Query by Method Name keyword, or the ORDER BY clause of a Query annotation value. Cursor-based pagination does not support the use of an ORDER BY clause in a Query annotation value. In additional, whe using cursor-based pagination, the Query annotation value must be written to end with a WHERE clause such that additional conditions can be appended by the Jakarta Data provider to fetch results relative to a cursor position.

Page Counts and Total Elements

The Jakarta Data API allows obtaining a total count of pages and elements across pages under certain conditions.

As indicated by the CursoredPage API, the total count of pages and count of total elements across pages are not accurate when using cursor-based pagination. Note that with cursor-based pagination, queries are performed relative to a cursor position and counts might not include elements prior to the cursor position. If total counts are needed with cursor-based pagination, write a separate countBy method or annotated Query method to retrieve the total count.

When using offset-pagination, the total count of pages and count of total elements can be accurately retrieved for pages that are retrieve via the Query by Method Name pattern, the Parameter-based Automatic Query pattern, and annotated Query methods that supply Jakarta Data Query Language (JDQL) queries that retrieve entities. Totals will also work with many Jakarta Persistence Query Language (JPQL) queries. Do not use pagination with queries that compute aggregate values such as COUNT(THIS) rather than retrieving entities.


Subpage of Built-in Jakarta Data Provider


Configuration of the built-in Jakarta Data Provider

The built-in Jakarta Data Provider is configured using the data element in server.xml
when using the data-1.0 feature is enabled.

This page describes the configuration options available for the built-in Jakarta Data Provider.

Logging parameters

By default, the built-in Jakarta Data Provider does not log the parameters passed to repository interface methods.
This default behavior ensures sensitive user data is not stored in cleartext in the logs, maintaining a secure operational environment.

However, to assist with debugging during development, developers can enable detailed logging for specific packages, repository interfaces, or individual repository methods.

Enabling Basic Trace Output

You can enable trace output for the data-1.0 feature by setting a trace specification in the logging element of your server.xml.
For example:

<server>

  <featureManager>
    <feature>data-1.0</feature>
    <feature>persistence-3.2</feature>
  </featureManager>

  <logging traceSpecification="*=info:data=all" />

</server>

Customizing Trace Output for Debugging

To aid in debugging, you can explicitly enable logging for specific packages, repository interfaces, or repository methods by using the logValues element under the data element.
This allows you to capture parameter values in the message and trace output for targeted debugging without exposing unnecessary information.

Consider when the getPrice method of the Cars repository interface:

@Repository
public interface Cars extends BasicRepository<Car, String> {
    @Query("SELECT price FROM Car WHERE vin = :vehicleId")
    Optional<Integer> getPrice(@Param("vehicleId") String vin);
}

is invoked from an application resource:

@Path("/cars")
@ApplicationScoped
public class ExampleResource {
    @Inject
    Cars cars;

    @GET
    @Path("/price/{vehicleIdNum}")
    @Produces(MediaType.TEXT_PLAIN)
    public String getPrice(@PathParam("vehicleIdNum") int vehicleIdNum) {

        Optional<Integer> price = cars.getPrice(vehicleIdNum);
        return "$" + price.get();
    }
}

You might see the following error message in messages.log:

[01/01/70, 00:00:00:000 UTC] 00000000 io.openliberty.data.internal.persistence.cdi.DataExtension   E CWWKD1047E: The java.lang.Float result of the getPrice method of the io.openliberty.example.Cars repository interface cannot be converted to the Integer return type of the repository method because the result is not within the range of -2147483648 to 2147483647.

To see the value from the database that caused the error you can enable logValues for the specific method identified by the error:

<server>
  ...
  <data>
    <logValues>io.openliberty.example.Cars.getPrice</logValues>
  </data>
</server>

This configuration produces a detailed error message with the failing value:

[01/01/70, 00:00:00:000 UTC] 00000000 io.openliberty.data.internal.persistence.cdi.DataExtension   E CWWKD1047E: The java.lang.Float (3000000000.0) result of the getPrice method of the io.openliberty.example.Cars repository interface cannot be converted to the Integer return type of the repository method because the result is not within the range of -2147483648 to 2147483647.

Table management

The built-in Jakarta Data Provider includes configuration options to manage the lifecycle of database tables for entities associated with Jakarta Data repositories.
These options, defined by the createTables and dropTables attributes, allow you to control the behavior of table creation and removal.

Create Tables

The createTables configuration element determines whether the built-in Jakarta Data Provider should attempt to create database tables for repository entities when the repository's dataStore attribute points to a DataSource.

By default, the built-in Jakarta Data Provider will attempt to create tables.

The createTables configuration is ignored when the dataStore is a persistence unit reference, as table creation is governed by persistence unit properties.
Similarly, if the dataStore is a databaseStore id, table creation is determined by the databaseStore configuration.

Disabling createTables after tables are created can improve performance in production environments.
For situations where the database user does not have permission to create tables, the ddlGen tool
can be used to generate DDL scripts for the database administrator to execute.

Example configuration:

<server>
  ...
  <data createTables="false" />
</server>

Drop Tables

The dropTables configuration element determines whether the built-in Jakarta Data Provider should attempt to remove database tables for repository entities when the repository's dataStore attribute points to a DataSource.

By default, the built-in Jakarta Data Provider will NOT attempt to remove tables.

The dropTables configuration is ignored when the dataStore is a persistence unit reference, as table removal is governed by persistence unit properties.
Similarly, if the dataStore is a databaseStore id, table removal is determined by the databaseStore configuration.

Enabling dropTables is useful for cleaning up test or development environments but should be used with caution in production to prevent accidental data loss.
This configuration provides a simple mechanism for managing database tables during development while protecting production environments from unintended changes.

Example configuration:

<server>
  ...
  <data dropTables="false" />
</server>

Subpage of Built-in Jakarta Data Provider


Data Definition Language (DDL) Generation

Open Liberty includes a utility for generating Data Definition Language (DDL) scripts.
This utility, called ddlGen, simplifies the process of creating or replicating database schemas for applications running on Open Liberty servers.

Background

A databaseStore is a configuration element in Open Liberty that defines a persistence store used by server features, including Jakarta Data.
It specifies how and where data is stored and accessed, providing a central definition for database useage.

A persistence unit is defined by the Jakarta Persistence specification.
It represents a set of entity classes managed by EntityManager instances in an application.
These entity classes correspond to the data contained within a single data store.
A persistence unit is defined in a persistence.xml file and includes metadata about the entity classes, database connection details, and provider-specific configurations.

Overview of DDL Generation

The ddlGen utility is included with Open Liberty and is located in the wlp/bin/ directory as ddlGen (for Unix-like systems) and ddlGen.bat (for Windows).
It generates DDL files for databaseStore elements defined or generated in the server configuration, enabling database administrators to create or replicate schemas required by applications.
Detailed documentation about ddlGen is available in the Liberty DDL Generation Guide.

Considerations

If a repository interface is configured with a dataStore that points to a dataSource, such as:

  • The DefaultDataSource
  • A DataSource JNDI name
  • A DataSource resource reference
  • A DataSource configuration id (from server.xml)

Then, the ddlGen utility will be able to generate DDL files from a generated databaseStore.
This ensures the database schema required for Jakarta Data repositories are well-defined and easily manageable.

The ddlGen utility does not generate DDL files for repository interfaces configured with a dataStore that points to a persistence unit reference.
In these cases, DDL generation is handled by the Jakarta Persistence provider, which offers its own methods for creating schema definitions.
This ensures the separation of responsibilities and allows Jakarta Persistence to maintain full control over the management of its schema generation.

Purpose

The ddlGen utility generates DDL files for each databaseStore element defined or generated in the Open Liberty server configuration.
This includes all features that utilize the databaseStore element, including Jakarta Data.
For Jakarta Data, the generated DDL files are suffixed with _JakartaData.ddl.

The ddlGen utility enables database administrators to create or replicate schemas without granting schema-altering privileges (e.g., CREATE, DROP) to Open Liberty database users.
This approach ensures Liberty users can only perform operational tasks (e.g., INSERT, UPDATE, DELETE) while maintaining strict control over schema management.

Prerequisites

To use the ddlGen utility, the localConnector-1.0 feature must be enabled in the server configuration:

<featureManager>
  <feature>localConnector-1.0</feature>
</featureManager>

Running the ddlGen Utility

  1. Start the server
./bin/server start <server-name>
  1. Generate DDL files
./bin/ddlGen generate <server-name>
  1. Stop the server
./bin/server stop <server-name>

The generated DDL files will be stored in the wlp/usr/<server-name>/ddl/ directory.
Each file corresponds to a databaseStore defined or generated in the server configuration.

Example DDL files

The Choosing the Database section of the build-in Jakarta Data Provider documentation
has sample server configurations for each possible dataStore configuration possible on a repository interface.
This section has example DDL files for each of these examples to show how the output and file name can change in different situations.

Default DataSource

DDL file name: ...databaseStore\[java.comp.DefaultDataSource\]\_JakartaData.ddl

DDL file output:

CREATE TABLE Car (
  VIN VARCHAR(255) NOT NULL,
  MAKE VARCHAR(255),
  MODEL VARCHAR(255),
  MODELYEAR INTEGER,
  ODOMETER INTEGER,
  PRICE FLOAT,
  PRIMARY KEY (VIN)
);

DataSource Id

DDL file name: ...databaseStore\[ExampleDataSourceID\]\_JakartaData.ddl

DDL file output:

CREATE TABLE Car (
    VIN NVARCHAR2(255) NOT NULL,
    MAKE NVARCHAR2(255) NULL,
    MODEL NVARCHAR2(255) NULL,
    MODELYEAR NUMBER(10) NULL,
    ODOMETER NUMBER(10) NULL,
    PRICE NUMBER(19,4) NULL,
    PRIMARY KEY (VIN)
)

EXIT;

DataSource JNDI Name

DDL file name: ...databaseStore\[jdbc.ExampleDataSource\]\_JakartaData.ddl

DDL file output:

CREATE TABLE Car (
    VIN VARCHAR(255) FOR MIXED DATA NOT NULL,
    MAKE VARCHAR(255) FOR MIXED DATA,
    MODEL VARCHAR(255) FOR MIXED DATA,
    MODELYEAR INTEGER,
    ODOMETER INTEGER,
    PRICE FLOAT,
    PRIMARY KEY (VIN)
);

DataSource Resource Reference

DDL file name: ...databaseStore\[java.app.env.jdbc.ExampleDataSourceRef\]\_JakartaData.ddl

DDL file output:

CREATE TABLE Car (
    VIN NVARCHAR(255) NOT NULL,
    MAKE NVARCHAR(255) NULL,
    MODEL NVARCHAR(255) NULL,
    MODELYEAR INTEGER NULL,
    ODOMETER INTEGER NULL,
    PRICE FLOAT(16) NULL,
    PRIMARY KEY (VIN)
);

DataSource Definition

DDL file name: ...databaseStore\[java.comp.jdbc.ExampleDataSourceDef\]\_JakartaData.ddl

DDL file output:

CREATE TABLE Car (
    VIN VARCHAR(255) NOT NULL,
    MAKE VARCHAR(255),
    MODEL VARCHAR(255),
    MODELYEAR INTEGER,
    ODOMETER INTEGER,
    PRICE FLOAT,
    PRIMARY KEY (VIN)
)

Persistence Unit Reference

No DDL file created, DDL generation is handled by the Jakarta Persistence provider, which offers its own methods for creating schema definitions.

DatabaseStore

DDL file name: ...databaseStore\[ExampleDatabaseStore\]\_JakartaData.ddl

DDL file output:

CREATE TABLE JAKARTA.DATACar (
    VIN VARCHAR(255) NOT NULL,
    MAKE VARCHAR(255),
    MODEL VARCHAR(255),
    MODELYEAR INTEGER,
    ODOMETER INTEGER,
    PRICE FLOAT,
    PRIMARY KEY (VIN)
);
@njr-11 njr-11 self-assigned this Aug 14, 2024
@KyleAure KyleAure self-assigned this Oct 3, 2024
@njr-11 njr-11 changed the title DRAFT IN PROGRESS - DO NOT START ON THIS YET - Documentation, Jakarta Data 1.0 Documentation, Jakarta Data 1.0 Dec 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants