From d18a34b0d2a81885a0bc4afc1be4cfcf10e3de9f Mon Sep 17 00:00:00 2001 From: Bastien JANSEN Date: Wed, 26 Nov 2025 17:48:53 +0100 Subject: [PATCH] HHH-19959 map `default-cascade` attribute in hbm.xml files --- .../internal/OverriddenMappingDefaults.java | 14 +++--- .../source/internal/hbm/MappingDocument.java | 21 ++++++++- .../source/internal/hbm/ModelBinder.java | 2 +- .../cascade/DefaultCascadeBindingTest.java | 45 +++++++++++++++++++ .../test/hbm/cascade/default-cascade.hbm.xml | 35 +++++++++++++++ 5 files changed, 108 insertions(+), 9 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/hbm/cascade/DefaultCascadeBindingTest.java create mode 100644 hibernate-core/src/test/resources/org/hibernate/orm/test/hbm/cascade/default-cascade.hbm.xml diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/OverriddenMappingDefaults.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/OverriddenMappingDefaults.java index 938e7c0cbf6e..aeed1e9ee1df 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/OverriddenMappingDefaults.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/OverriddenMappingDefaults.java @@ -151,7 +151,7 @@ public static class Builder { private String implicitDiscriminatorColumnName; private String implicitPackageName; private boolean autoImportEnabled; - private final EnumSet implicitCascadeTypes; + private EnumSet implicitCascadeTypes; private jakarta.persistence.AccessType implicitPropertyAccessType; private String implicitPropertyAccessorName; private boolean entitiesImplicitlyLazy; @@ -229,12 +229,12 @@ public Builder setAutoImportEnabled(boolean autoImportEnabled) { return this; } -// public Builder setImplicitCascadeStyleName(String implicitCascadeStyleName) { -// if ( StringHelper.isNotEmpty( implicitCascadeStyleName ) ) { -// this.implicitCascadeStyleName = implicitCascadeStyleName; -// } -// return this; -// } + public Builder setImplicitCascadeTypes(EnumSet implicitCascadeTypes) { + if (implicitCascadeTypes != null) { + this.implicitCascadeTypes = implicitCascadeTypes; + } + return this; + } public Builder setImplicitPropertyAccessType(jakarta.persistence.AccessType accessType) { if ( accessType != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/MappingDocument.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/MappingDocument.java index 48066a096d24..717fcef22f6e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/MappingDocument.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/MappingDocument.java @@ -4,8 +4,10 @@ */ package org.hibernate.boot.model.source.internal.hbm; +import java.util.EnumSet; import java.util.Set; +import org.hibernate.annotations.CascadeType; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.hbm.spi.EntityInfo; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmHibernateMapping; @@ -64,7 +66,7 @@ public MappingDocument( .setImplicitCatalogName( documentRoot.getCatalog() ) .setImplicitPackageName( documentRoot.getPackage() ) .setImplicitPropertyAccessorName( documentRoot.getDefaultAccess() ) -// .setImplicitCascadeStyleName( documentRoot.getDefaultCascade() ) + .setImplicitCascadeTypes( getCascadeTypes(documentRoot) ) .setEntitiesImplicitlyLazy( documentRoot.isDefaultLazy() ) .setAutoImportEnabled( documentRoot.isAutoImport() ) .setPluralAttributesImplicitlyLazy( documentRoot.isDefaultLazy() ) @@ -76,6 +78,23 @@ public MappingDocument( new TypeDefinitionRegistryStandardImpl( rootBuildingContext.getTypeDefinitionRegistry() ); } + private EnumSet getCascadeTypes(JaxbHbmHibernateMapping documentRoot) { + return switch ( documentRoot.getDefaultCascade() ) { + case "all" -> EnumSet.of( CascadeType.ALL ); + case "all-delete-orphan" -> EnumSet.of( CascadeType.ALL, CascadeType.DELETE_ORPHAN ); + case "persist" -> EnumSet.of( CascadeType.PERSIST ); + case "merge" -> EnumSet.of( CascadeType.MERGE ); + case "lock" -> EnumSet.of( CascadeType.LOCK ); + case "refresh" -> EnumSet.of( CascadeType.REFRESH ); + case "replicate" -> EnumSet.of( CascadeType.REPLICATE ); + case "evict" -> EnumSet.of( CascadeType.DETACH ); + case "delete" -> EnumSet.of( CascadeType.REMOVE ); + case "remove" -> EnumSet.of( CascadeType.REMOVE ); + case "delete-orphan" -> EnumSet.of( CascadeType.DELETE_ORPHAN ); + default -> null; + }; + } + public JaxbHbmHibernateMapping getDocumentRoot() { return documentRoot; } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java index 7194d44cf498..4290128e2a5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java @@ -2226,7 +2226,7 @@ private String toCascadeString(EnumSet defaultCascadeTypes) { else { buffer.append( ", " ); } - buffer.append( cascadeType.name().toLowerCase( Locale.ROOT ) ); + buffer.append( cascadeType.name().toLowerCase( Locale.ROOT ).replace( '_', '-' ) ); } return buffer.toString(); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hbm/cascade/DefaultCascadeBindingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hbm/cascade/DefaultCascadeBindingTest.java new file mode 100644 index 000000000000..e1ea76e4b0fb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hbm/cascade/DefaultCascadeBindingTest.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.hbm.cascade; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.orm.test.legacy.Holder; +import org.hibernate.testing.ServiceRegistryBuilder; +import org.hibernate.testing.orm.junit.BaseUnitTest; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@BaseUnitTest +class DefaultCascadeBindingTest { + + @Test + void testDefaultCascadeIsApplied() { + StandardServiceRegistry ssr = ServiceRegistryBuilder.buildServiceRegistry(); + + try { + MetadataSources ms = new MetadataSources( ssr ); + ms.addResource( "org/hibernate/orm/test/hbm/cascade/default-cascade.hbm.xml" ); + + Metadata metadata = ms.buildMetadata(); + + PersistentClass entityBinding = metadata.getEntityBinding( Holder.class.getName() ); + + // Default cascade + assertThat( entityBinding.getProperty( "ones" ).getCascadeStyle() ) + .hasToString( "[STYLE_ALL,STYLE_DELETE_ORPHAN]" ); + + // Explicit cascade + assertThat( entityBinding.getProperty( "fooArray" ).getCascadeStyle() ) + .hasToString( "STYLE_PERSIST" ); + } + finally { + ServiceRegistryBuilder.destroy( ssr ); + } + } +} diff --git a/hibernate-core/src/test/resources/org/hibernate/orm/test/hbm/cascade/default-cascade.hbm.xml b/hibernate-core/src/test/resources/org/hibernate/orm/test/hbm/cascade/default-cascade.hbm.xml new file mode 100644 index 000000000000..e1abef39eddf --- /dev/null +++ b/hibernate-core/src/test/resources/org/hibernate/orm/test/hbm/cascade/default-cascade.hbm.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +