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.enabledat 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 mappinghibernate.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 viaspring.jpa.mapping-resources; portable across providers but capped at whatorm_3_2.xsdcan 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-completedisables 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 →