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;
+
+ }
+}