HHH-9704 - Complete HHH-8805 work on 5.0;

HHH-9709 - JPA @ForeignKey not consistently applied from annotation binding
This commit is contained in:
Steve Ebersole 2015-04-02 17:02:23 -05:00
parent 4c690a8839
commit 22ca7125f7
10 changed files with 428 additions and 36 deletions

View File

@ -40,6 +40,7 @@ import javax.persistence.Basic;
import javax.persistence.Cacheable;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ConstraintMode;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
@ -2903,17 +2904,20 @@ public final class AnnotationBinder {
value.setTypeName( inferredData.getClassOrElementName() );
final String propertyName = inferredData.getPropertyName();
value.setTypeUsingReflection( propertyHolder.getClassName(), propertyName );
String fkName = null;
if ( joinColumn != null && joinColumn.foreignKey() != null ) {
fkName = joinColumn.foreignKey().name();
if ( joinColumn != null
&& joinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) {
// not ideal...
value.setForeignKeyName( "none" );
}
if ( StringHelper.isEmpty( fkName ) ) {
ForeignKey fk = property.getAnnotation( ForeignKey.class );
fkName = fk != null ? fk.name() : "";
}
if ( !StringHelper.isEmpty( fkName ) ) {
value.setForeignKeyName( fkName );
else {
final ForeignKey fk = property.getAnnotation( ForeignKey.class );
if ( fk != null && StringHelper.isEmpty( fk.name() ) ) {
value.setForeignKeyName( fk.name() );
}
else if ( joinColumn != null ) {
value.setForeignKeyName( joinColumn.foreignKey().name() );
}
}
String path = propertyHolder.getPath() + "." + propertyName;

View File

@ -26,6 +26,8 @@ package org.hibernate.cfg;
import java.util.Iterator;
import java.util.Map;
import javax.persistence.ConstraintMode;
import org.hibernate.AnnotationException;
import org.hibernate.MappingException;
import org.hibernate.annotations.ForeignKey;
@ -261,9 +263,22 @@ public class OneToOneSecondPass implements SecondPass {
);
}
}
ForeignKey fk = inferredData.getProperty().getAnnotation( ForeignKey.class );
String fkName = fk != null ? fk.name() : "";
if ( !BinderHelper.isEmptyAnnotationValue( fkName ) ) value.setForeignKeyName( fkName );
final ForeignKey fk = inferredData.getProperty().getAnnotation( ForeignKey.class );
if ( fk != null && !BinderHelper.isEmptyAnnotationValue( fk.name() ) ) {
value.setForeignKeyName( fk.name() );
}
else {
final javax.persistence.ForeignKey jpaFk = inferredData.getProperty().getAnnotation( javax.persistence.ForeignKey.class );
if ( jpaFk != null ) {
if ( jpaFk.value() == ConstraintMode.NO_CONSTRAINT ) {
value.setForeignKeyName( "none" );
}
else {
value.setForeignKeyName( StringHelper.nullIfEmpty( jpaFk.name() ) );
}
}
}
}
/**

View File

@ -30,6 +30,8 @@ import java.util.Map;
import java.util.Properties;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.CollectionTable;
import javax.persistence.ConstraintMode;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.FetchType;
@ -1106,9 +1108,37 @@ public abstract class CollectionBinder {
key.setUpdateable( joinColumns.length == 0 || joinColumns[0].isUpdatable() );
key.setCascadeDeleteEnabled( cascadeDeleteEnabled );
collValue.setKey( key );
ForeignKey fk = property != null ? property.getAnnotation( ForeignKey.class ) : null;
String fkName = fk != null ? fk.name() : "";
if ( !BinderHelper.isEmptyAnnotationValue( fkName ) ) key.setForeignKeyName( fkName );
if ( property != null ) {
final ForeignKey fk = property.getAnnotation( ForeignKey.class );
if ( fk != null && !BinderHelper.isEmptyAnnotationValue( fk.name() ) ) {
key.setForeignKeyName( fk.name() );
}
else {
final CollectionTable collectionTableAnn = property.getAnnotation( CollectionTable.class );
if ( collectionTableAnn != null ) {
if ( collectionTableAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) {
key.setForeignKeyName( "none" );
}
else {
key.setForeignKeyName( StringHelper.nullIfEmpty( collectionTableAnn.foreignKey().name() ) );
}
}
else {
final JoinTable joinTableAnn = property.getAnnotation( JoinTable.class );
if ( joinTableAnn != null ) {
if ( joinTableAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) {
key.setForeignKeyName( "none" );
}
else {
key.setForeignKeyName( StringHelper.nullIfEmpty( joinTableAnn.foreignKey().name() ) );
}
}
}
}
}
return key;
}
@ -1135,12 +1165,20 @@ public abstract class CollectionBinder {
boolean isCollectionOfEntities = collectionEntity != null;
ManyToAny anyAnn = property.getAnnotation( ManyToAny.class );
if (LOG.isDebugEnabled()) {
if ( LOG.isDebugEnabled() ) {
String path = collValue.getOwnerEntityName() + "." + joinColumns[0].getPropertyName();
if (isCollectionOfEntities && unique) LOG.debugf("Binding a OneToMany: %s through an association table", path);
else if (isCollectionOfEntities) LOG.debugf("Binding as ManyToMany: %s", path);
else if (anyAnn != null) LOG.debugf("Binding a ManyToAny: %s", path);
else LOG.debugf("Binding a collection of element: %s", path);
if ( isCollectionOfEntities && unique ) {
LOG.debugf("Binding a OneToMany: %s through an association table", path);
}
else if (isCollectionOfEntities) {
LOG.debugf("Binding as ManyToMany: %s", path);
}
else if (anyAnn != null) {
LOG.debugf("Binding a ManyToAny: %s", path);
}
else {
LOG.debugf("Binding a collection of element: %s", path);
}
}
//check for user error
if ( !isCollectionOfEntities ) {
@ -1186,14 +1224,11 @@ public abstract class CollectionBinder {
otherSideProperty = collectionEntity.getRecursiveProperty( joinColumns[0].getMappedBy() );
}
catch (MappingException e) {
StringBuilder error = new StringBuilder( 80 );
error.append( "mappedBy reference an unknown target entity property: " )
.append( collType ).append( "." ).append( joinColumns[0].getMappedBy() )
.append( " in " )
.append( collValue.getOwnerEntityName() )
.append( "." )
.append( joinColumns[0].getPropertyName() );
throw new AnnotationException( error.toString() );
throw new AnnotationException(
"mappedBy reference an unknown target entity property: "
+ collType + "." + joinColumns[0].getMappedBy() + " in "
+ collValue.getOwnerEntityName() + "." + joinColumns[0].getPropertyName()
);
}
Table table;
if ( otherSideProperty.getValue() instanceof Collection ) {
@ -1267,10 +1302,21 @@ public abstract class CollectionBinder {
buildOrderByClauseFromHql( hqlOrderBy, collectionEntity, collValue.getRole() )
);
}
final ForeignKey fk = property.getAnnotation( ForeignKey.class );
String fkName = fk != null ? fk.inverseName() : "";
if ( !BinderHelper.isEmptyAnnotationValue( fkName ) ) {
element.setForeignKeyName( fkName );
if ( fk != null && !BinderHelper.isEmptyAnnotationValue( fk.name() ) ) {
element.setForeignKeyName( fk.name() );
}
else {
final JoinTable joinTableAnn = property.getAnnotation( JoinTable.class );
if ( joinTableAnn != null ) {
if ( joinTableAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) {
element.setForeignKeyName( "none" );
}
else {
element.setForeignKeyName( StringHelper.nullIfEmpty( joinTableAnn.inverseForeignKey().name() ) );
}
}
}
}
else if ( anyAnn != null ) {

View File

@ -28,6 +28,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import javax.persistence.Access;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
@ -783,10 +784,44 @@ public class EntityBinder {
}
private void setFKNameIfDefined(Join join) {
// just awful..
org.hibernate.annotations.Table matchingTable = findMatchingComplimentTableAnnotation( join );
if ( matchingTable != null && !BinderHelper.isEmptyAnnotationValue( matchingTable.foreignKey().name() ) ) {
( (SimpleValue) join.getKey() ).setForeignKeyName( matchingTable.foreignKey().name() );
}
else {
javax.persistence.SecondaryTable jpaSecondaryTable = findMatchingSecondaryTable( join );
if ( jpaSecondaryTable != null ) {
if ( jpaSecondaryTable.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) {
( (SimpleValue) join.getKey() ).setForeignKeyName( "none" );
}
else {
( (SimpleValue) join.getKey() ).setForeignKeyName( StringHelper.nullIfEmpty( jpaSecondaryTable.foreignKey().name() ) );
}
}
}
}
private SecondaryTable findMatchingSecondaryTable(Join join) {
final String nameToMatch = join.getTable().getQuotedName();
SecondaryTable secondaryTable = annotatedClass.getAnnotation( SecondaryTable.class );
if ( secondaryTable != null && nameToMatch.equals( secondaryTable.name() ) ) {
return secondaryTable;
}
SecondaryTables secondaryTables = annotatedClass.getAnnotation( SecondaryTables.class );
if ( secondaryTables != null ) {
for ( SecondaryTable secondaryTable2 : secondaryTables.value() ) {
if ( secondaryTable != null && nameToMatch.equals( secondaryTable.name() ) ) {
return secondaryTable;
}
}
}
return null;
}
private org.hibernate.annotations.Table findMatchingComplimentTableAnnotation(Join join) {
@ -986,6 +1021,7 @@ public class EntityBinder {
secondaryTables.put( table.getQuotedName(), join );
secondaryTableJoins.put( table.getQuotedName(), joinColumns );
}
return join;
}

View File

@ -91,6 +91,10 @@ public final class StringHelper {
return buf.toString();
}
public static String join(String separator, Iterable objects) {
return join( separator, objects.iterator() );
}
public static String[] add(String[] x, String sep, String[] y) {
final String[] result = new String[x.length];
for ( int i = 0; i < x.length; i++ ) {

View File

@ -39,6 +39,8 @@ import org.hibernate.boot.model.relational.QualifiedTableName;
import org.hibernate.boot.model.relational.Schema;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.tool.hbm2ddl.ColumnMetadata;
import org.hibernate.tool.hbm2ddl.TableMetadata;
import org.hibernate.tool.schema.extract.spi.ColumnInformation;
@ -62,7 +64,7 @@ public class Table implements RelationalModel, Serializable, Exportable {
private Map columns = new LinkedHashMap();
private KeyValue idValue;
private PrimaryKey primaryKey;
private Map foreignKeys = new LinkedHashMap();
private Map<ForeignKeyKey, ForeignKey> foreignKeys = new LinkedHashMap<ForeignKeyKey, ForeignKey>();
private Map<String, Index> indexes = new LinkedHashMap<String, Index>();
private Map<String,UniqueKey> uniqueKeys = new LinkedHashMap<String,UniqueKey>();
private int uniqueInteger;
@ -277,6 +279,10 @@ public class Table implements RelationalModel, Serializable, Exportable {
return foreignKeys.values().iterator();
}
public Map<ForeignKeyKey, ForeignKey> getForeignKeys() {
return Collections.unmodifiableMap( foreignKeys );
}
public Iterator<UniqueKey> getUniqueKeyIterator() {
return getUniqueKeys().values().iterator();
}
@ -693,9 +699,9 @@ public class Table implements RelationalModel, Serializable, Exportable {
List keyColumns,
String referencedEntityName,
List referencedColumns) {
Object key = new ForeignKeyKey( keyColumns, referencedEntityName, referencedColumns );
final ForeignKeyKey key = new ForeignKeyKey( keyColumns, referencedEntityName, referencedColumns );
ForeignKey fk = (ForeignKey) foreignKeys.get( key );
ForeignKey fk = foreignKeys.get( key );
if ( fk == null ) {
fk = new ForeignKey();
fk.setTable( this );
@ -848,7 +854,7 @@ public class Table implements RelationalModel, Serializable, Exportable {
}
static class ForeignKeyKey implements Serializable {
public static class ForeignKeyKey implements Serializable {
String referencedClassName;
List columns;
List referencedColumns;
@ -876,6 +882,15 @@ public class Table implements RelationalModel, Serializable, Exportable {
fkk.referencedClassName.equals( referencedClassName ) && fkk.referencedColumns
.equals( referencedColumns );
}
@Override
public String toString() {
return "ForeignKeyKey{" +
"columns=" + StringHelper.join( ",", columns ) +
", referencedClassName='" + referencedClassName + '\'' +
", referencedColumns=" + StringHelper.join( ",", referencedColumns ) +
'}';
}
}
}

View File

@ -0,0 +1,85 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.foreignkeys.disabled;
import java.util.Map;
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.mapping.ForeignKey;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.Table.ForeignKeyKey;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
/**
* @author Steve Ebersole
*/
public class DisabledForeignKeyTest extends BaseUnitTestCase {
@Test
@TestForIssue( jiraKey = "HHH-9704" )
public void basicTests() {
StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder();
StandardServiceRegistry standardRegistry = registryBuilder.build();
try {
final MetadataSources sources = new MetadataSources( standardRegistry );
sources.addAnnotatedClass( ManyToManyOwner.class );
sources.addAnnotatedClass( ManyToManyTarget.class );
final MetadataImplementor metadata = (MetadataImplementor) sources.buildMetadata();
metadata.validate();
new SchemaExport( metadata ).execute( true, false, false, true );
int fkCount = 0;
for ( Table table : metadata.collectTableMappings() ) {
for ( Map.Entry<ForeignKeyKey, ForeignKey> entry : table.getForeignKeys().entrySet() ) {
assertFalse(
"Creation for ForeignKey [" + entry.getKey() + "] was not disabled",
entry.getValue().isCreationEnabled()
);
fkCount++;
}
}
// ultimately I want to actually create the ForeignKet reference, but simply disable its creation
// via ForeignKet#disableCreation()
assertEquals( "Was expecting 4 FKs", 0, fkCount );
}
finally {
StandardServiceRegistryBuilder.destroy( standardRegistry );
}
}
}

View File

@ -0,0 +1,104 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.foreignkeys.disabled;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.ConstraintMode;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.SecondaryTable;
/**
* @author Steve Ebersole
*/
@Entity
public class ManyToManyOwner {
public Integer id;
public String name;
private Set<ManyToManyTarget> manyToMany;
@Id
@GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// NOTE : we currently only pick up the JoinTable#foreignKey / JoinTable#inverseForeignKey
// not the JoinColumn#foreignKey reference on the JoinTable#joinColumns /JoinTable#inverseJoinColumns
@ManyToMany(
fetch = FetchType.LAZY,
cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH })
@JoinTable(
foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT),
joinColumns = {
@JoinColumn(
unique = false,
nullable = true,
insertable = true,
updatable = true,
foreignKey = @ForeignKey( name = "none" ),
name = "m2m_owner_id"
)
},
inverseJoinColumns = {
@JoinColumn(
unique = false,
nullable = true,
insertable = true,
updatable = true,
foreignKey = @ForeignKey( name = "none", value = ConstraintMode.NO_CONSTRAINT ),
name = "m2m_target_id"
)
},
inverseForeignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT),
name = "many_to_many"
)
public Set<ManyToManyTarget> getManyToMany() {
return manyToMany;
}
public void setManyToMany(Set<ManyToManyTarget> manyToMany) {
this.manyToMany = manyToMany;
}
}

View File

@ -0,0 +1,55 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.foreignkeys.disabled;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* @author Steve Ebersole
*/
@Entity
public class ManyToManyTarget {
public Integer id;
public String name;
@Id
@GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,28 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
/**
* Tests for disabling FK generation
*/
package org.hibernate.test.foreignkeys.disabled;