Conversation
… representation strategy Replaces the Hibernate 5 tuplizer bridge with a Hibernate 7 EntityRepresentationStrategy, injected through the pluggable `hibernate.persister.factory` service so no reflection or private API is needed. Ports the rest of the module to the 7.4 API surface far enough for 218 of 229 runnable tests to pass; the remaining failures are tracked for the full migration. - Bump to org.hibernate.orm:hibernate-core 7.4.8.Final, add community dialects, drop the Hibernate 5 SQLite dialect - New: BoxPersisterFactory, BoxRuntimeModelCreationContext, BoxBootstrapContext, BoxRepresentationResolver, BoxEntityRepresentationStrategy, BoxPropertyAccess - Removed: EntityTuplizer - Instantiator, getter/setter, proxy factory and lazy initializer ported to the new SPI (Object ids, EntityInstantiator, MapMember for the JPA metamodel, composite-id maps) - javax.persistence -> jakarta.persistence converters - Integrator/listener signatures, legacy Criteria -> HQL, ClassMetadata -> MappingMetamodel, save/saveOrUpdate/delete -> persist/merge/remove - Mapping: save-update cascade -> persist,merge; text columns no longer emit a length Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Keeps Hibernate 7.4.8 and the new metadata-on-boot setting, takes the 1.7.1 version bump, and retains development's SQLiteDialectResolver repointed at the community-dialects SQLiteDialect, since the Hibernate 5 SQLite dialect dependency was dropped. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
CI status: 10 known failures, migration in progressThe A. Test assertions still pinned to Hibernate 5 (4 tests). The version-string test expects B. Test code calling a removed API (2 tests). C. Entity-typed HQL parameters (4 tests). Because BoxLang entities are dynamic maps, Hibernate 7 resolves an association parameter's Java type to Groups A and C, plus the two group-B rewrites, are held pending design decisions from the maintainer (detached-entity semantics, orm.xml timing, dialect policy). They will land together in the next push so CI does not burn a cycle on partial fixes. This PR is not ready to merge until then. Generated by Claude Code |
…cts, and datetime precision Phase 2 of the Hibernate 7.4 migration. bx-orm is the ORM abstraction, so these keep the BoxLang-facing behavior identical while hiding Hibernate 7's breaking changes. - HQLQuery: resolve association parameters (a primary key or an entity instance) to the managed entity before binding, using the SQM parameters' anticipated types. Restores Hibernate 5's lenient parameter handling that Hibernate 7's strict validation rejected. - ORMApp.resolveEntityReference(): shared helper that turns a key or entity into the managed reference Hibernate expects. - EntitySave: detached entities are re-attached via merge() and the managed state is copied back into the caller's instance, so entitySave() still leaves the passed object live with its generated id and event changes (Hibernate 7 removed saveOrUpdate()). - DateTimeConverter maps to java.sql.Timestamp so Hibernate 7 binds fractional seconds instead of truncating date/time properties to whole seconds. - ORMConfig: dialect aliases (including legacy version-specific names) resolve to Hibernate 7 base dialects, in core or hibernate-community-dialects, with a one-time deprecation warning; replaces the removed hibernate.temp.use_jdbc_metadata_defaults with hibernate.boot.allow_jdbc_metadata_access. - Tests: updated version, metadata-on-boot, and dialect assertions; rewrote the getClassMetaData metadata/dirty-check tests onto getMappingMetamodel().getEntityDescriptor(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…tegy bridge Docs described the removed Hibernate 5 tuplizer. Refresh them to the Hibernate 7 design. - AGENTS.md: module now runs on Hibernate ORM 7.4.x; describe the representation-strategy bridge injected via the hibernate.persister.factory service; fix the skill summaries. - bx-orm-hibernate-bridge skill: rewritten around BoxEntityRepresentationStrategy, BoxRepresentationResolver, BoxPersisterFactory and the delegating contexts; RepresentationMode.MAP, EntityInstantiator, Object ids, MapMember, composite-id maps; notes EntityTuplizer/EntityMode removal and the Getter/Setter deprecation for Hibernate 8. - bx-orm-session-management skill: replace setEntityTuplizerFactory() with the persister-factory registration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Add standalone boot + CRUD smoke tests broadening dialect coverage beyond MySQL and SQLite: - DerbyDialectBootTest runs everywhere via embedded, in-memory Derby. - PostgreSQLDialectBootTest and MariaDBDialectBootTest are gated on the ORM_TEST_POSTGRES / ORM_TEST_MARIADB env vars so they skip locally and run in CI against service containers. Each test boots its own isolated BoxLang app (raw JDBC connectionString datasource, dbcreate=dropcreate) and asserts an entity can be saved and queried back. build.gradle now pulls the Derby, PostgreSQL, and MariaDB JDBC drivers as test dependencies and copies them into the module's isolated classloader dir; the CI workflow adds Postgres and MariaDB service containers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical HQL injection risk and additional moderate correctness and compatibility issues block approval.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR upgrades the ORM module from Hibernate 5.6 to 7.4 and refactors its entity representation, persistence APIs, Jakarta imports, dialect handling, and integration tests.
Changes:
- Replaces the Hibernate 5 tuplizer integration with Hibernate 7 representation strategies.
- Updates HQL filtering, entity persistence, converters, configuration, and dialect mappings.
- Adds database-specific ORM fixtures and boot-test coverage.
File summaries
| File | Review summary |
|---|---|
src/test/resources/postgresApp/models/Product.hbm.xml |
PostgreSQL ORM mapping fixture reviewed. |
src/test/resources/postgresApp/models/Product.bx |
PostgreSQL entity fixture reviewed. |
src/test/resources/postgresApp/index.bxs |
PostgreSQL test application entrypoint reviewed. |
src/test/resources/postgresApp/Application.bx |
PostgreSQL application configuration reviewed. |
src/test/resources/mariadbApp/models/Product.hbm.xml |
MariaDB ORM mapping fixture reviewed. |
src/test/resources/mariadbApp/models/Product.bx |
MariaDB entity fixture reviewed. |
src/test/resources/mariadbApp/index.bxs |
MariaDB test application entrypoint reviewed. |
src/test/resources/mariadbApp/Application.bx |
MariaDB application configuration reviewed. |
src/test/resources/derbyApp/models/Product.hbm.xml |
Derby ORM mapping fixture reviewed. |
src/test/resources/derbyApp/models/Product.bx |
Derby entity fixture reviewed. |
src/test/resources/derbyApp/index.bxs |
Derby test application entrypoint reviewed. |
src/test/resources/derbyApp/Application.bx |
Derby application configuration reviewed. |
src/test/java/ortus/boxlang/modules/orm/hibernate/converters/DateTimeConverterTest.java |
Nit (1 vote): add fractional-second input and equality assertions. |
src/test/java/ortus/boxlang/modules/orm/config/PostgreSQLDialectBootTest.java |
PostgreSQL dialect boot coverage reviewed. |
src/test/java/ortus/boxlang/modules/orm/config/ORMConfigTest.java |
ORM configuration tests reviewed. |
src/test/java/ortus/boxlang/modules/orm/config/MariaDBDialectBootTest.java |
MariaDB dialect boot coverage reviewed. |
src/test/java/ortus/boxlang/modules/orm/config/DerbyDialectBootTest.java |
Derby dialect boot coverage reviewed. |
src/test/java/ortus/boxlang/modules/orm/bifs/ORMGetSessionFactoryTest.java |
Session factory BIF coverage reviewed. |
src/test/java/ortus/boxlang/modules/orm/bifs/ORMGetHibernateVersionTest.java |
Hibernate version BIF coverage reviewed. |
src/test/java/ortus/boxlang/modules/orm/bifs/EntitySaveTest.java |
Entity save coverage reviewed. |
src/main/java/ortus/boxlang/modules/orm/SessionFactoryBuilder.java |
Hibernate 7 session factory integration reviewed. |
src/main/java/ortus/boxlang/modules/orm/ORMService.java |
Hibernate 7 metadata and service integration reviewed. |
src/main/java/ortus/boxlang/modules/orm/ORMApp.java |
Critical (3 votes): validate sort properties before HQL interpolation. Moderate (3 votes): resolve association filter values as managed entity references. |
src/main/java/ortus/boxlang/modules/orm/mapping/inspectors/ClassicPropertyMeta.java |
Cascade translation updates reviewed. |
src/main/java/ortus/boxlang/modules/orm/mapping/HibernateXMLWriter.java |
Hibernate mapping and formula generation reviewed. |
src/main/java/ortus/boxlang/modules/orm/HQLQuery.java |
Moderate (1 vote): resolve every expanded association parameter against the same entity target. |
src/main/java/ortus/boxlang/modules/orm/hibernate/EntityTuplizer.java |
Obsolete Hibernate 5 implementation removal reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/converters/TimeConverter.java |
Jakarta converter update reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/converters/StringConverter.java |
Jakarta converter update reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/converters/ShortConverter.java |
Jakarta converter update reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/converters/LongConverter.java |
Jakarta converter update reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/converters/IntegerConverter.java |
Jakarta converter update reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/converters/FloatConverter.java |
Jakarta converter update reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/converters/DoubleConverter.java |
Jakarta converter update reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/converters/DateTimeConverter.java |
JDBC timestamp conversion reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/converters/BooleanConverter.java |
Jakarta converter update reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/converters/BigIntegerConverter.java |
Jakarta converter update reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/converters/BigDecimalConverter.java |
Jakarta converter update reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/BoxRuntimeModelCreationContext.java |
Runtime model context delegation reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/BoxRepresentationResolver.java |
Moderate (1 vote): avoid relying directly on Hibernate’s internal resolver implementation. |
src/main/java/ortus/boxlang/modules/orm/hibernate/BoxProxyFactory.java |
Hibernate 7 proxy creation reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/BoxProxy.java |
Proxy integration reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/BoxPropertySetter.java |
Dynamic property setter integration reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/BoxPropertyGetter.java |
Nit (2 votes): update stale getMember() Javadoc. |
src/main/java/ortus/boxlang/modules/orm/hibernate/BoxPropertyAccess.java |
Dynamic property access reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/BoxPersisterFactory.java |
Custom persister integration reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/BoxLazyInitializer.java |
Lazy initialization integration reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/BoxEntityRepresentationStrategy.java |
Entity representation strategy reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/BoxClassInstantiator.java |
Hibernate 7 entity instantiation reviewed. |
src/main/java/ortus/boxlang/modules/orm/hibernate/BoxBootstrapContext.java |
Bootstrap context integration reviewed. |
src/main/java/ortus/boxlang/modules/orm/config/SQLiteDialectResolver.java |
SQLite dialect resolution reviewed. |
src/main/java/ortus/boxlang/modules/orm/config/ORMConfig.java |
Moderate (1 vote): retain mappings for all previously supported legacy dialect aliases. |
src/main/java/ortus/boxlang/modules/orm/config/EventListener.java |
ORM event listener integration reviewed. |
src/main/java/ortus/boxlang/modules/orm/bifs/EntitySave.java |
Moderate (2 votes): synchronize merged mapped state across both object scopes. |
src/main/java/ortus/boxlang/modules/orm/bifs/EntityNew.java |
Entity creation integration reviewed. |
src/main/java/ortus/boxlang/modules/orm/bifs/EntityLoadByExample.java |
Moderate (2 votes): include inherited persistent properties in example predicates. |
src/main/java/ortus/boxlang/modules/orm/bifs/EntityDelete.java |
Hibernate 7 deletion integration reviewed. |
gradle.properties |
Hibernate version upgrade reviewed. |
changelog.md |
Upgrade documentation reviewed. |
build.gradle |
Hibernate dependencies and test drivers reviewed. |
AGENTS.md |
Repository guidance reviewed. |
.github/workflows/tests.yml |
Database integration-test workflow reviewed. |
.agents/skills-custom/bx-orm-session-management/SKILL.md |
ORM session-management guidance reviewed. |
.agents/skills-custom/bx-orm-hibernate-bridge/SKILL.md |
Hibernate bridge guidance reviewed. |
Review details
Suppressed comments (4)
src/main/java/ortus/boxlang/modules/orm/HQLQuery.java:391
- List parameters are expanded by incrementing
parameterIndex, butentityParamsis keyed by the original SQM parameter position. Consequently, for an association query such asWHERE manufacturer IN (:ids)with two primary keys, only the first list element is resolved throughresolveBindValue; subsequent elements are bound as raw scalars and Hibernate 7 rejects the entity-typed parameter. Capture the association entity name before expanding the list and resolve every element with that same target.
int parameterIndex = 1;
for ( QueryParameter param : this.parameters ) {
if ( param.isListParam() ) {
Array list = ( Array ) param.getValue();
for ( Object value : list ) {
hqlQuery.setParameter( parameterIndex, resolveBindValue( parameterIndex, value, entityParams ) );
parameterIndex++;
src/main/java/ortus/boxlang/modules/orm/config/ORMConfig.java:976
- This replacement map omits legacy aliases that were accepted by the Hibernate 5 switch, including
FRONTBASE,INTERBASE,JDATASTORE,MCKOI,POINTBASE, andPROGRESS. Because unknown short names are returned unchanged, applications using any of these settings now pass an unresolved alias to Hibernate 7 and fail during boot; retain a compatibility mapping for every previously supported alias.
// Community dialects
m.put( "DERBY", COMMUNITY + "DerbyDialect" );
m.put( "DERBYTENFIVE", COMMUNITY + "DerbyDialect" );
m.put( "DERBYTENSIX", COMMUNITY + "DerbyDialect" );
m.put( "DERBYTENSEVEN", COMMUNITY + "DerbyDialect" );
src/main/java/ortus/boxlang/modules/orm/hibernate/BoxRepresentationResolver.java:25
- The resolver fallback is hard-wired to
org.hibernate.metamodel.internal.ManagedTypeRepresentationResolverStandard, an internal Hibernate implementation. This contradicts the public-SPI boundary described for the Hibernate 7 integration and can break linkage on a Hibernate 7.x update; isolate the version-specific adapter or use a supported public resolver contract.
import org.hibernate.metamodel.internal.ManagedTypeRepresentationResolverStandard;
src/test/java/ortus/boxlang/modules/orm/hibernate/converters/DateTimeConverterTest.java:39
- This test uses a whole-second
LocalDateTimeand only checks non-null, so it cannot detect the sub-second precision regression that this converter change is intended to fix. Use a value with non-zero fractional seconds and assert the converted timestamp equals the original instant.
- Files reviewed: 63/64 changed files
- Comments generated: 5
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Add a JMH microbenchmark suite (src/jmh) measuring entity CRUD and HQL throughput against an embedded Derby datasource: - `./gradlew jmh` benchmarks the current build (Hibernate 7). - `./gradlew jmhCompare` benchmarks Hibernate 7 (current) against the last Hibernate 5 release (module 1.7.0, downloaded and set up automatically), runs each version in its own forked JVM, and prints a side-by-side table. The benchmark loads the ORM module (and thus Hibernate) from a swappable module path, keeping only BoxLang + JMH on the outer classpath so Hibernate resolves inside the module's isolated classloader - mirroring production and keeping the version comparison honest. Rigor is overridable via -PperfForks / -PperfWarmup / -PperfIterations. See src/jmh/README.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
- ORMApp.loadEntitiesByFilter: validate/canonicalize the order-by property against the entity's persistent properties (fixes an HQL injection risk where the sort property was interpolated unchecked). - ORMApp.loadEntitiesByFilter: resolve to-one association filter values (a primary key or entity instance) to a managed reference before binding, reusing resolveEntityReference via SQM inspection - restores Hibernate 5 behavior that Hibernate 7's stricter parameter typing rejected. - EntitySave: sync both the `this` and variables scopes back onto the caller's detached instance after merge(), so generated ids / event-updated values are not left stale. - BoxPropertyGetter: remove stale getMember() javadoc that still claimed it returns null. - DateTimeConverterTest: assert sub-second precision is preserved. Full suite: 238/239 pass locally; the one failure is the known local-MariaDB fractional-seconds quirk (passes on CI MySQL 8). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…100) Address the two remaining Copilot review findings: - HQLQuery: an association list parameter (WHERE manufacturer IN (:ids)) records its entity target only at the list's first position, but the expanded elements occupied later positions and were bound as raw scalars, which Hibernate 7 rejects. Resolve every expanded element against the list's first-position target. - EntityLoadByExample: build predicates from the inheritance-aware persistent property set (getAllPersistentProperties) so examples on a subclass match parent-declared properties, and exclude ids, the version, and associations. Full suite: 238/239 pass locally; the one failure is the known local-MariaDB fractional-seconds quirk (passes on CI MySQL 8). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Copilot review addressedFixed and pushed (commits
Not changing:
Local suite: 238/239 pass; the single failure is the known local-MariaDB fractional-seconds quirk that passes on CI (MySQL 8). Generated by Claude Code |
Per review feedback, clarify BoxRepresentationResolver's embeddable branch: it delegates composite ids / components to Hibernate's built-in ManagedTypeRepresentationResolverStandard.INSTANCE because Hibernate exposes no public factory for the default embeddable strategy. Document it as the single, guarded internal touch-point in both the class javadoc and AGENTS.md, so it's the one place to revisit if a future Hibernate release relocates it. No behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Add two CPU-focused benchmarks to complement the I/O-bound CRUD benchmark: - ORMReadBenchmark: bulk hydration of N rows (default 2000) into BoxLang entities, exercising per-entity/per-property work at volume. - ORMBootBenchmark: cold ORM application boot (entity discovery + mapping generation + SessionFactory build), SingleShotTime, run one boot per fork. Adds a richer Widget entity (varied typed properties) to the derbyApp fixture so the read path exercises the attribute converters. README documents how to run each and how to read the sampling profiler. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…#100) Add a MappingXMLWriter that emits Hibernate 7's modern <entity-mappings> format as an alternative to the legacy hbm.xml DTD writer, for Hibernate 8 readiness. Opt in per app via the ormXmlMapping ORM setting (default false), so production and all existing tests stay on the HBM writer with zero behavior change. Covers: dynamic (MAP) entities as <entity metadata-complete="true">, ids with the increment generator, JPA AttributeConverters (<convert>), <version>, discriminators, single-table inheritance (incl. per-subclass secondary table) and joined inheritance, many-to-one/one-to-one, one-to-many/many-to-many, composite ids, formulas, and second-level cache. A Derby smoke test (DerbyOrmXmlMappingBootTest) boots an app with the flag on and round-trips these constructs. Known Hibernate 7.4.8 limitation for class-less dynamic entities: uuid id generation and byte[] basic attributes are not expressible in the modern format (documented with TODOs). These are resolved by moving entities to real POJO facades, a separate follow-up. Full suite: 242 completed, 1 failed (the known local-MariaDB fractional-seconds quirk that passes on CI MySQL 8), 9 skipped. No HBM regression. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…e 1) First slice of the composition-based POJO representation. EntityFacadeFactory generates, per entity at runtime (ByteBuddy), a real Java "facade" class that Hibernate maps as a normal POJO. The facade holds no state: each generated getter/setter delegates to a BoxEntityState, backed in production by the entity's BoxLang instance (which is a Map), so the BoxLang class and the facade share one state store. Because the facade is a real class with a real, typed id member, Hibernate's id-generator resolution works - including uuid, which is impossible for class-less dynamic (MAP) entities. A test proves a generated facade maps with a uuid id and persists correctly, delegating through the backing state. Not yet wired into the representation strategy (that is stage 2); nothing calls the factory yet, so existing behavior is unchanged. Adds an explicit byte-buddy dependency (pinned to Hibernate's version). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…(stage 2) Wire the generated entity facades (stage 1) end to end, gated on a new default-OFF `entityFacades` ORM setting. When enabled, entities map to real POJO facade classes and Hibernate does real id generation (uuid works); when off, the representation is byte-for-byte the original dynamic MAP behavior. - ORMConfig/ORMKeys: add `entityFacades` (default false). - facade.BoxIClassRunnableState: BoxEntityState over an IClassRunnable, writing both this + variables scopes (mirrors BoxPropertySetter); exposes getRunnable(). - facade.EntityFacadeNaming / FacadeSupport: deterministic facade FQN + id Java type, and the wrap/unwrap boundary with per-instance memoization (hidden $bxORMFacade key, since an IClassRunnable's hashCode is content-based). - BoxFacadeInstantiator: POJO-mode instantiator building the IClassRunnable via BoxClassInstantiator then wrapping it in the facade. - BoxEntityRepresentationStrategy/BoxRepresentationResolver/BoxPersisterFactory: facade branch (POJO mode, facade JavaType, standard property access, lazy off); MAP branch unchanged. - SessionFactoryBuilder: generate one facade per entity into the module loader before boot. HibernateXMLWriter: emit <class name=facadeFQN entity-name=...> in facade mode. - BIF/boundary unwrap (ORMService.getEntityName, EntityNew/EntitySave, ORMApp read paths, HQLQuery, EventListener) so devs only ever get the BoxLang class and events fire on it. FacadeRepresentationBootTest: boots facade-mode Derby and round-trips a uuid-keyed entity through entityNew/save/loadByPK/ormExecuteQuery. Full suite flag-off: 244 completed, 1 failed (known local-MariaDB fractional-seconds quirk), 9 skipped. No MAP regression. Associations, composite ids, and lazy proxies in facade mode are follow-ups. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Bring facade (POJO) mode to parity with MAP mode for associations and lazy loading; still gated on entityFacades, MAP mode untouched. - Association accessors translate at the Hibernate/BoxLang boundary: a to-one getter wraps the scope's IClassRunnable to the target facade for Hibernate, its setter unwraps back; a to-many accessor hands Hibernate its managed collection of facades and stores a FacadeCollectionView (a live List that unwraps facades->IClassRunnable on read, wraps on write) in the scope. Devs navigating parent.getChild()/getChildren() only ever see BoxLang instances. - EntityFacadeFactory: PropertySpec gains AssocKind (NONE/TO_ONE/TO_MANY); to-many accessors declared List<Object> for Hibernate 7 element typing. SessionFactoryBuilder tags each association property's kind. - Lazy proxies re-enabled for facades: representation strategy provides BoxProxyFactory + BoxProxy proxy type; HibernateXMLWriter no longer forces lazy=false; BoxLazyInitializer unwraps the loaded facade to the IClassRunnable (no-op in MAP mode). A lazy to-one arrives as a BoxProxy that initializes on access and yields the BoxLang instance. - FacadePropertyAccess: wraps standard reflection access to coerce a raw IClassRunnable owner to its facade (Hibernate hands the id-getter the raw instance during FK/isTransient breakdown). BoxIClassRunnableState.get() reads the variables scope (aligns with MAP-mode BoxPropertyGetter). FacadeAssociationBootTest (Derby, real BIFs): a uuid-keyed one-to-many + many-to-one graph and a lazy to-one round-trip, navigation yields IClassRunnable (never facades). Full suite flag-off: 246 completed, 1 failed (known local-MariaDB fractional-seconds quirk), 9 skipped. No MAP regression. Composite ids and inheritance in facade mode remain follow-ups. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…e 2c) Close the last parity gaps between facade (POJO) mode and MAP mode. - EntityFacadeFactory: multi-id facade generation plus subclass facades that extend their parent facade (rely on ByteBuddy's imitated ctor to avoid duplicate-signature failures). - SessionFactoryBuilder: generate facades ordered by inheritance depth (roots first); no longer require exactly one id per entity; subclasses receive the parent facade as their generated superclass. - HibernateXMLWriter: emit name= on <subclass>/<joined-subclass> and on composite-id markup in facade mode. - ORMApp: load-by-id builds a facade id instance carrying the struct key values for facade-mode composite ids; MAP-mode Map branch untouched. - Tests: FacadeCompositeIdBootTest, FacadeInheritanceBootTest and their fixture apps (facadeCompositeApp, facadeInheritanceApp). Facade mode is now at parity with MAP mode for simple ids, uuid, associations, lazy proxies, composite ids, and single-table/joined inheritance. All flag-gated behind entityFacades; zero MAP regression. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
… app Stage 3 (prove): flip the default entity representation to POJO facades (entityFacades defaults to true) so the whole suite exercises facade mode, and fix the one real defect that flipping the default surfaces. Cross-app facade collision: facade class names and the wrap/unwrap registry were keyed by bare entity name. Once every application generates facades, two apps in one JVM that each map a same-named entity (e.g. User, Animal) to a different shape collided on one global facade class / registry entry, handing one app another app's facade (wrong id/property types). Fix: namespace facades per application. - ORMConfig.facadeNamespace, stamped at ORMApp startup from the (unique) application name via EntityFacadeNaming.sanitizeNamespace. - EntityFacadeNaming.facadeClassName(namespace, entityName) -> per-app FQN (...generated.<namespace>.<Name>Facade); plus sanitizeNamespace/namespaceOf. - FacadeSupport registry keyed by namespace|entityName; instances stamped with their owning namespace so wrapInstance resolves the right facade even before it is memoized. BoxFacadeInstantiator stamps the namespace. - HibernateXMLWriter and SessionFactoryBuilder pass the namespace through facade generation, registration, and the <class name=...> emitted for Hibernate. The modern mapping.xml writer does not yet support facades (planned), so the derbyOrmXmlApp fixture is pinned to entityFacades=false to keep validating that writer in MAP mode. MAP mode remains fully available via the flag. All facade + Derby smoke tests pass locally (facade simple/uuid, associations, lazy proxies, composite ids, inheritance; Derby dialect; mapping.xml in MAP mode). The MySQL-backed suite runs on CI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Flipping facades to the default surfaced BIF paths that still handed Hibernate the raw IClassRunnable (which it no longer tracks - it tracks the facade) or handed developers a raw facade. Complete the boundary: - EntityReload: refresh the managed facade, not the detached IClassRunnable. - EntityIsAttached: check containment of the facade. - EntityLoadByExample: unwrap facade results to BoxLang instances. - BoxClassInstantiator: the generated to-many helpers (hasX/addX/removeX) now operate on the scope collection as a List<Object>, so a facade-mode FacadeCollectionView works alongside a MAP-mode Array/PersistentBag. - ORMApp.resolveEntityReference: a primary key or entity bound as an association query parameter resolves to the managed facade (session.get) rather than a lazy BoxProxy, so Hibernate 7's parameter type check accepts it. Added FacadeSupport.managed() helper for the session-op boundary. - HibernateXMLWriter: guard the facade branch against a null entity name. Tests: - HibernateXMLWriterTest pins itself to MAP mode (it asserts HBM structural output, which is representation-agnostic; facade naming is covered by the boot tests). - ORMClearSessionTest probes attachment via entityIsAttached (the supported, facade-aware BIF) instead of a raw session.contains of a BoxLang instance. - FacadeAssociationBootTest gains Derby coverage for reload, isAttached, to-many add/has, entityLoadByExample, and a PK-as-query-parameter bind. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…fire The last two facade-mode CI failures, both surfaced by making facades the default representation. 1. removeX during iteration (EntityLoadByPKTest.testEntityRemoveMethod): getChildren().each( c => parent.removeChild( c ) ) dropped/nulled elements. The scope holds a live FacadeCollectionView over Hibernate's managed collection; BoxLang's .each iterates by index (Array.intStream + size()>idx?get(idx):null), so removeX shrinking the collection shifts indices under the iterator. BoxLang's native Array is copy-friendly, which is why MAP mode works. Fix: BoxClassInstantiator now installs a facade-mode to-many getter that returns a stable snapshot Array (a copy of the current IClassRunnable elements) while addX/removeX keep mutating the live view - so iteration is stable and structural changes still flow to Hibernate. In MAP mode the scope already holds a plain Array, returned as-is. 2. Duplicate preUpdate/postUpdate (EntityEventsTest.testEntityUpdateEvents): updating an entity with an association/collection fired its update events twice. EventListener's preUpdate/preInsert state-sync wrote every scope property value back into Hibernate's event state array; for association and collection slots the facade's FacadeCollectionView/facade reference differs from Hibernate's own state entry, so overwriting it corrupted Hibernate's collection tracking and triggered a spurious second UPDATE. The sync now skips association and collection property types (event handlers change basic values, not associations; MAP mode never overwrote these either since the scope held Hibernate's own instance). Both reproduced and validated on embedded Derby via new FacadeAssociationBootTest cases (remove-during-each, single-element remove, and update-events-fire-once on a collection-bearing entity); the Manufacturer fixture gains an event log. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…keys The final facade-mode CI failure (EntityLoadByPKTest.testEntityRemoveMethod): removeX on a to-many silently removed nothing when the element's id property was named differently from the owning entity's. getRemoveMethod matched collection elements using the OWNER instantiator's identifier keys (this.identifierKeys) against the elements' scopes - so for an owner keyed `id` holding elements keyed `vin` (the app's Manufacturer -> Vehicle), the key was never present on the element and the filter matched nothing. The facadeApp fixtures masked this because owner and element both used `id`. Fix: remove the element the developer passed by identity/equality (it comes from iterating the same association), independent of id property names. The FacadeCollectionView unwraps facades on compare, so an IClassRunnable argument still matches its managed facade in the backing collection. Applies to MAP mode too (a plain Array/PersistentBag of IClassRunnables). Validated on Derby with a new Fleet(id=fleetId) -> Truck(id=plate) fixture whose id names deliberately differ (and from the owner's), reproducing the app's shape. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Make the Hibernate 7 mapping.xml writer work with the default facade representation, closing the gap that pinned `ormXmlMapping` apps to MAP mode. - MappingXMLWriter emits the generated facade `class` on `<entity>` and the facade FQN as association `target-entity` in facade mode; MAP mode still emits class-less `metadata-complete` entities. - The modern format binds the mapped class via the JPA annotation path, so a facade entity's Hibernate entity-name is its facade class FQN. Route by-name Session/metamodel calls through the JPA import name (ORMApp.hibernateEntityName via getImportedName) and make BoxEntityNameResolver report the facade FQN for BoxEntityFacade instances, so assertInstanceOfEntityType and by-name lookups resolve. - BoxRepresentationResolver keeps a facade-FQN fallback index so strategy resolution succeeds when Hibernate reports the entity-name as the facade class. - Flip the derbyOrmXmlApp fixture to facade mode; regenerate its mapping XML. Verified locally on Derby: DerbyOrmXmlMappingBootTest (mapping.xml facade) and FacadeAssociationBootTest (hbm facade) both green. Remaining suite failures are only the local MySQL container being unavailable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Flip the module to always emit Hibernate 7's modern mapping.xml format and remove the legacy hbm.xml DTD writer and the ormXmlMapping opt-in flag. hbm.xml is deprecated-for-removal in Hibernate; BoxLang-facing ORM behavior is unchanged. - MappingGenerator/SessionFactoryBuilder always use MappingXMLWriter and the combined <entity-mappings> file; the .orm.xml extension is the only output. - Remove HibernateXMLWriter + HibernateXMLWriterTest, the ormXmlMapping ORMConfig field and ORMKeys entry. Relocate the shared escapeReservedWords/toHibernateType helpers onto MappingXMLWriter. Delete stale .hbm.xml fixtures; regenerate .orm.xml. - All test apps now run facade + mapping.xml (the previous default was facade + hbm). Fixes surfaced by the switch: - BoxFacadeInstantiator.isInstance now recognizes a facade by its real class, so a primary-key load (entityLoadByPK/session.get/byId) of an entity managed in the current session no longer returns null. The old name comparison rejected the managed facade because the modern format's entity-name is the facade class FQN, not the BoxLang name. Real-class matching also fixes subclass polymorphism. - The writer emits the legacy string uuid generator for facade entities instead of @uuidgenerator, whose java.util.UUID JDBC binding missed the VARCHAR id column on a session.get/byId load (HQL matched, PK load did not). Verified locally on Derby: FacadeRepresentation/Inheritance/Association/CompositeId, DerbyOrmXmlMapping, and DerbyDialect boot tests all green. Full MySQL suite runs in CI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…ribute name Facade mode + modern mapping.xml: a bidirectional association whose owning property name is capitalized (e.g. a many-to-one named `Client`) broke SessionFactory boot for the whole application with: AnnotationException: Collection '...ClientFacade.contacts' is 'mappedBy' a property named 'Client' which does not exist in the target entity 'ClientContactFacade' Hibernate discovers a facade entity's attributes by JavaBean introspection of its generated getX/setX accessors, which decapitalizes the leading character (`Client` -> `client`). A collection's mapped-by must therefore name that decapitalized attribute, not the original BoxLang property name. The writer now runs mapped-by through Introspector.decapitalize in facade mode (MAP mode is class-less and keeps the XML name verbatim). This was the single root cause of all 31 MariaDB CI failures on the previous commit (the main app's ClientContact.Client many-to-one). Adds FacadeMappedByBootTest + facadeMappedByApp, a minimal Derby fixture whose Item.Owner many-to-one reproduces the exact failure and round-trips the graph. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…ping The modern mapping.xml writer skipped binary (byte[]) properties, but the facade class still generated a getX()/setX() accessor for them. Hibernate then auto-discovered that Object-typed accessor and mapped the column as JAVA_OBJECT (SqlTypes 2000), which has no SQL type name -> schema export and multi-table joined-inheritance strategy setup failed. This broke SessionFactory boot for the whole app (e.g. the main test app's Employee extends User graph with an `avatar` byte[] column), the second root cause of the MariaDB CI failures. Fix (facade mode, where the entity is a real class): - SessionFactoryBuilder generates a concrete byte[] accessor for a binary property (not Object), so Hibernate infers the column's SQL type from it. - MappingXMLWriter emits a plain <basic> (column only, no converter/java-type) for a binary property, letting Hibernate resolve VARBINARY/BLOB from the byte[] accessor. Binary stays skipped only in class-less MAP mode, where byte[] is not expressible in the modern format. FacadeMappedByBootTest now also persists and reads back a byte[] photo column. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
An inverse one-to-many that declares no explicit fkcolumn (the FK is defined only on the inverse many-to-one, e.g. the main app's Author.rejections -> Rejection.user) produced no mapped-by, so Hibernate treated the collection as owning and synthesized a default join table. That join table's FK to the owning table (users) then failed schema export, and the owning entity's tests all failed at runtime with "Table 'test.users' doesn't exist" - the shared cause of the remaining MariaDB failures. resolveMappedBy now falls back, when the collection has no key column, to the target entity's back-reference to-one (matched by entity type) to derive mapped-by, so the collection stays non-owning and no join table is synthesized. FacadeMappedByBootTest's Owner.items now declares no fkcolumn to exercise this path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
The modern mapping.xml writer emitted a text/clob property as an in-row varchar(length) (e.g. varchar(8000)). A wide varchar counts toward a table's maximum row size; the main app's Author (table `users`) has two text length="8000" columns, whose varchar(8000) pushed the table past MySQL 8's 65535-byte row limit, so CREATE TABLE failed and every test touching that entity failed at runtime with "Table 'test.users' doesn't exist". MariaDB tolerated the wide varchar, which is why local Derby/MariaDB runs passed while MySQL CI did not. The writer now emits <lob/> for a text/clob property and drops the column length, so Hibernate creates a TEXT/LONGTEXT (large-object, off-row) column - both semantically correct and immune to the row-size limit. Reproduced and verified against MySQL 8 (the CI database) with the full app/ ContentBox suite: the previously-unbootable app now boots and 205/215 tests pass (the remaining 10 are unrelated behavioral issues, addressed separately). Also broadened FacadeMappedByBootTest (mapped-superclass parent, a text/lob column, and an inverse one-to-many with no fkcolumn) to guard these on Derby. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…acade In facade (POJO) mode Hibernate manages the generated facade, not the caller's IClassRunnable, and by-name cache/metamodel calls key off the facade class (the modern mapping.xml entity-name), not the BoxLang name. Three BIFs missed this and failed on MySQL with "Unknown entity type" / "Non-entity object instance passed to evict": - entityDelete: wrap to the facade and remove it (re-associating a detached facade via merge first, since Hibernate 6+ rejects removing an unmanaged instance). - entityMerge: wrap to the facade, merge by Hibernate entity-name, and return the caller's BoxLang instance (unwrapped from the managed facade). - ormEvictEntity: evict the L2 cache under ORMApp.hibernateEntityName (the facade class), keeping the BoxLang name only for id-type resolution. Verified against MySQL 8 (the CI database) with the app/ suite: previously-failing delete/merge/evict and the event-driven delete now pass (10 -> 8 failures); the remaining 8 are separate issues addressed next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
In facade (POJO) mode Hibernate manages a generated facade and the modern mapping.xml entity-name is the facade class FQN, so code reaching through the raw ormGetSession()/ormGetSessionFactory() to call Hibernate with a BoxLang entity name or instance failed (Unknown entity type / Non-entity object passed to evict). The ORM BIFs translate at that boundary, but consumers such as cborm use the raw Session/SessionFactory directly. Add FacadeAwareHibernate: a dynamic proxy over the target's full interface set (so SPI casts like SessionFactoryImplementor still work) that, per call, rewrites a registered BoxLang entity name to its Hibernate entity-name (via getImportedName; non-entity strings such as HQL/property names pass through), wraps IClassRunnable arguments to their managed facade, and unwraps facade results / re-wraps returned Session/SessionFactory/Cache/Metamodel so chained calls keep translating. Applied only in facade mode (MAP mode's entity-name is already the BoxLang name). Verified against MySQL 8 (CI database): entityMerge (session.detach), ormEvictEntity (getCache().containsEntity), ormGetSessionFactory and entitySave dirty-state (getMappingMetamodel().getEntityDescriptor) by BoxLang name now pass; full app/ suite 8 -> 3 failures with no regressions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…ables A discriminated single-table subclass that maps its own columns to a separate join table is emitted as a JPA <secondary-table>. Unlike the @SecondaryRow annotation (owned defaults to true), Hibernate's orm.xml binding leaves a <secondary-table> read-only when owned is omitted: the row is SELECTed via outer join but never written. That silently dropped the subclass's own columns and FKs on insert (e.g. ClientContact.Client persisted as null) and skipped the secondary-row delete (FK violation on cascade). Emitting owned="true" makes Hibernate manage the secondary table's insert/update/delete, the modern-format equivalent of the classic hbm <join> Hibernate manages by default. Also fixes MappingGeneratorTest to check the regenerated .orm.xml file (the module now emits mapping.xml, not hbm.xml). Reproduced and verified against MySQL 8 with the app/ ClientContact and cbContentStore entities; isolated the cause with standalone plain-Hibernate tests (annotations write the secondary row; orm.xml does not unless owned). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Apply the same owned="true" fix to the other discriminated-subclass secondary-table fixtures (Employee, Contractor, Page, Entry, Dog) so the committed mappings match the writer output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Adds a HibernateFeatureCoverageBootTest (Derby, facade/default mode) that round-trips a <version> optimistic-lock column and a computed formula property. Building it surfaced three facade-mode regressions, all fixed: - ClassicPropertyMeta rejected the normalized ormType "integer" for a version (only accepted int/long/short) and omitted timestamp versions. - The facade generated an Object accessor for the version property, which is not a legal Hibernate version type and failed SessionFactory build; it now generates a concrete numeric/temporal accessor. - The preInsert/preUpdate event state-sync overwrote Hibernate's managed version slot with the scope's stale value, causing OptimisticLockException; it now skips the version slot as it already skips associations/collections. First installment of the documented-feature coverage sweep. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…tors Extends HibernateFeatureCoverageBootTest (Derby, facade/default mode) with round-trips for previously-untested documented features, all passing without code changes: - many-to-many via a link table (add/has/remove round-trip) - shared-primary-key one-to-one (foreign id generator + constrained) - identity id generator with dynamic insert/update Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Adds round-trips (Derby, facade/default mode) for the sequence id generator and documented ormType aliases (yesno/truefalse -> boolean, bigint -> long, tinyint -> short, numeric -> double, nvarchar -> string). Both pass without code changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…-collection> A fieldtype="collection" value collection (a collection of scalars) was silently dropped: COLLECTION was neither a column, association, nor version, and the code held only a dead "not yet supported" note. It is now parsed as a collection, categorized as an association, and emitted as a JPA <element-collection> (BAG) with the element value declared as <column> + an explicit Hibernate JavaType descriptor (<java-type>) — <column> and <convert> are mutually exclusive in the element-collection schema. The facade exposes it as a to-many List whose scalar elements pass through unwrapped. HibernateFeatureCoverageBootTest round-trips an array-of-strings element collection on Derby (facade/default mode). Map collections are next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…lection> Extends value/element collections to the struct/map case (type="struct" with structKeyColumn/structKeyType), emitted as an <element-collection> MAP with <map-key-class> + <map-key-column> for the key and <column> + <java-type> for the value. Adds a TO_MANY_MAP facade AssocKind with a Map accessor; the interceptor converts the BoxLang Struct's Key keys to their scalar form when handing the map to Hibernate (and leaves Hibernate's own map untouched). HibernateFeatureCoverageBootTest now round-trips both array and map element collections on Derby (facade/default mode). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…pace The committed generated mappings carried a stale `generated.default` facade namespace, but each test app generates its own per-app namespace (derived from the application name, e.g. `bxormfacademappedbytest`). Every test run therefore rewrote these files, producing perpetual working-tree churn. Regenerate them to match what the apps actually emit so the fixtures are stable. Pure namespace changes; full MySQL suite green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
…ndler Adds a global event handler fixture that records fired events into application scope, and a test proving the global-only lifecycle events (onFlush, onClear) plus preInsert reach the configured global handler in facade mode. Closes the "declared but never asserted" gap for global-only events. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Adds Derby facade-mode round-trips for the native and guid id generators and for a read-only (immutable) entity whose updates Hibernate silently ignores. All pass without code changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Round-trips a uuid2-generated entity through a primary-key load on Derby in facade mode; passes without code changes (no UUID-vs-string binding issue). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X
Description
This PR upgrades the BoxLang ORM module from Hibernate 5.6.15 to 7.4.8, which is a major version jump requiring significant architectural changes to the ORM integration layer.
Key Changes
Hibernate Version Upgrade
hibernateVersionfrom5.6.15.Finalto7.4.8.Finalingradle.propertiesorg.hibernate:*toorg.hibernate.orm:*inbuild.gradlejavax.persistence.*tojakarta.persistence.*across all converter classesORM Architecture Refactoring
Hibernate 6+ removed the tuplizer-based entity handling in favor of a representation strategy pattern. This required a complete redesign of how BoxLang entities are integrated:
Removed:
EntityTuplizer.java- The Hibernate 5 tuplizer implementation is no longer compatible with Hibernate 7Added:
BoxRepresentationResolver.java- ImplementsManagedTypeRepresentationResolverto route entity representation to BoxLang-aware strategiesBoxEntityRepresentationStrategy.java- ImplementsEntityRepresentationStrategyto provide instantiation, proxy, property access, and name resolution for BoxLang entitiesBoxBootstrapContext.java- Wraps Hibernate's bootstrap context to inject the custom representation resolverBoxRuntimeModelCreationContext.java- Wraps the runtime model creation context to use the custom bootstrap contextBoxPersisterFactory.java- CustomPersisterFactoryservice that injects the representation resolver into entity persistersBoxPropertyAccess.java- Provides property getter/setter access for mapped propertiesAPI Updates
BoxClassInstantiator: Updated to implementEntityInstantiatorinstead ofInstantiator; refactored constructor to acceptEntityRecordinstead ofEntityMetamodelBoxProxyFactory: Updated to work with Hibernate 7's proxy modelBoxPropertyGetter/BoxPropertySetter: Updated to work with Hibernate 7's property access APIsORMApp.loadEntitiesByFilter(): Replaced deprecated Criteria API with HQL query builderEntityLoadByExample.java: Replaced deprecated Criteria API with HQL-based filteringEntitySave.java: Updated to usepersist()andmerge()instead of deprecatedsave()/saveOrUpdate()ORMService.java: Updated to useSharedSessionContractImplementorinstead of deprecatedClassMetadataSessionFactoryBuilder.java: RemovedEntityModereferences (no longer applicable in Hibernate 7)HibernateXMLWriter.java: Added formula element generation for computed propertiesHQLQuery.java: Updated query option setters for Hibernate 7 APIClassicPropertyMeta.java: Added cascade translation method to handle Hibernate 7 cascade syntaxConfiguration Changes
ORMConfig.java: Changedhibernate.temp.use_jdbc_metadata_defaultstohibernate.allow_metadata_on_bootwith new enum values (allow/disallow)MariaDB102Dialect→MariaDBDialectJira/Github Issues
This upgrade addresses compatibility with Hibernate 7.x and modernizes the ORM module to use current Jakarta EE standards.
Type of change
Checklist
https://claude.ai/code/session_01SnmFsqBfa9SR4V1rRXw57X