HHH-10643 - Attribute 'foreignKeyDefinition' of @javax.persistence.ForeignKey ignored by schema exporter

This commit is contained in:
Matthias Kurz 2016-05-29 22:17:48 +02:00 committed by Vlad Mihalcea
parent 2abf9ddac4
commit 78de650efe
19 changed files with 408 additions and 18 deletions

View File

@ -2925,6 +2925,7 @@ public final class AnnotationBinder {
}
else if ( joinColumn != null ) {
value.setForeignKeyName( StringHelper.nullIfEmpty( joinColumn.foreignKey().name() ) );
value.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumn.foreignKey().foreignKeyDefinition() ) );
}
}

View File

@ -258,6 +258,7 @@ public class OneToOneSecondPass implements SecondPass {
}
else {
value.setForeignKeyName( StringHelper.nullIfEmpty( jpaFk.name() ) );
value.setForeignKeyDefinition( StringHelper.nullIfEmpty( jpaFk.foreignKeyDefinition() ) );
}
}
}

View File

@ -1132,17 +1132,20 @@ public abstract class CollectionBinder {
}
else {
key.setForeignKeyName( StringHelper.nullIfEmpty( collectionTableAnn.foreignKey().name() ) );
key.setForeignKeyDefinition( StringHelper.nullIfEmpty( collectionTableAnn.foreignKey().foreignKeyDefinition() ) );
}
}
else {
final JoinTable joinTableAnn = property.getAnnotation( JoinTable.class );
if ( joinTableAnn != null ) {
String foreignKeyName = joinTableAnn.foreignKey().name();
String foreignKeyDefinition = joinTableAnn.foreignKey().foreignKeyDefinition();
ConstraintMode foreignKeyValue = joinTableAnn.foreignKey().value();
if ( joinTableAnn.joinColumns().length != 0 ) {
final JoinColumn joinColumnAnn = joinTableAnn.joinColumns()[0];
if ( "".equals( foreignKeyName ) ) {
foreignKeyName = joinColumnAnn.foreignKey().name();
foreignKeyDefinition = joinColumnAnn.foreignKey().foreignKeyDefinition();
}
if ( foreignKeyValue != ConstraintMode.NO_CONSTRAINT ) {
foreignKeyValue = joinColumnAnn.foreignKey().value();
@ -1153,6 +1156,7 @@ public abstract class CollectionBinder {
}
else {
key.setForeignKeyName( StringHelper.nullIfEmpty( foreignKeyName ) );
key.setForeignKeyDefinition( StringHelper.nullIfEmpty( foreignKeyDefinition ) );
}
}
else {
@ -1163,6 +1167,7 @@ public abstract class CollectionBinder {
}
else {
key.setForeignKeyName( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().name() ) );
key.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().foreignKeyDefinition() ) );
}
}
}
@ -1342,11 +1347,13 @@ public abstract class CollectionBinder {
final JoinTable joinTableAnn = property.getAnnotation( JoinTable.class );
if ( joinTableAnn != null ) {
String foreignKeyName = joinTableAnn.inverseForeignKey().name();
String foreignKeyDefinition = joinTableAnn.inverseForeignKey().foreignKeyDefinition();
ConstraintMode foreignKeyValue = joinTableAnn.foreignKey().value();
if ( joinTableAnn.inverseJoinColumns().length != 0 ) {
final JoinColumn joinColumnAnn = joinTableAnn.inverseJoinColumns()[0];
if ( "".equals( foreignKeyName ) ) {
foreignKeyName = joinColumnAnn.foreignKey().name();
foreignKeyDefinition = joinColumnAnn.foreignKey().foreignKeyDefinition();
}
if ( foreignKeyValue != ConstraintMode.NO_CONSTRAINT ) {
foreignKeyValue = joinColumnAnn.foreignKey().value();
@ -1357,6 +1364,7 @@ public abstract class CollectionBinder {
}
else {
element.setForeignKeyName( StringHelper.nullIfEmpty( foreignKeyName ) );
element.setForeignKeyDefinition( StringHelper.nullIfEmpty( foreignKeyDefinition ) );
}
}
}

View File

@ -807,6 +807,7 @@ public class EntityBinder {
}
else {
( (SimpleValue) join.getKey() ).setForeignKeyName( StringHelper.nullIfEmpty( jpaSecondaryTable.foreignKey().name() ) );
( (SimpleValue) join.getKey() ).setForeignKeyDefinition( StringHelper.nullIfEmpty( jpaSecondaryTable.foreignKey().foreignKeyDefinition() ) );
}
}
}

View File

@ -2073,6 +2073,17 @@ public abstract class Dialect implements ConversionContext {
return res.toString();
}
public String getAddForeignKeyConstraintString(
String constraintName,
String foreignKeyDefinition) {
return new StringBuilder( 30 )
.append( " add constraint " )
.append( quote( constraintName ) )
.append( " " )
.append( foreignKeyDefinition )
.toString();
}
/**
* The syntax used to add a primary key constraint to a table.
*

View File

@ -111,6 +111,17 @@ public class InformixDialect extends Dialect {
return result.toString();
}
public String getAddForeignKeyConstraintString(
String constraintName,
String foreignKeyDefinition) {
return new StringBuilder( 30 )
.append( " add constraint " )
.append( foreignKeyDefinition )
.append( " constraint " )
.append( constraintName )
.toString();
}
/**
* Informix constraint name must be at the end.
* <p/>

View File

@ -161,6 +161,12 @@ public class SAPDBDialect extends Dialect {
return res.toString();
}
public String getAddForeignKeyConstraintString(
String constraintName,
String foreignKeyDefinition) {
return foreignKeyDefinition;
}
@Override
public String getAddPrimaryKeyConstraintString(String constraintName) {
return " primary key ";

View File

@ -64,6 +64,7 @@ public class DenormalizedTable extends Table {
),
fk.getColumns(),
fk.getReferencedEntityName(),
fk.getKeyDefinition(),
fk.getReferencedColumns()
);
}

View File

@ -22,6 +22,7 @@ import org.hibernate.internal.util.StringHelper;
public class ForeignKey extends Constraint {
private Table referencedTable;
private String referencedEntityName;
private String keyDefinition;
private boolean cascadeDeleteEnabled;
private List<Column> referencedColumns = new ArrayList<Column>();
private boolean creationEnabled = true;
@ -77,13 +78,23 @@ public class ForeignKey extends Constraint {
i++;
}
final String result = dialect.getAddForeignKeyConstraintString(
constraintName,
columnNames,
referencedTable.getQualifiedName( dialect, defaultCatalog, defaultSchema ),
referencedColumnNames,
isReferenceToPrimaryKey()
);
final String result = keyDefinition != null ?
dialect.getAddForeignKeyConstraintString(
constraintName,
keyDefinition
) :
dialect.getAddForeignKeyConstraintString(
constraintName,
columnNames,
referencedTable.getQualifiedName(
dialect,
defaultCatalog,
defaultSchema
),
referencedColumnNames,
isReferenceToPrimaryKey()
);
return cascadeDeleteEnabled && dialect.supportsCascadeDelete()
? result + " on delete cascade"
: result;
@ -153,6 +164,14 @@ public class ForeignKey extends Constraint {
this.referencedEntityName = referencedEntityName;
}
public String getKeyDefinition() {
return keyDefinition;
}
public void setKeyDefinition(String keyDefinition) {
this.keyDefinition = keyDefinition;
}
public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) {
final StringBuilder buf = new StringBuilder( "alter table " );
buf.append( getTable().getQualifiedName( dialect, defaultCatalog, defaultSchema ) );

View File

@ -74,6 +74,7 @@ public class ManyToOne extends ToOne {
getForeignKeyName(),
getConstraintColumns(),
( (EntityType) getType() ).getAssociatedEntityName(),
getForeignKeyDefinition(),
refColumns
);
fk.setCascadeDeleteEnabled(isCascadeDeleteEnabled() );

View File

@ -73,6 +73,7 @@ public class SimpleValue implements KeyValue {
private String nullValue;
private Table table;
private String foreignKeyName;
private String foreignKeyDefinition;
private boolean alternateUniqueKey;
private boolean cascadeDeleteEnabled;
@ -193,7 +194,7 @@ public class SimpleValue implements KeyValue {
@Override
public void createForeignKeyOfEntity(String entityName) {
if ( !hasFormula() && !"none".equals(getForeignKeyName())) {
ForeignKey fk = table.createForeignKey( getForeignKeyName(), getConstraintColumns(), entityName );
ForeignKey fk = table.createForeignKey( getForeignKeyName(), getConstraintColumns(), entityName, getForeignKeyDefinition() );
fk.setCascadeDeleteEnabled(cascadeDeleteEnabled);
}
}
@ -347,6 +348,14 @@ public class SimpleValue implements KeyValue {
public void setForeignKeyName(String foreignKeyName) {
this.foreignKeyName = foreignKeyName;
}
public String getForeignKeyDefinition() {
return foreignKeyDefinition;
}
public void setForeignKeyDefinition(String foreignKeyDefinition) {
this.foreignKeyDefinition = foreignKeyDefinition;
}
public boolean isAlternateUniqueKey() {
return alternateUniqueKey;

View File

@ -681,14 +681,15 @@ public class Table implements RelationalModel, Serializable, Exportable {
public void createForeignKeys() {
}
public ForeignKey createForeignKey(String keyName, List keyColumns, String referencedEntityName) {
return createForeignKey( keyName, keyColumns, referencedEntityName, null );
public ForeignKey createForeignKey(String keyName, List keyColumns, String referencedEntityName, String keyDefinition) {
return createForeignKey( keyName, keyColumns, referencedEntityName, keyDefinition, null );
}
public ForeignKey createForeignKey(
String keyName,
List keyColumns,
String referencedEntityName,
String keyDefinition,
List referencedColumns) {
final ForeignKeyKey key = new ForeignKeyKey( keyColumns, referencedEntityName, referencedColumns );
@ -697,6 +698,7 @@ public class Table implements RelationalModel, Serializable, Exportable {
fk = new ForeignKey();
fk.setTable( this );
fk.setReferencedEntityName( referencedEntityName );
fk.setKeyDefinition(keyDefinition);
fk.addColumns( keyColumns.iterator() );
if ( referencedColumns != null ) {
fk.addReferencedColumns( referencedColumns.iterator() );

View File

@ -103,13 +103,18 @@ public class StandardForeignKeyExporter implements Exporter<ForeignKey> {
final StringBuilder buffer = new StringBuilder( "alter table " )
.append( sourceTableName )
.append(
dialect.getAddForeignKeyConstraintString(
foreignKey.getName(),
columnNames,
targetTableName,
targetColumnNames,
foreignKey.isReferenceToPrimaryKey()
)
foreignKey.getKeyDefinition() != null ?
dialect.getAddForeignKeyConstraintString(
foreignKey.getName(),
foreignKey.getKeyDefinition()
) :
dialect.getAddForeignKeyConstraintString(
foreignKey.getName(),
columnNames,
targetTableName,
targetColumnNames,
foreignKey.isReferenceToPrimaryKey()
)
);
if ( dialect.supportsCascadeDelete() ) {

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.schemaupdate;
package org.hibernate.test.schemaupdate.foreignkeys;
import java.util.EnumSet;
import javax.persistence.Entity;

View File

@ -0,0 +1,81 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.schemaupdate.foreignkeys.definition;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.EnumSet;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.schema.TargetType;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
/**
* @author Vlad MIhalcea
*/
public abstract class AbstractForeignKeyDefinitionTest extends BaseUnitTestCase {
private File output;
private StandardServiceRegistry ssr;
private MetadataImplementor metadata;
@Before
public void setUp() throws IOException {
output = File.createTempFile( "update_script", ".sql" );
output.deleteOnExit();
ssr = new StandardServiceRegistryBuilder().build();
createSchema();
}
@After
public void tearsDown() {
StandardServiceRegistryBuilder.destroy( ssr );
}
private void createSchema() {
final MetadataSources metadataSources = new MetadataSources( ssr );
for ( Class c : getAnnotatedClasses() ) {
metadataSources.addAnnotatedClass( c );
}
metadata = (MetadataImplementor) metadataSources.buildMetadata();
metadata.validate();
new SchemaExport()
.setHaltOnError( true )
.setOutputFile( output.getAbsolutePath() )
.setFormat( false )
.create( EnumSet.of( TargetType.SCRIPT ), metadata );
}
protected abstract Class<?>[] getAnnotatedClasses();
protected abstract boolean validate(String fileContent);
@Test
@TestForIssue(jiraKey = "HHH-10643")
public void testForeignKeyDefinitionOverridesDefaultNamingStrategy()
throws Exception {
String fileContent = new String( Files.readAllBytes( output.toPath() ) );
assertTrue( "Script file : " + fileContent, validate( fileContent ) );
}
}

View File

@ -0,0 +1,54 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.schemaupdate.foreignkeys.definition;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.testing.RequiresDialect;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(value = H2Dialect.class)
public class ForeignKeyDefinitionManyToOneTest
extends AbstractForeignKeyDefinitionTest {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Box.class,
Thing.class,
};
}
@Entity(name = "Box")
public static class Box {
@Id
public Integer id;
@ManyToOne
@JoinColumn(foreignKey = @ForeignKey(name = "thingy", foreignKeyDefinition = "foreign key /* FK */ (thing_id) references Thing"))
public Thing thing;
}
@Entity(name = "Thing")
public static class Thing {
@Id
public Integer id;
}
@Override
protected boolean validate(String fileContent) {
return fileContent.contains( "/* FK */" );
}
}

View File

@ -0,0 +1,69 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.schemaupdate.foreignkeys.definition;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.testing.RequiresDialect;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(value = H2Dialect.class)
public class ForeignKeyDefinitionOneToManyJoinTableTest
extends AbstractForeignKeyDefinitionTest {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Box.class,
Thing.class,
};
}
@Entity(name = "Box")
public static class Box {
@Id
public Integer id;
}
@Entity(name = "Thing")
public static class Thing {
@Id
public Integer id;
@OneToMany
@JoinTable(name = "box_thing",
joinColumns = @JoinColumn(name = "box_id"),
inverseJoinColumns = @JoinColumn(name = "thing_id"),
foreignKey = @ForeignKey(
name = "thingy",
foreignKeyDefinition = "foreign key /* Thing_FK */ (thing_id) references Thing"
),
inverseForeignKey = @ForeignKey(
name = "boxy",
foreignKeyDefinition = "foreign key /* Box_FK */ (box_id) references Box"
)
)
public List<Thing> things = new ArrayList<>();
}
@Override
protected boolean validate(String fileContent) {
return fileContent.contains( "/* Thing_FK */" ) && fileContent.contains(
"/* Box_FK */" );
}
}

View File

@ -0,0 +1,54 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.schemaupdate.foreignkeys.definition;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.testing.RequiresDialect;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(value = H2Dialect.class)
public class ForeignKeyDefinitionOneToOneTest
extends AbstractForeignKeyDefinitionTest {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Box.class,
Thing.class,
};
}
@Entity(name = "Box")
public static class Box {
@Id
public Integer id;
@OneToOne
@JoinColumn(foreignKey = @ForeignKey(name = "thingy", foreignKeyDefinition = "foreign key /* FK */ (thing_id) references Thing"))
public Thing thing;
}
@Entity(name = "Thing")
public static class Thing {
@Id
public Integer id;
}
@Override
protected boolean validate(String fileContent) {
return fileContent.contains( "/* FK */" );
}
}

View File

@ -0,0 +1,56 @@
/*
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.schemaupdate.foreignkeys.definition;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.SecondaryTable;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.testing.RequiresDialect;
/**
* @author Vlad Mihalcea
*/
@RequiresDialect(value = H2Dialect.class)
public class ForeignKeyDefinitionSecondaryTableTest
extends AbstractForeignKeyDefinitionTest {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
User.class,
};
}
@Entity(name = "Users")
@SecondaryTable(name = "User_details", foreignKey = @ForeignKey(name = "secondary", foreignKeyDefinition = "foreign key /* FK */ (id) references Users"))
public class User {
@Id
@GeneratedValue
private int id;
private String emailAddress;
@Column(name = "SECURITY_USERNAME", table = "User_details")
private String username;
@Column(name = "SECURITY_PASSWORD", table = "User_details")
private String password;
}
@Override
protected boolean validate(String fileContent) {
return fileContent.contains( "/* FK */" );
}
}