Skip to content

Run on JPA 3.2 and 4, from a single artifact. - #4307

Open
oscarfanchin wants to merge 22 commits into
spring-projects:mainfrom
oscarfanchin:gh-4197-impl
Open

Run on JPA 3.2 and 4, from a single artifact.#4307
oscarfanchin wants to merge 22 commits into
spring-projects:mainfrom
oscarfanchin:gh-4197-impl

Conversation

@oscarfanchin

@oscarfanchin oscarfanchin commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Handling interoperability between generations turned into two distinct problems.

Hibernate's changes to SPIs and classes are handled by two reflective adapters, one per Hibernate
generation. The binary incompatibility between the Jakarta Persistence 3.2 and 4.0 signatures is
handled by a bridge. On 3.2, which is the baseline, the calls go through directly; on 4.0 they rely
on reflection.

Hibernate 8.x is used through opt-in profiles and the snapshot repository is declared inside them,
so the default build resolves nothing from it.

I tried to make the way I worked visible in the commits: cross-version compilation first, then the
build and the tests obtained by recompiling, with the behaviour changes documented as they came up.

The point is where this ends up: a build made only against Jakarta Persistence 3.2 and Hibernate 7
runs under both profiles, without recompiling.

Three of these changes are deliberately standalone, so any one of them can be dropped without
affecting the others: the temporary artifact versions, the AOT generation change, and the workaround
for Hibernate 8 named queries. I tried it, and they come out easily.

Rationale in #4197.

  • You have read the Spring Data contribution guidelines.
  • You use the code formatters provided here and have them applied to your changes. Don’t submit any formatting related changes.
  • You submit test cases (unit or integration tests) that back your changes.
  • You added yourself as author in the headers of the classes you touched. Amend the date range in the Apache license header if needed. For new types, add the license header (copy from another file and set the current year only).

Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Split the reflective Hibernate glue into Hibernate7Adapter and
Hibernate8Adapter behind a common HibernateAdapter contract. The
adapter is selected by type presence rather than by the declared
version, which is not reliable in shaded or repackaged distributions,
and missing types degrade to the legacy path instead of failing during
class initialization.

The adapters cover the differences that are structural and permanent:
the SQM access point and the named query mementos Hibernate 8 renamed
and relocated, and the MutationOrSelectionQuery indirection that
selection queries now go through. Methods are resolved against the
implementation class, because Hibernate 8's SqmStatementAccess does
not declare getQueryString(), and are cached per class instead of
being looked up on every call.

Hibernate 7 keeps the inherited null answer for asSelectionQuery(...):
on that generation selection queries implement SelectionQuery directly
and the caller's instanceof check already covers them.

Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Jakarta Persistence 4 changes the return type of the untyped query factory
methods on EntityManager from Query to StatementOrTypedQuery, which breaks
binary compatibility with code compiled against 3.2. Introduce an adapter that
resolves those methods at runtime and invokes them reflectively, so a single
binary works against both API generations.

The overloads whose signature is unchanged, such as createQuery(CriteriaSelect),
are called directly and are not routed through reflection.

Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Replace the direct calls to the EntityManager methods whose return type changed
in Jakarta Persistence 4 with calls to JpaPortableQueries, including the query
creation emitted by the AOT repository contributor.

EntityManagerFactory.createEntityManager() has no counterpart in Jakarta
Persistence 4; the Map overload survives with an unchanged signature and is used
instead.

Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Generated repositories are compiled against the user's own classpath, so their
query creation would work against either Jakarta Persistence generation without
help. They are routed through JpaPortableQueries only so that the generated path
stays identical to the runtime one and cannot drift from it later.

Nothing depends on this: dropping this commit makes the contributor emit the
EntityManager calls directly again, and the test suite stays green.

Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Test sources cannot name the untyped query factory methods directly and stay
compilable against both API generations, so stubbing and verification go through
EntityManagerTestUtils, which resolves them at runtime.

The helper deliberately does not delegate to the production adapter: stubbing,
exercising and verifying through the same code would make a defect in it
undetectable. Mockito.verify(...) stays at the call sites because arguments are
evaluated before the call, and a matcher passed as a parameter would be
registered before verification starts.

Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
Signed-off-by: Oscar Fanchin <oscar.fanchin@gmail.com>
@oscarfanchin

Copy link
Copy Markdown
Contributor Author

Hi @mp911de

Rebased after the 4.2.0-M1 release. The branch now builds against Hibernate 7.4.5 and EclipseLink 5.0.1, and the suites are green on the default profile and on the Hibernate 8 ones.

One commit dropped in the rebase: the EclipseLink test filtering is already handled on main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants