jdaugherty opened a new pull request, #16695:
URL: https://github.com/apache/grails-core/pull/16695
Fixes #16688
### Problem
A domain property mapped with a Hibernate `CompositeUserType`, as the GORM
guide documents for multi-column types,
```groovy
amount type: MonetaryAmountType, {
column name: 'amount'
column name: 'currency'
}
```
fails the `HibernateDatastore` bootstrap under `grails-data-hibernate7` with
```
java.lang.IllegalArgumentException: Named type [class
com.example.gorm.MonetaryAmountType] did not implement BasicType nor UserType
```
The binder puts every `type:` on a `BasicValue`, and Hibernate 7 resolves
the type name of a `BasicValue` through `TypeDefinition`, which accepts a
`BasicType` or a `UserType` only. A composite user type is not a basic value in
Hibernate 6 and 7: `@CompositeType` is bound as a `Component` whose type name
is the user type, with one property per attribute of the `embeddable()` class,
in alphabetical attribute order, read through `getPropertyValue(value, index)`
and rebuilt through `instantiate(ValueAccess)`.
### Fix
- `HibernatePersistentProperty.getCompositeUserType()` resolves the
composite user type of a property from its own `type:` or from the `user-type`
registered for its class.
- `GrailsPropertyBinder` routes such a property to the new
`CompositeUserTypeBinder`, which builds the component the way Hibernate's own
`EmbeddableBinder` does: a property per attribute of the embeddable class
(record components, or readable and writable bean properties less statics,
`transients` and transient fields) in alphabetical order, each a `BasicValue`
typed by reflection on the embeddable class and accessed through Hibernate's
composite-user-type property access strategy.
- The columns of the property mapping are assigned to the attributes in that
order and go through the existing `ColumnBinder`, so `sqlType`, `length`,
`index`, `unique` and the property's `nullable` constraint apply. An attribute
without a configured column is named `<property>_<attribute>`. More columns
than attributes is a `MappingException` naming the property, the counts and the
attributes. `typeParams` are logged as ignored, because Hibernate instantiates
a composite user type itself.
### Tests
`CompositeUserTypeMappingSpec` (new) covers the reporter's mapping with two
properties of the same type, the metadata and schema (`char(3)`, `NOT NULL` on
the required property, nullable columns on the optional one), insert,
read-back, update and clearing to null, HQL on the whole value and on an
attribute path, criteria `eq`, `findAllBy` and `countBy`, default column names,
a type registered with `user-type`, an `embeddable()` record distinct from the
value class, and the column-count error. `GrailsPropertyBinderSpec` gets a
binder-level case and `GrailsHibernatePersistentPropertySpec` covers
`getCompositeUserType()`. All seven features of the new spec fail on `8.0.x`
with the reported exception before the fix.
Run locally on JDK 21: the four specs above together with
`UserTypeMappingSpec` (49 tests), `:grails-data-hibernate7-core:codeStyle` and
`validateRepositoryConventions`. The full module suite is left to CI.
### Docs
- GORM guide, _Custom Hibernate Types_: new _Composite User Types_ section
with the Hibernate 7 contract (embeddable class, alphabetical attribute index,
column order, default names, `user-type` registration, `typeParams`).
- `type` mapping reference: the multi-column example follows the attribute
order and uses `sqlType: "char(3)"` (an explicit `sqlType` is emitted verbatim,
so a separate `length` has no effect).
- Grails 8 upgrade guide, Hibernate 5 to 7 migration: new _Custom User
Types_ note on the `UserType` API change and on the column order of a ported
`CompositeUserType`.
Not verified against the reporter's example application end to end (it would
need a local snapshot publish); its `grails-data-hibernate7` branch also calls
a non-existent `Currency.of(String)` in `instantiate`, which fails
independently of this bug.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]