Skip to content

Hibernate 7 Support#15530

Open
jdaugherty wants to merge 946 commits intospring-boot-4from
8.0.x-hibernate7
Open

Hibernate 7 Support#15530
jdaugherty wants to merge 946 commits intospring-boot-4from
8.0.x-hibernate7

Conversation

@jdaugherty
Copy link
Copy Markdown
Contributor

@jdaugherty jdaugherty commented Mar 23, 2026

I accidentally pushed to 8.0.x-hibernate7 instead of 8.0.x-hibernate7-bom. This caused #15510 to close as a result.

From that PR's todos:

Current Progress:

Fixing bad merges of the gradle plugin extraction
Fixing license headers
Fixing styling
Attempted merge of code styles
Running CI against hibernate 7 branch
Splitting boms into a hibernate5 & 7 bom (probably needs more work)

TODO:

docs need further updates
several functional tests were added for hibernate 5 and not copied to the hibernate 7 - @jamesfredley
we need to diff the hibernate 5 functional tests with hibernate 7 to see if any were subsequently changed - @jamesfredley
functional tests in hibernate 7 should use the hibernate 7 bom.
grails-datamapping-core changes need to be looked at closer; I might have merged these the wrong way. - @jamesfredley

I am opening this PR to track the status of Hibernate 7 merge to 8.x.

Some notes from previous meetings on the Hibernate 7 Upgrade:

  • HibernateQuery is the core class, GormDetachedCriteria is now inside of it & this class no longer uses an abstract builder pattern. This does mean we need an active connection to create a query.
  • HibernateCriteriaBuilder was changed to HibernateQuery - purpose was to build query components & run separately
  • Domain Binding was changed to no longer use a monolithic class. Now, "GrailsDomainBinder" is instead a thin wrapper around the root binder implementation.

jamesfredley and others added 8 commits March 19, 2026 00:21
Remove unused GormQueryOperations import from GormStaticApi. Add missing
trailing newlines to NamedCriteriaProxy and FindByMethodSpec.

Assisted-by: Claude Code <Claude@Claude.ai>
…st app

MultiDataSourceWithSessionSpec requires micronaut-http-client and
micronaut-serde-jackson for integration tests, matching the H5 test app.

Assisted-by: Claude Code <Claude@Claude.ai>
The Core Projects code style check pipes Gradle output through tee into
build/codestyle-output.log, but the build/ directory may not exist at the
start of CI. Add mkdir -p to create the directory and reports subdirectory
before tee starts writing. Introduced in 7f13d5e.

Assisted-by: Claude Code <Claude@Claude.ai>
# Conflicts:
#	gradle.properties
#	grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/GormEntity.groovy
#	grails-doc/src/en/ref/Database Mapping/insertable.adoc
#	grails-doc/src/en/ref/Database Mapping/updatable.adoc
#	grails-test-suite-uber/build.gradle
@testlens-app

This comment has been minimized.

borinquenkid and others added 21 commits March 23, 2026 13:28
getBinders() now accepts an optional collector parameter (defaulting to
getCollector()) so callers can pass a pre-created collector, ensuring
second-pass registrations made during bindRoot are visible to the same
collector used in the test body.

CollectionBinderSpec, ListSecondPassBinderSpec and MapSecondPassBinderSpec
received the same fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

# Conflicts:
#	grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/GrailsPropertyBinder.java
#	grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/SingleTableSubclassBinder.java
#	grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/cfg/domainbinding/binder/UnionSubclassBinder.java
…patch

Extract bindCollectionSecondPass into focused private methods:
- bindOneToManyAssociation: handles OneToMany collection setup
- applyMultiTenantFilter: applies tenant filter condition
- bindCollectionKey: handles bidirectional/unidirectional key binding
- bindCollectionElement: dispatches element binding by property type
- bindManyToManyElement: typed ManyToMany element binding
- bindBidirectionalMapElement: typed bidirectional map element binding

Replace ManyToOneBinder.bindManyToOne(HibernateAssociation) with
two typed overloads that eliminate instanceof guards:
- bindManyToOne(HibernateToOneProperty): handles direct to-one
  associations; extracts bindOneToOneUniqueKey for the OneToOne case
- bindManyToOne(HibernateManyToManyProperty): handles inverse side
  of ManyToMany; extracts prepareCircularManyToMany

Add HibernateManyToManyProperty.getHibernateInverseSide() covariant
override returning HibernateManyToManyProperty, enabling CollectionSecondPassBinder
to call the typed overload without a cast.

Update ManyToOneBinderSpec to call typed overloads directly
(removing 'as HibernateAssociation' casts).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…oncrete types; eliminate OneToOne cast and HibernateToOneProperty overload
…ve it from bindProperty, bindComponent, bindClassProperties, and bindCompositeId method signatures
borinquenkid and others added 2 commits April 2, 2026 21:44
- SimpleIdBinder.bindSimpleId: remove RootClass and HibernateSimpleIdentityProperty
  params; derive from domainClass internally; fix always-throw bug (add return)
- CompositeIdBinder.bindCompositeId: remove RootClass and HibernateCompositeIdentity
  params; derive from domainClass internally; fix always-throw bug (add return)
- IdentityBinder: call single-arg binders; set persistentClass from root before dispatch
- HibernateCompositeIdentityProperty: change parts type from
  HibernateSimpleIdentityProperty[] to HibernatePersistentProperty[] so regular
  composite key fields (HibernateSimpleProperty) are not filtered out
- HibernatePersistentEntity.getIdentityProperty: pass all composite parts directly
  without filtering by type
- Update all specs: CompositeIdBinderSpec, SimpleIdBinderSpec, IdentityBinderSpec,
  HibernatePersistentPropertySpec (add composite key regression test)
- AGENTS.md: add rule 11 — every code touch must update all tests for the changed class

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
setPersistentClass is already called in ClassBinder.bindClass before
bindIdentity is invoked, making the param and the call redundant.
Update RootPersistentClassCommonValuesBinder and IdentityBinderSpec accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@borinquenkid
Copy link
Copy Markdown
Member

borinquenkid commented Apr 3, 2026

merge() return value: The change from returning the original object to returning the
merged instance is a semantic change. Apps that call merge() but continue using the
original reference may silently work with stale data.
This is a regression

DetachedCriteria.list() with args: Returning PagedResultList for any non-empty
args (not just when max is set) could change behavior for code that passes sort-only
arguments.
This is also a regression

There is no reason to change behavior for these.

borinquenkid and others added 2 commits April 3, 2026 00:01
- Extract performUpsert() from save() in HibernateGormInstanceApi
  Routes to performPersist (id==null) or performMerge (id set)
- Override GormStaticApi.merge(D d) in HibernateGormStaticApi
  to delegate to instanceApi.merge(d) instead of session.persist(d)
  which throws on detached entities in Hibernate 7
- Fix performMerge to sync id (before flush) and version (after flush)
  back to the caller's instance, matching Hibernate 5 mutation semantics
- Add two tests: merge on new instance gets id+version=0;
  merge on detached instance keeps id, version increments to 1

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Existing test: list(max:N) and list(offset:N, max:M) -> PagedResultList
- New test: list() and list(offset:N) without max -> plain List, not PagedResultList

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jdaugherty
Copy link
Copy Markdown
Contributor Author

@borinquenkid in @jamesfredley PR for Spring Boot, he pointed out that the hibernate 5 classes were copied into Grails. We moved those under a spring-orm project and preserved the headers. From @jamesfredley summary, it sounds like those same classes were copied into the hibernate 7 implementation. Can we take the same approach we took on that PR:

  1. create a spring-orm project under hibernate7
  2. move those forked files there
  3. update the license headers to match the original files
  4. reference them in NOTICE/LICENSE per guidelines

borinquenkid and others added 4 commits April 3, 2026 08:19
…ria.list

withPopulatedQuery already calls populateArgumentsForCriteria unconditionally.
The extra call inside the args?.max branch caused duplicate ORDER BY clauses
since Query.order() is additive (orderBy.add). Remove the redundant call.

Add test to verify sort+max returns results in correct order exactly once.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ate7

# Conflicts:
#	NOTICE
#	gradle/publish-root-config.gradle
- Add Query.clearOrders() to core — clears orderBy list; subclasses override
- Override HibernateQuery.clearOrders() to also clear JPA detachedCriteria orders,
  replacing the TODO HACK that used order(null) to piggyback order clearing
- HibernateQuery.order() now unconditionally delegates to detachedCriteria (no null check)
- PagedResultList.initialize() calls clearOrders() on cloned query before COUNT,
  replacing the direct orderBy.clear() which missed HibernateQuery's JPA orders
- DetachedCriteria.list() uses protected newPagedResultList() factory method
- Tests: HibernateQuerySpec (clearOrders), PagedResultListSpec (DetachedCriteria
  sort+max totalCount), DetachedCriteriaSpec TCK (sort+max+totalCount)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a second bypassed and not-bypassed Highway row so findAllBypassed()
and findAllNotBypassed() actually validate multi-row behavior.
Replace findNotBypassed()/findBypassed() singular calls (which now
throw NonUniqueResultException with 2 rows) with findBypassedByName/
findNotBypassedByName to keep singular finders unambiguous.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@testlens-app

This comment has been minimized.

borinquenkid and others added 8 commits April 3, 2026 09:04
When actionName is null and no id is present, append the controller token
so that <g:form controller="foo"> (no action) generates /foo instead of
an empty string. Preserve existing behaviour when an id is present and
no action is given (id-only URL patterns such as /$id? still resolve
correctly without prepending the context controller name).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The 'many-to-many queries with sorting do not throw exception' feature
(issue #14636) now passes on Hibernate 5 as well. Remove the
@PendingFeatureIf guard and the unused IgnoreIf/PendingFeatureIf imports.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fix: add Groovy proxy support to HibernateProxyHandler.isInitialized()
@testlens-app

This comment has been minimized.

Fixed regressions from Hibernate 5
@testlens-app
Copy link
Copy Markdown

testlens-app bot commented Apr 4, 2026

✅ All tests passed ✅

🏷️ Commit: e2d9374
▶️ Tests: 14021 executed
🟡 Checks: 17/28 completed


Learn more about TestLens at testlens.app.

import org.gradle.api.tasks.testing.Test
import groovy.transform.CompileDynamic

class GrailsTestPlugin implements Plugin<Project> {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I mentioned this on the dev PR thinking we still had a root aggregation project, but I don't see one other than grails-data-test-report.

Shouldn't we remove this. Add a generic grails-test-report and instead use the testReportAggregation Gradle plugin (like grails-data-test-report already does)? This way we get caching and we don't implement our own?

@matrei what are your thoughts here?

'pkg:maven/org.jruby/jzlib@1.1.5?type=jar' : 'BSD-3-Clause'// https://web.archive.org/web/20240822213507/http://www.jcraft.com/jzlib/LICENSE.txt shows it's a 3 clause
'pkg:maven/org.jruby/jzlib@1.1.5?type=jar' : 'BSD-3-Clause', // https://web.archive.org/web/20240822213507/http://www.jcraft.com/jzlib/LICENSE.txt shows it's a 3 clause
'pkg:maven/org.jboss/jandex@3.2.3?type=pom' : 'CC0-1.0', // upstream declares Public Domain with CC0 URL but no SPDX id
'pkg:maven/org.hibernate.tool/hibernate-tools-orm@6.6.36.Final?type=jar' : 'LGPL-2.1-only', // upstream pom does not expose SPDX id
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I assume this can be removed since Hibernate 7 is what we're going with (ASF2 licensed)

'pkg:maven/org.jruby/jzlib@1.1.5?type=jar' : 'BSD-3-Clause', // https://web.archive.org/web/20240822213507/http://www.jcraft.com/jzlib/LICENSE.txt shows it's a 3 clause
'pkg:maven/org.jboss/jandex@3.2.3?type=pom' : 'CC0-1.0', // upstream declares Public Domain with CC0 URL but no SPDX id
'pkg:maven/org.hibernate.tool/hibernate-tools-orm@6.6.36.Final?type=jar' : 'LGPL-2.1-only', // upstream pom does not expose SPDX id
'pkg:maven/org.hibernate.orm/hibernate-core@6.6.36.Final?type=jar': 'LGPL-2.1-only',
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same as the above, remove?

'pkg:maven/org.jboss/jandex@3.2.3?type=pom' : 'CC0-1.0', // upstream declares Public Domain with CC0 URL but no SPDX id
'pkg:maven/org.hibernate.tool/hibernate-tools-orm@6.6.36.Final?type=jar' : 'LGPL-2.1-only', // upstream pom does not expose SPDX id
'pkg:maven/org.hibernate.orm/hibernate-core@6.6.36.Final?type=jar': 'LGPL-2.1-only',
'pkg:maven/org.hibernate.tool/hibernate-tools-utils@6.6.36.Final?type=jar' : 'LGPL-2.1-only', // upstream pom does not expose SPDX id
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same as above?

'pkg:maven/org.hibernate.tool/hibernate-tools-orm@6.6.36.Final?type=jar' : 'LGPL-2.1-only', // upstream pom does not expose SPDX id
'pkg:maven/org.hibernate.orm/hibernate-core@6.6.36.Final?type=jar': 'LGPL-2.1-only',
'pkg:maven/org.hibernate.tool/hibernate-tools-utils@6.6.36.Final?type=jar' : 'LGPL-2.1-only', // upstream pom does not expose SPDX id
'pkg:maven/org.liquibase/liquibase-core@5.0.1?type=jar' : 'Apache-2.0'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Isn't core not ASF 2 licensed until after 2 years?

'pkg:maven/opensymphony/sitemesh@2.6.0?type=jar' : 'OpenSymphony', // custom license approved by legal LEGAL-707
'pkg:maven/org.jruby/jzlib@1.1.5?type=jar' : 'BSD-3-Clause'// https://web.archive.org/web/20240822213507/http://www.jcraft.com/jzlib/LICENSE.txt shows it's a 3 clause
'pkg:maven/org.jruby/jzlib@1.1.5?type=jar' : 'BSD-3-Clause', // https://web.archive.org/web/20240822213507/http://www.jcraft.com/jzlib/LICENSE.txt shows it's a 3 clause
'pkg:maven/org.jboss/jandex@3.2.3?type=pom' : 'CC0-1.0', // upstream declares Public Domain with CC0 URL but no SPDX id
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

According to https://repo1.maven.org/maven2/org/jboss/jandex/3.3.1/jandex-3.3.1.pom it was moved to https://smallrye.io/blog/jandex-3-0-0/

Do you know what's including this library? Since the new blog says it's ASF 2.0 licensed it would be better if we could use that version instead (we may not be able to if hibernate isn't pulling in the ASF version)

Copy link
Copy Markdown
Contributor Author

@jdaugherty jdaugherty Apr 4, 2026

Choose a reason for hiding this comment

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

If we can't adopt the ASF version, we may have to update our LICENSE file per https://www.apache.org/legal/resolved.html#handling-public-domain-licensed-works (We will likely have to open an ASF LEGAL request or get Paul's feedback to make sure we're doing this right)

@@ -0,0 +1,31 @@
/*
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Was this a bad merge? I think we removed this on 7.0.x

liquibaseHibernate5Version=4.27.0
liquibaseHibernate5CoreVersion=4.27.0
liquibaseHibernate7CoreVersion=4.27.0
#TODO - hibernate7 version - 6.0.0-SNAPSHOT
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The SBOM indicated we were using 5, was that a mistake or are we using 4 still?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants