diff --git a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc index 07432ccf02..2393631ee3 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc @@ -904,6 +904,9 @@ If enabled, allows schema update and validation to support synonyms. Due to the `*hibernate.hbm2ddl.extra_physical_table_types*` (e.g. `BASE TABLE`):: Identifies a comma-separated list of values to specify extra table types, other than the default `TABLE` value, to recognize as defining a physical table by schema update, creation and validation. +`*hibernate.hbm2ddl.default_constraint_mode*` (`CONSTRAINT` (default value) or `NO_CONSTRAINT`):: +Default `javax.persistence.ConstraintMode` for foreign key mapping if `PROVIDER_DEFAULT` strategy used. + `*hibernate.schema_update.unique_constraint_strategy*` (e.g. `DROP_RECREATE_QUIETLY`, `RECREATE_QUIETLY`, `SKIP`):: Unique columns and unique keys both use unique constraints in most dialects. `SchemaUpdate` needs to create these constraints, but DBs support for finding existing constraints is extremely inconsistent. diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuilderImpl.java index ea9edffb91..f5ebda41ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuilderImpl.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import javax.persistence.AttributeConverter; +import javax.persistence.ConstraintMode; import javax.persistence.SharedCacheMode; import org.hibernate.HibernateException; @@ -330,6 +331,11 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont return this; } + public MetadataBuilder noConstraintByDefault() { + this.options.noConstraintByDefault = true; + return this; + } + @Override public MetadataBuilder applySqlFunction(String functionName, SQLFunction function) { this.bootstrapContext.addSqlFunction( functionName, function ); @@ -610,6 +616,7 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont private boolean implicitlyForceDiscriminatorInSelect; private boolean useNationalizedCharacterData; private boolean specjProprietarySyntaxEnabled; + private boolean noConstraintByDefault; private ArrayList sourceProcessOrdering; private IdGeneratorInterpreterImpl idGenerationTypeInterpreter = new IdGeneratorInterpreterImpl(); @@ -702,6 +709,12 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont false ); + this.noConstraintByDefault = ConstraintMode.NO_CONSTRAINT.name().equalsIgnoreCase( configService.getSetting( + AvailableSettings.HBM2DDL_DEFAULT_CONSTRAINT_MODE, + String.class, + null + ) ); + this.implicitNamingStrategy = strategySelector.resolveDefaultableStrategy( ImplicitNamingStrategy.class, configService.getSettings().get( AvailableSettings.IMPLICIT_NAMING_STRATEGY ), @@ -882,6 +895,11 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont return specjProprietarySyntaxEnabled; } + @Override + public boolean isNoConstraintByDefault() { + return noConstraintByDefault; + } + @Override public List getSourceProcessOrdering() { return sourceProcessOrdering; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadataBuildingOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadataBuildingOptions.java index 4a405ca585..3b8be18e37 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadataBuildingOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadataBuildingOptions.java @@ -158,6 +158,11 @@ public abstract class AbstractDelegatingMetadataBuildingOptions implements Metad return delegate.isSpecjProprietarySyntaxEnabled(); } + @Override + public boolean isNoConstraintByDefault() { + return delegate.isNoConstraintByDefault(); + } + @Override public List getSourceProcessOrdering() { return delegate.getSourceProcessOrdering(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuildingOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuildingOptions.java index 9e552c41e3..65a964e924 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuildingOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataBuildingOptions.java @@ -230,6 +230,16 @@ public interface MetadataBuildingOptions { boolean isSpecjProprietarySyntaxEnabled(); + /** + * Should we create constraint by default? + * + * @see javax.persistence.ConstraintMode#PROVIDER_DEFAULT + * @see org.hibernate.cfg.AvailableSettings#DEFAULT_CONSTRAINT_MODE + * + * @return {@code true} if not create constraint by default; {@code false} otherwise. + */ + boolean isNoConstraintByDefault(); + /** * Retrieve the ordering in which sources should be processed. * diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index 2336265597..50e5730652 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -146,6 +146,8 @@ import org.hibernate.cfg.annotations.QueryBinder; import org.hibernate.cfg.annotations.SimpleValueBinder; import org.hibernate.cfg.annotations.TableBinder; import org.hibernate.engine.OptimisticLockStyle; +import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.internal.CoreMessageLogger; @@ -707,15 +709,17 @@ public final class AnnotationBinder { else { final PrimaryKeyJoinColumn pkJoinColumn = clazzToProcess.getAnnotation( PrimaryKeyJoinColumn.class ); final PrimaryKeyJoinColumns pkJoinColumns = clazzToProcess.getAnnotation( PrimaryKeyJoinColumns.class ); - - if ( pkJoinColumns != null && pkJoinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) { + final boolean noConstraintByDefault = context.getBuildingOptions().isNoConstraintByDefault(); + if ( pkJoinColumns != null && ( pkJoinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT + || pkJoinColumns.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) { // don't apply a constraint based on ConstraintMode key.setForeignKeyName( "none" ); } else if ( pkJoinColumns != null && !StringHelper.isEmpty( pkJoinColumns.foreignKey().name() ) ) { key.setForeignKeyName( pkJoinColumns.foreignKey().name() ); } - else if ( pkJoinColumn != null && pkJoinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) { + else if ( pkJoinColumn != null && ( pkJoinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT + || pkJoinColumn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault )) { // don't apply a constraint based on ConstraintMode key.setForeignKeyName( "none" ); } @@ -3093,7 +3097,8 @@ public final class AnnotationBinder { property, propertyHolder.getOverriddenForeignKey( StringHelper.qualify( propertyHolder.getPath(), propertyName ) ), joinColumn, - joinColumns + joinColumns, + context ); String path = propertyHolder.getPath() + "." + propertyName; @@ -3439,9 +3444,13 @@ public final class AnnotationBinder { XProperty property, javax.persistence.ForeignKey fkOverride, JoinColumn joinColumn, - JoinColumns joinColumns) { - if ( ( joinColumn != null && joinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) - || ( joinColumns != null && joinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) ) { + JoinColumns joinColumns, + MetadataBuildingContext context) { + final boolean noConstraintByDefault = context.getBuildingOptions().isNoConstraintByDefault(); + if ( ( joinColumn != null && ( joinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT + || joinColumn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) + || ( joinColumns != null && ( joinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT + || joinColumns.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) ) { value.setForeignKeyName( "none" ); } else { @@ -3450,7 +3459,8 @@ public final class AnnotationBinder { value.setForeignKeyName( fk.name() ); } else { - if ( fkOverride != null && fkOverride.value() == ConstraintMode.NO_CONSTRAINT ) { + if ( fkOverride != null && ( fkOverride.value() == ConstraintMode.NO_CONSTRAINT + || fkOverride.value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) { value.setForeignKeyName( "none" ); } else if ( fkOverride != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 096b563196..41e8374e06 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -1529,6 +1529,19 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { */ String HBM2DDL_HALT_ON_ERROR = "hibernate.hbm2ddl.halt_on_error"; + /** + *

+ * This setting is used when you use {@link javax.persistence.ConstraintMode#PROVIDER_DEFAULT} strategy for foreign key mapping. + * valid value is {@code CONSTRAINT} and {@code NO_CONSTRAINT}. + *

+ *

+ * The default value is CONSTRAINT. + *

+ * + * @since 5.4 + */ + String HBM2DDL_DEFAULT_CONSTRAINT_MODE = "hibernate.hbm2ddl.default_constraint_mode"; + String JMX_ENABLED = "hibernate.jmx.enabled"; String JMX_PLATFORM_SERVER = "hibernate.jmx.usePlatformServer"; String JMX_AGENT_ID = "hibernate.jmx.agentId"; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java index 5924453c99..3c7883424d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java @@ -103,7 +103,8 @@ public class OneToOneSecondPass implements SecondPass { inferredData.getProperty(), inferredData.getProperty().getAnnotation( javax.persistence.ForeignKey.class ), inferredData.getProperty().getAnnotation( JoinColumn.class ), - inferredData.getProperty().getAnnotation( JoinColumns.class ) + inferredData.getProperty().getAnnotation( JoinColumns.class), + buildingContext ); PropertyBinder binder = new PropertyBinder(); @@ -275,7 +276,8 @@ public class OneToOneSecondPass implements SecondPass { * Note:
*
    *
  • From the mappedBy side we should not create the PK nor the FK, this is handled from the other side.
  • - *
  • This method is a dirty dupe of EntityBinder.bindSecondaryTable. + *
  • This method is a dirty dupe of EntityBinder.bindSecondaryTable
  • . + *
*

*/ private Join buildJoinFromMappedBySide(PersistentClass persistentClass, Property otherSideProperty, Join originalJoin) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java index c577158085..aefb7cb5e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java @@ -32,7 +32,6 @@ import javax.persistence.OneToMany; import org.hibernate.AnnotationException; import org.hibernate.FetchMode; -import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.Cache; @@ -89,11 +88,9 @@ import org.hibernate.cfg.PropertyHolderBuilder; import org.hibernate.cfg.PropertyInferredData; import org.hibernate.cfg.PropertyPreloadedData; import org.hibernate.cfg.SecondPass; -import org.hibernate.criterion.Junction; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.mapping.Any; @@ -1173,6 +1170,7 @@ public abstract class CollectionBinder { Collection collValue, Ejb3JoinColumn[] joinColumns, boolean cascadeDeleteEnabled, + boolean noConstraintByDefault, XProperty property, PropertyHolder propertyHolder, MetadataBuildingContext buildingContext) { @@ -1217,7 +1215,8 @@ public abstract class CollectionBinder { else { final CollectionTable collectionTableAnn = property.getAnnotation( CollectionTable.class ); if ( collectionTableAnn != null ) { - if ( collectionTableAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) { + if ( collectionTableAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT + || collectionTableAnn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) { key.setForeignKeyName( "none" ); } else { @@ -1248,7 +1247,8 @@ public abstract class CollectionBinder { foreignKeyValue = joinColumnAnn.foreignKey().value(); } } - if ( foreignKeyValue == ConstraintMode.NO_CONSTRAINT ) { + if ( foreignKeyValue == ConstraintMode.NO_CONSTRAINT + || foreignKeyValue == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) { key.setForeignKeyName( "none" ); } else { @@ -1260,7 +1260,8 @@ public abstract class CollectionBinder { final javax.persistence.ForeignKey fkOverride = propertyHolder.getOverriddenForeignKey( StringHelper.qualify( propertyHolder.getPath(), property.getName() ) ); - if ( fkOverride != null && fkOverride.value() == ConstraintMode.NO_CONSTRAINT ) { + if ( fkOverride != null && ( fkOverride.value() == ConstraintMode.NO_CONSTRAINT || + fkOverride.value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) ) { key.setForeignKeyName( "none" ); } else if ( fkOverride != null ) { @@ -1270,7 +1271,8 @@ public abstract class CollectionBinder { else { final JoinColumn joinColumnAnn = property.getAnnotation( JoinColumn.class ); if ( joinColumnAnn != null ) { - if ( joinColumnAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) { + if ( joinColumnAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT + || joinColumnAnn.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) { key.setForeignKeyName( "none" ); } else { @@ -1466,7 +1468,8 @@ public abstract class CollectionBinder { foreignKeyValue = joinColumnAnn.foreignKey().value(); } } - if ( joinTableAnn.inverseForeignKey().value() == ConstraintMode.NO_CONSTRAINT ) { + if ( joinTableAnn.inverseForeignKey().value() == ConstraintMode.NO_CONSTRAINT + || joinTableAnn.inverseForeignKey().value() == ConstraintMode.PROVIDER_DEFAULT && buildingContext.getBuildingOptions().isNoConstraintByDefault() ) { element.setForeignKeyName( "none" ); } else { @@ -1702,7 +1705,8 @@ public abstract class CollectionBinder { catch (AnnotationException ex) { throw new AnnotationException( "Unable to map collection " + collValue.getOwner().getClassName() + "." + property.getName(), ex ); } - SimpleValue key = buildCollectionKey( collValue, joinColumns, cascadeDeleteEnabled, property, propertyHolder, buildingContext ); + SimpleValue key = buildCollectionKey( collValue, joinColumns, cascadeDeleteEnabled, + buildingContext.getBuildingOptions().isNoConstraintByDefault(), property, propertyHolder, buildingContext ); if ( property.isAnnotationPresent( ElementCollection.class ) && joinColumns.length > 0 ) { joinColumns[0].setJPA2ElementCollection( true ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java index 90a55a048d..ff12ec196c 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java @@ -985,7 +985,9 @@ public class EntityBinder { else { javax.persistence.SecondaryTable jpaSecondaryTable = findMatchingSecondaryTable( join ); if ( jpaSecondaryTable != null ) { - if ( jpaSecondaryTable.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) { + final boolean noConstraintByDefault = context.getBuildingOptions().isNoConstraintByDefault(); + if ( jpaSecondaryTable.foreignKey().value() == ConstraintMode.NO_CONSTRAINT + || jpaSecondaryTable.foreignKey().value() == ConstraintMode.PROVIDER_DEFAULT && noConstraintByDefault ) { ( (SimpleValue) join.getKey() ).setForeignKeyName( "none" ); } else { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java index e4c16b9a37..00a3334677 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java @@ -347,7 +347,8 @@ public class MapBinder extends CollectionBinder { if ( element != null ) { final javax.persistence.ForeignKey foreignKey = getMapKeyForeignKey( property ); if ( foreignKey != null ) { - if ( foreignKey.value() == ConstraintMode.NO_CONSTRAINT ) { + if ( foreignKey.value() == ConstraintMode.NO_CONSTRAINT + || foreignKey.value() == ConstraintMode.PROVIDER_DEFAULT && getBuildingContext().getBuildingOptions().isNoConstraintByDefault() ) { element.setForeignKeyName( "none" ); } else { diff --git a/hibernate-core/src/test/java/org/hibernate/test/foreignkeys/disabled/DefaultConstraintModeTest.java b/hibernate-core/src/test/java/org/hibernate/test/foreignkeys/disabled/DefaultConstraintModeTest.java new file mode 100644 index 0000000000..86ab995be1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/foreignkeys/disabled/DefaultConstraintModeTest.java @@ -0,0 +1,73 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.foreignkeys.disabled; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +import java.util.stream.StreamSupport; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Environment; +import org.hibernate.mapping.Table; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Test; + +/** + * @author Yanming Zhou + */ +public class DefaultConstraintModeTest extends BaseUnitTestCase { + + private static final String TABLE_NAME = "TestEntity"; + + @Test + @TestForIssue(jiraKey = "HHH-14253") + public void testForeignKeyShouldNotBeCreated() { + testForeignKeyCreation(false); + } + + @Test + @TestForIssue(jiraKey = "HHH-14253") + public void testForeignKeyShouldBeCreated() { + testForeignKeyCreation(true); + } + + private void testForeignKeyCreation(boolean created) { + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() + .applySetting(Environment.HBM2DDL_DEFAULT_CONSTRAINT_MODE, created ? "CONSTRAINT" : "NO_CONSTRAINT").build(); + Metadata metadata = new MetadataSources(ssr).addAnnotatedClass(TestEntity.class).buildMetadata(); + assertThat(findTable(metadata, TABLE_NAME).getForeignKeys().isEmpty(), is(!created)); + } + + private static Table findTable(Metadata metadata, String tableName) { + return StreamSupport.stream(metadata.getDatabase().getNamespaces().spliterator(), false) + .flatMap(namespace -> namespace.getTables().stream()).filter(t -> t.getName().equals(tableName)) + .findFirst().orElse(null); + } + + @Entity + @javax.persistence.Table(name = TABLE_NAME) + public static class TestEntity { + + @Id + private Long id; + + @ManyToOne + @JoinColumn + private TestEntity mate; + + } +}