Skip to content

Helidon Data JDBC - implementation of declarative and imperative APIs - #12232

Open
bhatpmk wants to merge 48 commits into
helidon-io:mainfrom
bhatpmk:11713-data-jdbc-declarative
Open

Helidon Data JDBC - implementation of declarative and imperative APIs#12232
bhatpmk wants to merge 48 commits into
helidon-io:mainfrom
bhatpmk:11713-data-jdbc-declarative

Conversation

@bhatpmk

@bhatpmk bhatpmk commented Aug 5, 2026

Copy link
Copy Markdown

Resolves #11711
Resolves #11712
Resolves #11713

Description

Adds a Helidon Data provider built directly on Java SE JDBC. It supports compile-time generated declarative repositories and a public imperative JdbcClient API without depending on Jakarta Persistence.

  1. Declarative repositories

    • Adds @jdbc.Statement for declaring SQL on repository methods.
    • Adds @Jdbc.Client for selecting a configured JDBC client.
    • Supports both named parameters and positional JDBC parameters.
    • Supports inherited abstract methods, including methods declared by separately compiled parent interfaces.
    • Named parameters in a separately compiled parent interface require that interface to be compiled with -parameters. Positional parameters do not require retained parameter names.
    • Supports explicit QUERY and UPDATE execution.
    • Supports AUTO execution based on the Java method shape and JDBC annotations. SQL keywords are not used to infer execution.
  2. Parameter binding

    • Supports the fixed portable Java scalar types defined by the provider.
    • Generates typed null bindings with the canonical JDBCType for each supported reference type.
  3. Result mapping

    • Supports scalar results, Java records, and application row mappers.
    • Maps record components by column label and calls the canonical constructor directly.
    • Supports marker row mappers selected by their generic service contract.
    • Supports explicit row mapper selection by service type.
    • Supports T, Optional, and List query results.
    • Fully materializes results before returning from the client or repository method.
  4. Updates and generated keys

    • Supports update methods returning void, int, or long.
    • Uses large update counts when supported by the driver and falls back to legacy integer update counts otherwise.
    • Supports driver-selected generated keys and explicitly named generated columns.
    • Maps generated keys as scalars, records, or application-defined results.
    • Uses a staged generated-key API that preserves column order and does not acquire resources until a terminal operation runs.
  5. Imperative JDBC API

    • Adds the public preview JdbcClient API for imperative applications.
    • Uses the same API from generated repository implementations.
    • Supports standalone construction from direct connection settings or a named DataSource, as well as registry-managed injection.
    • Provides staged statement binding, update execution, row mapping, generated-key mapping, and materialized result terminals.
    • Keeps connections, statements, and result sets inside the provider. JDBC resources cannot escape through the public API.
    • Standalone clients do not participate in transactions established by annotations on their callers.
  6. Local transactions

    • Integrates local JDBC transactions with the existing Helidon transaction SPI.
    • Acquires the transaction connection lazily and reuses it for operations against the same data source.
    • Supports REQUIRED, MANDATORY, SUPPORTED, NEW, UNSUPPORTED, and NEVER propagation.
    • Handles transaction suspension, resumption, rollback-only state, commit, rollback, and cleanup failures.
    • Invalidates connections when the transaction outcome or connection state is unsafe.
    • Does not support using the JDBC and Jakarta Persistence providers together for transactional data access.
    • Shared Helidon Data code generation copies transaction annotations declared directly on a repository type to the generated implementation when the annotation is identified by @Tx.TransactionType. This
      behavior applies to both JDBC and Jakarta Persistence generated repositories. Type-level repository transaction annotations have limited preview behavior; applications should apply one transaction
      annotation directly to each repository method that requires transaction propagation.
  7. Portable SQL profile

    • Imperative and generated repositories use the same portable marker-recognition policy so code generation and runtime binding produce consistent parameter plans.

    • The portable profile protects standard quoted strings and identifiers, MySQL backtick identifiers, conventional comments, PostgreSQL escape and dollar-quoted strings, and Oracle alternative-quoted
      strings when their complete delimiters are recognized.

    • The profile intentionally does not implement a complete database dialect:

      • -- starts a marker-protected comment only when followed by whitespace, a control character, or the end of the input. SQL should therefore use conventional -- comment spacing.
      • Square-bracket identifiers do not protect marker-shaped text.
      • A pgJDBC ?? escape is preserved and does not produce bind markers.
      • Nested block comments are rejected instead of applying PostgreSQL nesting rules globally.
    • Database-specific profiles are not configurable in this release.

Documentation

This is a new feature and #12231 will provide the documentation.

@oracle-contributor-agreement oracle-contributor-agreement Bot added the OCA Verified All contributors have signed the Oracle Contributor Agreement. label Aug 5, 2026
@bhatpmk bhatpmk changed the title Declarative implementation of Helidon Data based on JDBC Helidon Data JDBC - implementation of declarative and imperative APIs Aug 5, 2026
@bhatpmk
bhatpmk requested a review from tomas-langer August 5, 2026 07:02
Comment thread data/jakarta-persistence/tests/pom.xml Outdated
Comment thread data/jdbc/codegen/src/main/java/io/helidon/data/jdbc/codegen/JdbcMethodPlan.java Outdated
Comment thread data/jdbc/jdbc/src/test/java/io/helidon/data/jdbc/JdbcAnnotationsTest.java Outdated
Comment thread data/jdbc/jdbc/src/test/java/io/helidon/data/jdbc/JdbcAnnotationsTest.java Outdated
Comment thread data/jdbc/jdbc/src/test/java/io/helidon/data/jdbc/JdbcClientApiTest.java Outdated
Comment thread data/jdbc/jdbc/pom.xml Outdated
Comment thread data/jdbc/tests/src/test/resources/application.properties Outdated
.build()));
plan.method().throwsChecked().forEach(method::addThrows);
for (TypeName txAnnotation : JdbcPersistenceTypes.TX_ANNOTATIONS) {
plan.method().findAnnotation(txAnnotation).ifPresent(method::addAnnotation);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI finding: Type-level transaction annotations are lost here. Tx.* annotations target both types and methods and are source-retained, but this code copies only annotations declared directly on repository methods. The generated class does not copy the repository type annotation either. Consequently, @Tx.Required interface Repository generates methods without the requested transaction interceptor. Resolve the effective type- and method-level transaction annotation, with method-level precedence, and add coverage for type-level use.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated io.helidon.data.codegen.common.BasePersistenceGenerator to retain transaction annotations declarated at the type-level. The change doesn't resolve precedence or conflicting annotations. The change will also affect the JPA based implementation.

Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcRunner.java Outdated
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcScriptRunner.java Outdated
Comment thread data/jdbc/codegen/src/main/java/io/helidon/data/jdbc/codegen/JdbcMethodPlan.java Outdated
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcScriptRunner.java Outdated
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcExceptionTranslator.java Outdated
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcRow.java Outdated
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcClientImpl.java Outdated
Comment thread data/tests/jdbc/src/test/java/io/helidon/data/jdbc/JdbcResourceOwnershipTest.java Outdated
@bhatpmk
bhatpmk force-pushed the 11713-data-jdbc-declarative branch from 916a1ed to 22eca82 Compare August 5, 2026 11:34
@bhatpmk
bhatpmk marked this pull request as draft August 11, 2026 05:47

@bhatpmk bhatpmk left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have consolidated response to the review comments together.

Comment thread data/jdbc/codegen/src/main/java/io/helidon/data/jdbc/codegen/JdbcMethodPlan.java Outdated
Comment thread data/jdbc/jdbc/src/test/java/io/helidon/data/jdbc/JdbcAnnotationsTest.java Outdated
Comment thread data/jdbc/tests/src/test/resources/application.properties Outdated
Comment thread data/jdbc/jdbc/pom.xml Outdated
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcPersistenceUnitFactory.java Outdated
* @param start first dash offset
* @return whether the two dashes begin a protected line comment
*/
boolean lineComment(String source, int start) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an intentional portability boundary. PostgreSQL treats every -- sequence as a line-comment opener, while MySQL requires whitespace or a control character after the second dash. Treating every occurrence as a comment would hide a real bind marker in valid MySQL expressions such as balance--?. The portable profile therefore requires whitespace, a control character, or end-of-input. PostgreSQL SQL processed by JdbcClient must consequently use conventional spacing such as -- comment. Supporting the broader PostgreSQL rule will be done with a database profile, in future.

* @param start first dash offset
* @return whether the two dashes begin a protected line comment
*/
private boolean lineComment(String source, int start) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rule intentionally mirrors the runtime JDBC marker policy. Code generation rewrites named markers while runtime code counts positional markers, so the two implementations must classify line comments identically. Otherwise, a generated repository can validate one parameter plan and execute another. The portable whitespace requirement preserves markers for databases with MySQL style double-dash semantics. PostgreSQL SQL should use -- comment until an explicitly selected PostgreSQL profile is available.

@bhatpmk
bhatpmk marked this pull request as ready for review August 12, 2026 06:08
@bhatpmk
bhatpmk requested a review from tomas-langer August 12, 2026 06:08

@tomas-langer tomas-langer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI Review: Review findings are included in the inline comments.

Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcPersistenceUnitFactory.java Outdated
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcRunner.java
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcRunner.java Outdated
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcStatement.java Outdated
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcClientImpl.java Outdated
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcPreparationPlan.java Outdated
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcRunner.java Outdated
* rule explicit and is verified against the runtime scanner's conformance
* corpus.
*/
final class JdbcSqlMarkerLexer {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naïvely I would think that SQL lexing would be useful outside of code generation.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed. The general SQL lexical recognition is extracted to data/jdbc/lexical module. Updated JDBC codegen and runtime code to use the new module.

/**
* Compile-time correspondence between repository parameters and JDBC positions.
*/
record JdbcSqlParameterPlan(String sql, List<Bind> binds) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a lot of ceremony for what should be a fairly simple operation. I would also expect named parameter support to work at runtime.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The general SQL marker recognition is removed from this class to the shared JDBC lexical module. The remaining plan is specific to codegen. Named parameters work declarative repositories, as codegen rewrites them to positional JDBC SQL and generates corresponding binds.

/**
* Resolves repository methods and generic types across an interface hierarchy.
*/
final class JdbcTypeHierarchy {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class seems to recapitulate whole swaths of javax.lang.model.util.Types and implement a good chunk of the Java Language Specification. Was that its intent? It surprises me naïvely that this would be necessary.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent was to resolve effective inherited repository methods. Correctly supporting inheritance caused the implementation to reproduce a substantial portion of Types and Java language specification.

The provider-neutral operations are now moved to an internal TypeHierarchyResolver in the common codegen module. The JDBC repository method discovery and explicit RowMapper contract resolution now use TypeHierarchyResolver.

Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcPersistenceUnitFactory.java Outdated
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcClient.java Outdated
Comment thread data/jdbc/jdbc/src/main/java/io/helidon/data/jdbc/JdbcClient.java Outdated
… independently, preventing timezone conversion errors from canceling during round-trip assertions
@bhatpmk
bhatpmk force-pushed the 11713-data-jdbc-declarative branch from 488e6a8 to 0f1278d Compare September 4, 2026 01:53
@bhatpmk
bhatpmk requested a review from tomas-langer September 4, 2026 04:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

OCA Verified All contributors have signed the Oracle Contributor Agreement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Helidon Data: JDBC Declarative Helidon Data: JDBC Imperative Helidon Data JDBC

3 participants