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

9.6 KiB
Executable File

04 — Annotations vs. XML mappings in Hibernate 7.4.5 — what actually still works

← Previous: 03 — Inserting objects | Next: 05 — JPA persistence annotations →

Backs ankurm.com: Hibernate 7 annotations vs. XML mappings.

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 for the verbatim run, and the test classes under 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 and HbmXmlRuntimeTest put a real, annotation-free POJO (HbmEmployee) on the classpath with only an .hbm.xml mapping, 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.)

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 does: OrmXmlOnlyEntity carries zero JPA/Hibernate annotations — not even @Entity — and its entire mapping lives in 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 proves it mechanically: OverrideEntity.value is annotated @Column(name = "annotation_name"), and a matching 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 shows it is stronger than that: with metadata-complete set, 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 proves this is not just schema noise: MappingXmlNaturalIdEntity has zero annotations, its <natural-id> is declared purely in 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 | Next: 05 — JPA persistence annotations →