Files
hibernate-demo/docs/04-annotations-vs-xml.md

122 lines
9.6 KiB
Markdown
Executable File

# 04 — Annotations vs. XML mappings in Hibernate 7.4.5 — what actually still works
[← Previous: 03 — Inserting objects](03-inserting-objects.md) | [Next: 05 — JPA persistence annotations →](05-jpa-persistence-annotations.md)
Backs [ankurm.com: Hibernate 7 annotations vs. XML mappings](https://ankurm.com/hibernate-annotations-vs-xml-mappings-making-the-right-choice-in-hibernate-7/).
Post 4863 frames this as a two-way choice: annotations vs. `orm.xml`, with `hbm.xml` waved off
as "deprecated." That framing undersells what actually happens when you boot Hibernate
7.4.5.Final with each of these on the classpath, and it misses that there are really **three**
XML dialects in play, not two. Everything below was booted, not read about — see
[`docs/output/mappingstyle-xml-vs-annotations.txt`](output/mappingstyle-xml-vs-annotations.txt) for the verbatim run, and the test classes
under [`src/test/java/com/ankurm/hibernatedemo/mappingstyle/`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/) for the exact setup of each case.
## `hbm.xml` is not dead — it is a live code path with a warning label
The Hibernate 6 story was: `hbm.xml` is deprecated, and `hibernate.transform_hbm_xml.enabled`
is a shim that rewrites it into the modern model at boot. That shim setting still exists in
7.4.5 (`org.hibernate.cfg.MappingSettings.TRANSFORM_HBM_XML`, and the whole
`org.hibernate.boot.jaxb.hbm.*` package plus an `HbmXmlTransformer` are present in
`hibernate-core-7.4.5.Final.jar`), but it is **not required** to use `hbm.xml`.
[`HbmXmlBootTest`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/HbmXmlBootTest.java) and [`HbmXmlRuntimeTest`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/HbmXmlRuntimeTest.java) put a real, annotation-free POJO
([`HbmEmployee`](../src/main/java/com/ankurm/hibernatedemo/mappingstyle/HbmEmployee.java)) on the classpath with only an [`.hbm.xml` mapping](../src/test/resources/com/ankurm/hibernatedemo/mappingstyle/HbmEmployee.hbm.xml), and boot a plain Hibernate
`SessionFactory` three ways:
- default settings (no `transform_hbm_xml.enabled` at all) — **boots successfully**, logs one
WARN: `HHH90000028: Support for <hibernate-mappings/> is deprecated ... migrate to orm.xml or
mapping.xml, or enable hibernate.transform_hbm_xml.enabled for on the fly transformation`
- `hibernate.transform_hbm_xml.enabled=true` — boots successfully, no observable behavior
difference for a simple mapping
- `hibernate.transform_hbm_xml.enabled=false` (explicit) — **also boots successfully**
`HbmXmlRuntimeTest` goes further: it actually persists and loads an `HbmEmployee` row through
the hbm.xml-only mapping, with the transform setting deliberately unset, and it round-trips
correctly. So the honest 7.4.5 status of `hbm.xml` is: **it still fully works, unmodified,
with a WARN-level deprecation notice** — not a shim you must opt into, not a hard failure, and
not silently broken. The "transform" setting appears to matter for hbm.xml features that no
longer have a native binding path in 7.x and must be rewritten into the modern model to be
understood at all; a plain `<class>`/`<id>`/`<property>` mapping like this one never needs it.
Treat any blog claim that `hbm.xml` "requires" the transform flag, or throws without it, as
wrong for 7.4.5 — it is a live code path. (Chapter 14 hits the same "orm.xml still works with
zero registration effort" theme from the named-query angle — see
[`14 — Named queries`](14-named-queries.md#named-queries-in-ormxml).)
## `orm.xml` really can define an entire entity, annotation-free
Post 4863's `orm.xml` example only *overrides* an already-annotated `Employee`. It never proves
`orm.xml` can carry a mapping on its own. [`OrmXmlMappingResourcesTest`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/OrmXmlMappingResourcesTest.java) does: [`OrmXmlOnlyEntity`](../src/main/java/com/ankurm/hibernatedemo/mappingstyle/OrmXmlOnlyEntity.java)
carries **zero** JPA/Hibernate annotations — not even `@Entity` — and its entire mapping lives
in [`orm-xml-only-mapping.xml`](../src/test/resources/orm-xml-only-mapping.xml). The Spring Boot wiring that makes this work is
`spring.jpa.mapping-resources` (confirmed via `javap` on
`org.springframework.boot.jpa.autoconfigure.JpaProperties` in `spring-boot-jpa-4.1.1.jar` —
note the package: Boot 4's autoconfigure split moved this off the old
`org.springframework.boot.autoconfigure.orm.jpa` path entirely). With that one property set,
Hibernate creates `xml_only_widgets`, and a JPQL query (`select o from OrmXmlOnlyEntity o ...`)
against it succeeds — the entity name resolves purely from the XML.
## XML wins on conflict — confirmed, not just documented
Post 4863's Q5 claims "XML always wins over annotations." [`AnnotationXmlOverrideTest`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/AnnotationXmlOverrideTest.java) proves it
mechanically: [`OverrideEntity`](../src/main/java/com/ankurm/hibernatedemo/mappingstyle/OverrideEntity.java)`.value` is annotated `@Column(name = "annotation_name")`, and a
matching [`orm-xml-override-mapping.xml`](../src/test/resources/orm-xml-override-mapping.xml) entry maps the same field to `xml_name`. Reading the runtime metamodel
(`SessionFactoryImplementor.getMappingMetamodel().getEntityDescriptor(...).getPropertyColumnNames(...)`)
confirms the live column name is `xml_name`, and a native query against that literal column
name returns the persisted value. The article's claim is correct.
## `<xml-mapping-metadata-complete/>` is not an override switch — it is an annotation kill switch
This is the sharpest correction. The natural assumption is that
`<persistence-unit-metadata><xml-mapping-metadata-complete/></persistence-unit-metadata>` means
"XML overrides annotations for the attributes XML mentions." [`XmlMappingMetadataCompleteTest`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/XmlMappingMetadataCompleteTest.java)
shows it is stronger than that: with [metadata-complete set](../src/test/resources/orm-xml-metadata-complete.xml), `OverrideEntity`'s `@Id
@GeneratedValue` on the `id` field is **entirely ignored**, even though the matching `orm.xml`
entry never mentions `id` at all. Boot fails with
`org.hibernate.AnnotationException: Entity '...OverrideEntity' has no identifier`. Metadata-complete
does not selectively override — it switches off annotation processing for the whole persistence
unit, and any attribute XML doesn't repeat is simply gone.
## The real "what XML can do that annotations can't" — a third XML dialect
Post 4863 never mentions this, and it changes the shape of the whole topic: Hibernate 7 ships a
**third** XML mapping format, distinct from both legacy `hbm.xml` and JPA-standard `orm.xml`.
It lives in `org/hibernate/xsd/mapping/mapping-7.0.xsd` inside `hibernate-core-7.4.5.Final.jar`,
under namespace `http://www.hibernate.org/xsd/orm/mapping`, and its own XSD documentation calls
it out explicitly: *"XSD which 'extends' the JPA orm.xml XSD adding support for Hibernate
specific features."* This is exactly the second migration target the `HHH90000028` deprecation
warning names ("migrate to orm.xml **or mapping.xml**").
The difference is not cosmetic. Grepping both schemas for Hibernate-only concepts:
| Element | `orm_3_2.xsd` (JPA-standard `orm.xml`) | `mapping-7.0.xsd` (Hibernate's native dialect) |
|---|---|---|
| `<natural-id>` | absent | present |
| `<formula>` / `<discriminator-formula>` / `<join-formula>` | absent | present |
| `<filter>`-related elements | absent | present (11 occurrences) |
[`MappingXmlNaturalIdTest`](../src/test/java/com/ankurm/hibernatedemo/mappingstyle/MappingXmlNaturalIdTest.java) proves this is not just schema noise: [`MappingXmlNaturalIdEntity`](../src/main/java/com/ankurm/hibernatedemo/mappingstyle/MappingXmlNaturalIdEntity.java) has
**zero** annotations, its `<natural-id>` is declared purely in [`mapping-xml-natural-id.xml`](../src/test/resources/mapping-xml-natural-id.xml)
(root element `<entity-mappings xmlns="http://www.hibernate.org/xsd/orm/mapping" version="7.0">`),
and `session.byNaturalId(...).using("sku", ...).load()` resolves it correctly. So the accurate
answer to "what can XML express that annotations can't" is actually inverted from how the
article poses it: the JPA-portable `orm.xml` dialect can express **less** than annotations
(no Hibernate extensions at all), while Hibernate's own `mapping.xml` dialect can express
**anything annotations can**, including natural ids, formulas, and filters — you trade JPA
portability for that power, not gain something unavailable to annotations. (Chapter 06 covers
`@NaturalId` from the annotation side; this is the same feature expressed purely in XML.)
## Practical takeaway
- Legacy `hbm.xml`: still boots and runs in 7.4.5, WARN-level deprecated, no forced migration.
- `orm.xml` (JPA-standard): fully capable of defining an entity from scratch; wired into Spring
Boot via `spring.jpa.mapping-resources`; portable across providers but capped at what
`orm_3_2.xsd` can express — no Hibernate-only concepts.
- Hibernate's native `mapping.xml` (`mapping-7.0.xsd`): the actual annotation-equivalent XML
dialect, including `@NaturalId`, `@Formula`, and filters — not portable to other JPA
providers, but not missing anything either.
- XML (either dialect) always wins over a conflicting annotation for the same attribute.
- `xml-mapping-metadata-complete` disables annotation processing for the *entire* persistence
unit, not just the attributes the XML repeats — a much bigger blast radius than "override."
[← Previous: 03 — Inserting objects](03-inserting-objects.md) | [Next: 05 — JPA persistence annotations →](05-jpa-persistence-annotations.md)