HHH-11180 - JPA @ForeignKey still not consistently applied from annotation binding

- Fix ForeignKey support for PrimaryKeyJoinColumn / PrimaryKeyJoinColumns
- Fix ForeignKey support for JoinColumn / JoinColumns
- Fix ForeignKey support for JoinTable when applying value NO_CONSTRAINT.
- Fix ForeignKey support for MapKeyJoinColumn / MapKeyJoinColumns
- Fix ForeignKey support for AssociationOverride / AssociationOverrides
This commit is contained in:
Christian Beikov 2016-10-21 19:51:35 +02:00 committed by Vlad Mihalcea
parent 15b0a683f2
commit aeb3aee626
6 changed files with 716 additions and 87 deletions

View File

@ -15,6 +15,7 @@ import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.MappedSuperclass;
@ -48,6 +49,8 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
private Map<String, JoinColumn[]> currentPropertyJoinColumnOverride;
private Map<String, JoinTable> holderJoinTableOverride;
private Map<String, JoinTable> currentPropertyJoinTableOverride;
private Map<String, ForeignKey> holderForeignKeyOverride;
private Map<String, ForeignKey> currentPropertyForeignKeyOverride;
private String path;
private MetadataBuildingContext context;
private Boolean isInIdClass;
@ -170,29 +173,28 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
this.currentPropertyColumnOverride = null;
this.currentPropertyJoinColumnOverride = null;
this.currentPropertyJoinTableOverride = null;
this.currentPropertyForeignKeyOverride = null;
}
else {
this.currentPropertyColumnOverride = buildColumnOverride(
property,
getPath()
);
this.currentPropertyColumnOverride = buildColumnOverride( property, getPath() );
if ( this.currentPropertyColumnOverride.size() == 0 ) {
this.currentPropertyColumnOverride = null;
}
this.currentPropertyJoinColumnOverride = buildJoinColumnOverride(
property,
getPath()
);
this.currentPropertyJoinColumnOverride = buildJoinColumnOverride( property, getPath() );
if ( this.currentPropertyJoinColumnOverride.size() == 0 ) {
this.currentPropertyJoinColumnOverride = null;
}
this.currentPropertyJoinTableOverride = buildJoinTableOverride(
property,
getPath()
);
this.currentPropertyJoinTableOverride = buildJoinTableOverride( property, getPath() );
if ( this.currentPropertyJoinTableOverride.size() == 0 ) {
this.currentPropertyJoinTableOverride = null;
}
this.currentPropertyForeignKeyOverride = buildForeignKeyOverride( property, getPath() );
if ( this.currentPropertyForeignKeyOverride.size() == 0 ) {
this.currentPropertyForeignKeyOverride = null;
}
}
}
@ -298,6 +300,30 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
return override;
}
public ForeignKey getOverriddenForeignKey(String propertyName) {
ForeignKey result = getExactOverriddenForeignKey( propertyName );
if ( result == null && propertyName.contains( ".collection&&element." ) ) {
//support for non map collections where no prefix is needed
//TODO cache the underlying regexp
result = getExactOverriddenForeignKey( propertyName.replace( ".collection&&element.", "." ) );
}
return result;
}
private ForeignKey getExactOverriddenForeignKey(String propertyName) {
ForeignKey override = null;
if ( parent != null ) {
override = parent.getExactOverriddenForeignKey( propertyName );
}
if ( override == null && currentPropertyForeignKeyOverride != null ) {
override = currentPropertyForeignKeyOverride.get( propertyName );
}
if ( override == null && holderForeignKeyOverride != null ) {
override = holderForeignKeyOverride.get( propertyName );
}
return override;
}
/**
* Get column overriding, property first, then parent, then holder
* replace the placeholder 'collection&&element' with nothing
@ -352,6 +378,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
Map<String, Column[]> columnOverride = new HashMap<String, Column[]>();
Map<String, JoinColumn[]> joinColumnOverride = new HashMap<String, JoinColumn[]>();
Map<String, JoinTable> joinTableOverride = new HashMap<String, JoinTable>();
Map<String, ForeignKey> foreignKeyOverride = new HashMap<String, ForeignKey>();
while ( current != null && !context.getBuildingOptions().getReflectionManager().toXClass( Object.class ).equals( current ) ) {
if ( current.isAnnotationPresent( Entity.class ) || current.isAnnotationPresent( MappedSuperclass.class )
|| current.isAnnotationPresent( Embeddable.class ) ) {
@ -359,12 +386,15 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
Map<String, Column[]> currentOverride = buildColumnOverride( current, getPath() );
Map<String, JoinColumn[]> currentJoinOverride = buildJoinColumnOverride( current, getPath() );
Map<String, JoinTable> currentJoinTableOverride = buildJoinTableOverride( current, getPath() );
Map<String, ForeignKey> currentForeignKeyOverride = buildForeignKeyOverride( current, getPath() );
currentOverride.putAll( columnOverride ); //subclasses have precedence over superclasses
currentJoinOverride.putAll( joinColumnOverride ); //subclasses have precedence over superclasses
currentJoinTableOverride.putAll( joinTableOverride ); //subclasses have precedence over superclasses
currentForeignKeyOverride.putAll( foreignKeyOverride ); //subclasses have precedence over superclasses
columnOverride = currentOverride;
joinColumnOverride = currentJoinOverride;
joinTableOverride = currentJoinTableOverride;
foreignKeyOverride = currentForeignKeyOverride;
}
current = current.getSuperclass();
}
@ -372,31 +402,32 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
holderColumnOverride = columnOverride.size() > 0 ? columnOverride : null;
holderJoinColumnOverride = joinColumnOverride.size() > 0 ? joinColumnOverride : null;
holderJoinTableOverride = joinTableOverride.size() > 0 ? joinTableOverride : null;
holderForeignKeyOverride = foreignKeyOverride.size() > 0 ? foreignKeyOverride : null;
}
private static Map<String, Column[]> buildColumnOverride(XAnnotatedElement element, String path) {
Map<String, Column[]> columnOverride = new HashMap<String, Column[]>();
if ( element == null ) return columnOverride;
AttributeOverride singleOverride = element.getAnnotation( AttributeOverride.class );
AttributeOverrides multipleOverrides = element.getAnnotation( AttributeOverrides.class );
AttributeOverride[] overrides;
if ( singleOverride != null ) {
overrides = new AttributeOverride[] { singleOverride };
}
else if ( multipleOverrides != null ) {
overrides = multipleOverrides.value();
}
else {
overrides = null;
}
if ( element != null ) {
AttributeOverride singleOverride = element.getAnnotation( AttributeOverride.class );
AttributeOverrides multipleOverrides = element.getAnnotation( AttributeOverrides.class );
AttributeOverride[] overrides;
if ( singleOverride != null ) {
overrides = new AttributeOverride[]{ singleOverride };
}
else if ( multipleOverrides != null ) {
overrides = multipleOverrides.value();
}
else {
overrides = null;
}
//fill overridden columns
if ( overrides != null ) {
for (AttributeOverride depAttr : overrides) {
columnOverride.put(
StringHelper.qualify( path, depAttr.name() ),
new Column[] { depAttr.column() }
);
if ( overrides != null ) {
for ( AttributeOverride depAttr : overrides ) {
columnOverride.put(
StringHelper.qualify( path, depAttr.name() ),
new Column[]{ depAttr.column() }
);
}
}
}
return columnOverride;
@ -404,56 +435,62 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
private static Map<String, JoinColumn[]> buildJoinColumnOverride(XAnnotatedElement element, String path) {
Map<String, JoinColumn[]> columnOverride = new HashMap<String, JoinColumn[]>();
if ( element == null ) return columnOverride;
AssociationOverride singleOverride = element.getAnnotation( AssociationOverride.class );
AssociationOverrides multipleOverrides = element.getAnnotation( AssociationOverrides.class );
AssociationOverride[] overrides;
if ( singleOverride != null ) {
overrides = new AssociationOverride[] { singleOverride };
}
else if ( multipleOverrides != null ) {
overrides = multipleOverrides.value();
}
else {
overrides = null;
}
//fill overridden columns
if ( overrides != null ) {
for (AssociationOverride depAttr : overrides) {
columnOverride.put(
StringHelper.qualify( path, depAttr.name() ),
depAttr.joinColumns()
);
if ( element != null ) {
AssociationOverride[] overrides = buildAssociationOverrides( element, path );
if ( overrides != null ) {
for ( AssociationOverride depAttr : overrides ) {
columnOverride.put(
StringHelper.qualify( path, depAttr.name() ),
depAttr.joinColumns()
);
}
}
}
return columnOverride;
}
private static Map<String, JoinTable> buildJoinTableOverride(XAnnotatedElement element, String path) {
Map<String, JoinTable> tableOverride = new HashMap<String, JoinTable>();
if ( element == null ) return tableOverride;
private static Map<String, ForeignKey> buildForeignKeyOverride(XAnnotatedElement element, String path) {
Map<String, ForeignKey> foreignKeyOverride = new HashMap<String, ForeignKey>();
if ( element != null ) {
AssociationOverride[] overrides = buildAssociationOverrides( element, path );
if ( overrides != null ) {
for ( AssociationOverride depAttr : overrides ) {
foreignKeyOverride.put( StringHelper.qualify( path, depAttr.name() ), depAttr.foreignKey() );
}
}
}
return foreignKeyOverride;
}
private static AssociationOverride[] buildAssociationOverrides(XAnnotatedElement element, String path) {
AssociationOverride singleOverride = element.getAnnotation( AssociationOverride.class );
AssociationOverrides multipleOverrides = element.getAnnotation( AssociationOverrides.class );
AssociationOverrides pluralOverrides = element.getAnnotation( AssociationOverrides.class );
AssociationOverride[] overrides;
if ( singleOverride != null ) {
overrides = new AssociationOverride[] { singleOverride };
}
else if ( multipleOverrides != null ) {
overrides = multipleOverrides.value();
else if ( pluralOverrides != null ) {
overrides = pluralOverrides.value();
}
else {
overrides = null;
}
return overrides;
}
//fill overridden tables
if ( overrides != null ) {
for (AssociationOverride depAttr : overrides) {
if ( depAttr.joinColumns().length == 0 ) {
tableOverride.put(
StringHelper.qualify( path, depAttr.name() ),
depAttr.joinTable()
);
private static Map<String, JoinTable> buildJoinTableOverride(XAnnotatedElement element, String path) {
Map<String, JoinTable> tableOverride = new HashMap<String, JoinTable>();
if ( element != null ) {
AssociationOverride[] overrides = buildAssociationOverrides( element, path );
if ( overrides != null ) {
for ( AssociationOverride depAttr : overrides ) {
if ( depAttr.joinColumns().length == 0 ) {
tableOverride.put(
StringHelper.qualify( path, depAttr.name() ),
depAttr.joinTable()
);
}
}
}
}

View File

@ -657,8 +657,20 @@ public final class AnnotationBinder {
}
else {
final PrimaryKeyJoinColumn pkJoinColumn = clazzToProcess.getAnnotation( PrimaryKeyJoinColumn.class );
if ( pkJoinColumn != null && pkJoinColumn.foreignKey() != null
&& !StringHelper.isEmpty( pkJoinColumn.foreignKey().name() ) ) {
final PrimaryKeyJoinColumns pkJoinColumns = clazzToProcess.getAnnotation( PrimaryKeyJoinColumns.class );
if ( pkJoinColumns != null && pkJoinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) {
// 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 ) {
// don't apply a constraint based on ConstraintMode
key.setForeignKeyName( "none" );
}
else if ( pkJoinColumn != null && !StringHelper.isEmpty( pkJoinColumn.foreignKey().name() ) ) {
key.setForeignKeyName( pkJoinColumn.foreignKey().name() );
}
}
@ -2914,6 +2926,7 @@ public final class AnnotationBinder {
}
final JoinColumn joinColumn = property.getAnnotation( JoinColumn.class );
final JoinColumns joinColumns = property.getAnnotation( JoinColumns.class );
//Make sure that JPA1 key-many-to-one columns are read only tooj
boolean hasSpecjManyToOne=false;
@ -2942,8 +2955,8 @@ public final class AnnotationBinder {
final String propertyName = inferredData.getPropertyName();
value.setTypeUsingReflection( propertyHolder.getClassName(), propertyName );
if ( joinColumn != null
&& joinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) {
if ( ( joinColumn != null && joinColumn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT )
|| ( joinColumns != null && joinColumns.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) ) {
// not ideal...
value.setForeignKeyName( "none" );
}
@ -2952,9 +2965,25 @@ public final class AnnotationBinder {
if ( fk != null && StringHelper.isNotEmpty( fk.name() ) ) {
value.setForeignKeyName( fk.name() );
}
else if ( joinColumn != null ) {
value.setForeignKeyName( StringHelper.nullIfEmpty( joinColumn.foreignKey().name() ) );
value.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumn.foreignKey().foreignKeyDefinition() ) );
else {
final javax.persistence.ForeignKey fkOverride = propertyHolder.getOverriddenForeignKey(
StringHelper.qualify( propertyHolder.getPath(), propertyName )
);
if ( fkOverride != null && fkOverride.value() == ConstraintMode.NO_CONSTRAINT ) {
value.setForeignKeyName( "none" );
}
else if ( fkOverride != null ) {
value.setForeignKeyName( StringHelper.nullIfEmpty( fkOverride.name() ) );
value.setForeignKeyDefinition( StringHelper.nullIfEmpty( fkOverride.foreignKeyDefinition() ) );
}
else if ( joinColumns != null ) {
value.setForeignKeyName( StringHelper.nullIfEmpty( joinColumns.foreignKey().name() ) );
value.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumns.foreignKey().foreignKeyDefinition() ) );
}
else if ( joinColumn != null ) {
value.setForeignKeyName( StringHelper.nullIfEmpty( joinColumn.foreignKey().name() ) );
value.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumn.foreignKey().foreignKeyDefinition() ) );
}
}
}

View File

@ -7,6 +7,7 @@
package org.hibernate.cfg;
import javax.persistence.Column;
import javax.persistence.ForeignKey;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
@ -67,6 +68,14 @@ public interface PropertyHolder {
*/
JoinColumn[] getOverriddenJoinColumn(String propertyName);
/**
* return null if hte foreign key is not overridden, or the foreign key if true
*/
default ForeignKey getOverriddenForeignKey(String propertyName) {
// todo: does this necessarily need to be a default method?
return null;
}
/**
* return
* - null if no join table is present,

View File

@ -868,7 +868,7 @@ public abstract class CollectionBinder {
LOG.debugf( "Mapping collection: %s -> %s", collection.getRole(), collection.getCollectionTable().getName() );
}
bindFilters( false );
bindCollectionSecondPass( collection, null, fkJoinColumns, cascadeDeleteEnabled, property, buildingContext );
bindCollectionSecondPass( collection, null, fkJoinColumns, cascadeDeleteEnabled, property, propertyHolder, buildingContext );
if ( !collection.isInverse()
&& !collection.getKey().isNullable() ) {
// for non-inverse one-to-many, with a not-null fk, add a backref!
@ -1085,6 +1085,7 @@ public abstract class CollectionBinder {
Ejb3JoinColumn[] joinColumns,
boolean cascadeDeleteEnabled,
XProperty property,
PropertyHolder propertyHolder,
MetadataBuildingContext buildingContext) {
//binding key reference using column
KeyValue keyVal;
@ -1160,14 +1161,26 @@ public abstract class CollectionBinder {
}
}
else {
final JoinColumn joinColumnAnn = property.getAnnotation( JoinColumn.class );
if ( joinColumnAnn != null ) {
if ( joinColumnAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) {
key.setForeignKeyName( "none" );
}
else {
key.setForeignKeyName( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().name() ) );
key.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().foreignKeyDefinition() ) );
final javax.persistence.ForeignKey fkOverride = propertyHolder.getOverriddenForeignKey(
StringHelper.qualify( propertyHolder.getPath(), property.getName() )
);
if ( fkOverride != null && fkOverride.value() == ConstraintMode.NO_CONSTRAINT ) {
key.setForeignKeyName( "none" );
}
else if ( fkOverride != null ) {
key.setForeignKeyName( StringHelper.nullIfEmpty( fkOverride.name() ) );
key.setForeignKeyDefinition( StringHelper.nullIfEmpty( fkOverride.foreignKeyDefinition() ) );
}
else {
final JoinColumn joinColumnAnn = property.getAnnotation( JoinColumn.class );
if ( joinColumnAnn != null ) {
if ( joinColumnAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) {
key.setForeignKeyName( "none" );
}
else {
key.setForeignKeyName( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().name() ) );
key.setForeignKeyDefinition( StringHelper.nullIfEmpty( joinColumnAnn.foreignKey().foreignKeyDefinition() ) );
}
}
}
}
@ -1319,7 +1332,7 @@ public abstract class CollectionBinder {
collValue.setCollectionTable( associationTableBinder.bind() );
}
bindFilters( isCollectionOfEntities );
bindCollectionSecondPass( collValue, collectionEntity, joinColumns, cascadeDeleteEnabled, property, buildingContext );
bindCollectionSecondPass( collValue, collectionEntity, joinColumns, cascadeDeleteEnabled, property, propertyHolder, buildingContext );
ManyToOne element = null;
if ( isCollectionOfEntities ) {
@ -1348,7 +1361,7 @@ public abstract class CollectionBinder {
if ( joinTableAnn != null ) {
String foreignKeyName = joinTableAnn.inverseForeignKey().name();
String foreignKeyDefinition = joinTableAnn.inverseForeignKey().foreignKeyDefinition();
ConstraintMode foreignKeyValue = joinTableAnn.foreignKey().value();
ConstraintMode foreignKeyValue = joinTableAnn.inverseForeignKey().value();
if ( joinTableAnn.inverseJoinColumns().length != 0 ) {
final JoinColumn joinColumnAnn = joinTableAnn.inverseJoinColumns()[0];
if ( "".equals( foreignKeyName ) ) {
@ -1359,7 +1372,7 @@ public abstract class CollectionBinder {
foreignKeyValue = joinColumnAnn.foreignKey().value();
}
}
if ( joinTableAnn.foreignKey().value() == ConstraintMode.NO_CONSTRAINT ) {
if ( joinTableAnn.inverseForeignKey().value() == ConstraintMode.NO_CONSTRAINT ) {
element.setForeignKeyName( "none" );
}
else {
@ -1575,6 +1588,7 @@ public abstract class CollectionBinder {
Ejb3JoinColumn[] joinColumns,
boolean cascadeDeleteEnabled,
XProperty property,
PropertyHolder propertyHolder,
MetadataBuildingContext buildingContext) {
try {
BinderHelper.createSyntheticPropertyReference(
@ -1589,7 +1603,7 @@ 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, buildingContext );
SimpleValue key = buildCollectionKey( collValue, joinColumns, cascadeDeleteEnabled, property, propertyHolder, buildingContext );
if ( property.isAnnotationPresent( ElementCollection.class ) && joinColumns.length > 0 ) {
joinColumns[0].setJPA2ElementCollection( true );
}

View File

@ -12,8 +12,11 @@ import java.util.Map;
import java.util.Random;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.ConstraintMode;
import javax.persistence.InheritanceType;
import javax.persistence.MapKeyClass;
import javax.persistence.MapKeyJoinColumn;
import javax.persistence.MapKeyJoinColumns;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
@ -294,6 +297,20 @@ public class MapBinder extends CollectionBinder {
col.forceNotNull();
}
}
if ( element != null ) {
final javax.persistence.ForeignKey foreignKey = getMapKeyForeignKey( property );
if ( foreignKey != null ) {
if ( foreignKey.value() == ConstraintMode.NO_CONSTRAINT ) {
element.setForeignKeyName( "none" );
}
else {
element.setForeignKeyName( StringHelper.nullIfEmpty( foreignKey.name() ) );
element.setForeignKeyDefinition( StringHelper.nullIfEmpty( foreignKey.foreignKeyDefinition() ) );
}
}
}
if ( isIndexOfEntities ) {
bindManytoManyInverseFk(
collectionEntity,
@ -306,6 +323,20 @@ public class MapBinder extends CollectionBinder {
}
}
private javax.persistence.ForeignKey getMapKeyForeignKey(XProperty property) {
final MapKeyJoinColumns mapKeyJoinColumns = property.getAnnotation( MapKeyJoinColumns.class );
if ( mapKeyJoinColumns != null ) {
return mapKeyJoinColumns.foreignKey();
}
else {
final MapKeyJoinColumn mapKeyJoinColumn = property.getAnnotation( MapKeyJoinColumn.class );
if ( mapKeyJoinColumn != null ) {
return mapKeyJoinColumn.foreignKey();
}
}
return null;
}
private boolean mappingDefinedAttributeOverrideOnMapKey(XProperty property) {
if ( property.isAnnotationPresent( AttributeOverride.class ) ) {
return namedMapKey( property.getAnnotation( AttributeOverride.class ) );

View File

@ -0,0 +1,509 @@
/*
* 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.constraint;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.persistence.AssociationOverride;
import javax.persistence.AssociationOverrides;
import javax.persistence.CollectionTable;
import javax.persistence.ConstraintMode;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyJoinColumn;
import javax.persistence.MapKeyJoinColumns;
import javax.persistence.MappedSuperclass;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.PrimaryKeyJoinColumns;
import javax.persistence.SecondaryTable;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.mapping.Column;
import org.junit.Test;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author Christian Beikov
*/
@TestForIssue( jiraKey = "HHH-11180" )
public class ForeignKeyConstraintTest extends BaseNonConfigCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
CreditCard.class,
Person.class,
Student.class,
Professor.class,
Vehicle.class,
VehicleBuyInfo.class,
Car.class,
Truck.class,
Company.class
};
}
@Test
public void testJoinColumn() {
assertForeignKey( "FK_CAR_OWNER", "OWNER_PERSON_ID" );
assertForeignKey( "FK_CAR_OWNER3", "OWNER_PERSON_ID3" );
assertForeignKey( "FK_PERSON_CC", "PERSON_CC_ID" );
assertNoForeignKey( "FK_CAR_OWNER2", "OWNER_PERSON_ID2" );
assertNoForeignKey( "FK_CAR_OWNER4", "OWNER_PERSON_ID4" );
assertNoForeignKey( "FK_PERSON_CC2", "PERSON_CC_ID2" );
}
@Test
public void testJoinColumns() {
assertForeignKey( "FK_STUDENT_CAR", "CAR_NR", "CAR_VENDOR_NR" );
assertForeignKey( "FK_STUDENT_CAR3", "CAR_NR3", "CAR_VENDOR_NR3" );
assertNoForeignKey( "FK_STUDENT_CAR2", "CAR_NR2", "CAR_VENDOR_NR2" );
assertNoForeignKey( "FK_STUDENT_CAR4", "CAR_NR4", "CAR_VENDOR_NR4" );
}
@Test
public void testJoinTable() {
assertForeignKey( "FK_VEHICLE_BUY_INFOS_STUDENT", "STUDENT_ID" );
}
@Test
public void testJoinTableInverse() {
assertForeignKey( "FK_VEHICLE_BUY_INFOS_VEHICLE_BUY_INFO", "VEHICLE_BUY_INFO_ID" );
}
@Test
public void testPrimaryKeyJoinColumn() {
assertForeignKey( "FK_STUDENT_PERSON", "PERSON_ID" );
assertNoForeignKey( "FK_PROFESSOR_PERSON", "PERSON_ID" );
}
@Test
public void testPrimaryKeyJoinColumns() {
assertForeignKey( "FK_CAR_VEHICLE", "CAR_NR", "VENDOR_NR" );
assertNoForeignKey( "FK_TRUCK_VEHICLE", "CAR_NR", "VENDOR_NR" );
}
@Test
public void testCollectionTable() {
assertForeignKey( "FK_OWNER_INFO_CAR", "CAR_NR", "VENDOR_NR" );
}
@Test
public void testMapKeyJoinColumn() {
assertForeignKey( "FK_OWNER_INFO_PERSON", "PERSON_ID" );
}
@Test
public void testMapKeyJoinColumns() {
assertForeignKey( "FK_VEHICLE_BUY_INFOS_VEHICLE", "VEHICLE_NR", "VEHICLE_VENDOR_NR" );
}
@Test
public void testAssociationOverride() {
// class level association overrides
assertForeignKey( "FK_COMPANY_OWNER", "OWNER_PERSON_ID" );
assertForeignKey( "FK_COMPANY_CREDIT_CARD", "CREDIT_CARD_ID" );
assertForeignKey( "FK_COMPANY_CREDIT_CARD3", "CREDIT_CARD_ID3" );
assertNoForeignKey( "FK_COMPANY_OWNER2", "OWNER_PERSON_ID2" );
assertNoForeignKey( "FK_COMPANY_CREDIT_CARD2", "CREDIT_CARD_ID2" );
assertNoForeignKey( "FK_COMPANY_CREDIT_CARD4", "CREDIT_CARD_ID4" );
// embeddable association overrides
assertForeignKey( "FK_COMPANY_CARD", "AO_CI_CC_ID" );
assertNoForeignKey( "FK_COMPANY_CARD2", "AO_CI_CC_ID2" );
assertForeignKey( "FK_COMPANY_CARD3", "AO_CI_CC_ID3" );
assertNoForeignKey( "FK_COMPANY_CARD4", "AO_CI_CC_ID4" );
}
@Test
public void testSecondaryTable() {
assertForeignKey( "FK_CAR_DETAILS_CAR", "CAR_NR", "CAR_VENDOR_NR" );
}
private void assertForeignKey(String foreignKeyName, String... columns) {
Set<String> columnSet = new LinkedHashSet<>( Arrays.asList( columns ) );
for ( Namespace namespace : metadata().getDatabase().getNamespaces() ) {
for ( org.hibernate.mapping.Table table : namespace.getTables() ) {
Iterator<org.hibernate.mapping.ForeignKey> fkItr = table.getForeignKeyIterator();
while ( fkItr.hasNext() ) {
org.hibernate.mapping.ForeignKey fk = fkItr.next();
if ( foreignKeyName.equals( fk.getName() ) ) {
assertEquals( "ForeignKey column count not like expected", columnSet.size(), fk.getColumnSpan() );
List<String> columnNames = fk.getColumns().stream().map(Column::getName).collect(Collectors.toList());
assertTrue(
"ForeignKey columns [" + columnNames + "] do not match expected columns [" + columnSet + "]",
columnSet.containsAll( columnNames )
);
return;
}
}
}
}
fail( "ForeignKey '" + foreignKeyName + "' could not be found!" );
}
private void assertNoForeignKey(String foreignKeyName, String... columns) {
Set<String> columnSet = new LinkedHashSet<>( Arrays.asList( columns ) );
for ( Namespace namespace : metadata().getDatabase().getNamespaces() ) {
for ( org.hibernate.mapping.Table table : namespace.getTables() ) {
Iterator<org.hibernate.mapping.ForeignKey> fkItr = table.getForeignKeyIterator();
while ( fkItr.hasNext() ) {
org.hibernate.mapping.ForeignKey fk = fkItr.next();
assertFalse(
"ForeignKey [" + foreignKeyName + "] defined and shouldn't have been.",
foreignKeyName.equals( fk.getName() )
);
}
}
}
}
@Entity(name = "CreditCard")
public static class CreditCard {
@Id
public String number;
}
@Entity(name = "Person")
@Inheritance( strategy = InheritanceType.JOINED )
public static class Person {
@Id
@GeneratedValue
@javax.persistence.Column( nullable = false, unique = true)
public long id;
@OneToMany
@JoinColumn(name = "PERSON_CC_ID", foreignKey = @ForeignKey( name = "FK_PERSON_CC" ) )
public List<CreditCard> creditCards;
@OneToMany
@JoinColumn(name = "PERSON_CC_ID2", foreignKey = @ForeignKey( name = "FK_PERSON_CC2", value = ConstraintMode.NO_CONSTRAINT ) )
public List<CreditCard> creditCards2;
}
@Entity(name = "Professor")
@PrimaryKeyJoinColumn(
name = "PERSON_ID",
foreignKey = @ForeignKey( name = "FK_PROFESSOR_PERSON", value = ConstraintMode.NO_CONSTRAINT )
)
public static class Professor extends Person {
}
@Entity(name = "Student")
@PrimaryKeyJoinColumn( name = "PERSON_ID", foreignKey = @ForeignKey( name = "FK_STUDENT_PERSON" ) )
public static class Student extends Person {
@javax.persistence.Column( name = "MATRICULATION_NUMBER" )
public String matriculationNumber;
@ManyToOne
@JoinColumns(
value = {
@JoinColumn( name = "CAR_NR", referencedColumnName = "CAR_NR" ),
@JoinColumn( name = "CAR_VENDOR_NR", referencedColumnName = "VENDOR_NR" )
},
foreignKey = @ForeignKey( name = "FK_STUDENT_CAR" )
)
public Car car;
@ManyToOne
@JoinColumns(
value = {
@JoinColumn( name = "CAR_NR2", referencedColumnName = "CAR_NR" ),
@JoinColumn( name = "CAR_VENDOR_NR2", referencedColumnName = "VENDOR_NR" )
},
foreignKey = @ForeignKey( name = "FK_STUDENT_CAR2", value = ConstraintMode.NO_CONSTRAINT )
)
public Car car2;
@OneToOne
@JoinColumns(
value = {
@JoinColumn( name = "CAR_NR3", referencedColumnName = "CAR_NR" ),
@JoinColumn( name = "CAR_VENDOR_NR3", referencedColumnName = "VENDOR_NR" )
},
foreignKey = @ForeignKey( name = "FK_STUDENT_CAR3" )
)
public Car car3;
@OneToOne
@JoinColumns(
value = {
@JoinColumn( name = "CAR_NR4", referencedColumnName = "CAR_NR" ),
@JoinColumn( name = "CAR_VENDOR_NR4", referencedColumnName = "VENDOR_NR" )
},
foreignKey = @ForeignKey( name = "FK_STUDENT_CAR4", value = ConstraintMode.NO_CONSTRAINT )
)
public Car car4;
@ManyToMany
@JoinTable(
name = "VEHICLE_BUY_INFOS",
foreignKey = @ForeignKey( name = "FK_VEHICLE_BUY_INFOS_STUDENT" ),
inverseForeignKey = @ForeignKey( name = "FK_VEHICLE_BUY_INFOS_VEHICLE_BUY_INFO" ),
joinColumns = @JoinColumn( name = "STUDENT_ID"),
inverseJoinColumns = @JoinColumn( name = "VEHICLE_BUY_INFO_ID" )
)
@MapKeyJoinColumns(
value = {
@MapKeyJoinColumn( name = "VEHICLE_NR", referencedColumnName = "VEHICLE_NR" ),
@MapKeyJoinColumn( name = "VEHICLE_VENDOR_NR", referencedColumnName = "VEHICLE_VENDOR_NR" )
},
foreignKey = @ForeignKey( name = "FK_VEHICLE_BUY_INFOS_VEHICLE" )
)
public Map<Vehicle, VehicleBuyInfo> vehicleBuyInfos = new HashMap<>();
}
@Entity(name = "VehicleBuyInfo")
public static class VehicleBuyInfo {
@Id
@GeneratedValue
public long id;
public String info;
}
@Entity(name = "Vehicle")
@Inheritance( strategy = InheritanceType.JOINED )
public static class Vehicle {
@EmbeddedId
public VehicleId id;
}
@Embeddable
public static class VehicleId implements Serializable {
@javax.persistence.Column( name = "VEHICLE_VENDOR_NR" )
public long vehicleVendorNumber;
@javax.persistence.Column( name = "VEHICLE_NR" )
public long vehicleNumber;
@Override
public boolean equals(Object o) {
if ( this == o ) return true;
if ( !( o instanceof VehicleId ) ) return false;
VehicleId vehicleId = (VehicleId) o;
if ( vehicleVendorNumber != vehicleId.vehicleVendorNumber ) return false;
return vehicleNumber == vehicleId.vehicleNumber;
}
@Override
public int hashCode() {
int result = (int) ( vehicleVendorNumber ^ ( vehicleVendorNumber >>> 32 ) );
result = 31 * result + (int) ( vehicleNumber ^ ( vehicleNumber >>> 32 ) );
return result;
}
}
@Entity(name = "Car")
@SecondaryTable(
name = "CAR_DETAILS",
pkJoinColumns = {
@PrimaryKeyJoinColumn( name = "CAR_NR", referencedColumnName = "CAR_NR" ),
@PrimaryKeyJoinColumn( name = "CAR_VENDOR_NR", referencedColumnName = "VENDOR_NR" )
},
foreignKey = @ForeignKey( name = "FK_CAR_DETAILS_CAR" )
)
@PrimaryKeyJoinColumns(
value = {
@PrimaryKeyJoinColumn( name = "CAR_NR", referencedColumnName = "VEHICLE_NR" ),
@PrimaryKeyJoinColumn( name = "VENDOR_NR", referencedColumnName = "VEHICLE_VENDOR_NR" )
},
foreignKey = @ForeignKey( name = "FK_CAR_VEHICLE" )
)
public static class Car extends Vehicle {
public String color;
@ManyToOne
@JoinColumn( name = "OWNER_PERSON_ID", foreignKey = @ForeignKey( name = "FK_CAR_OWNER") )
public Person owner;
@ManyToOne
@JoinColumn( name = "OWNER_PERSON_ID2", foreignKey = @ForeignKey( name = "FK_CAR_OWNER2", value = ConstraintMode.NO_CONSTRAINT ) )
public Person owner2;
@OneToOne
@JoinColumn( name = "OWNER_PERSON_ID3", foreignKey = @ForeignKey( name = "FK_CAR_OWNER3") )
public Person owner3;
@OneToOne
@JoinColumn( name = "OWNER_PERSON_ID4", foreignKey = @ForeignKey( name = "FK_CAR_OWNER4", value = ConstraintMode.NO_CONSTRAINT ) )
public Person owner4;
@ElementCollection
@CollectionTable(
name = "OWNER_INFO",
joinColumns = {
@JoinColumn( name = "CAR_NR" ),
@JoinColumn( name = "VENDOR_NR" )
},
foreignKey = @ForeignKey( name = "FK_OWNER_INFO_CAR" )
)
@MapKeyJoinColumn( name = "PERSON_ID", foreignKey = @ForeignKey( name = "FK_OWNER_INFO_PERSON" ) )
public Map<Person, String> ownerInfo = new HashMap<>();
}
@Entity(name = "Truck")
@PrimaryKeyJoinColumns(
value = {
@PrimaryKeyJoinColumn( name = "CAR_NR", referencedColumnName = "VEHICLE_NR" ),
@PrimaryKeyJoinColumn( name = "VENDOR_NR", referencedColumnName = "VEHICLE_VENDOR_NR" )
},
foreignKey = @ForeignKey( name = "FK_TRUCK_VEHICLE", value = ConstraintMode.NO_CONSTRAINT )
)
public static class Truck extends Vehicle {
public boolean fourWheelDrive;
}
@MappedSuperclass
public static abstract class AbstractCompany {
@Id
@GeneratedValue
public long id;
@ManyToOne
@JoinColumn( name = "OWNER_ID" )
public Person owner;
@ManyToOne
@JoinColumn( name = "OWNER_ID2" )
public Person owner2;
@OneToOne
@JoinColumn( name = "CC_ID" )
public CreditCard creditCard;
@OneToOne
@JoinColumn( name = "CC_ID2" )
public CreditCard creditCard2;
@OneToMany
@JoinColumn( name = "CC_ID3" )
public List<CreditCard> creditCards1;
@OneToMany
@JoinColumn( name = "CC_ID4" )
public List<CreditCard> creditCards2;
}
@Embeddable
public static class CompanyInfo {
public String data;
@OneToMany
@JoinColumn( name = "CI_CC_ID", foreignKey = @ForeignKey( name = "FK_CI_CC" ) )
public List<CreditCard> cards;
@OneToMany
@JoinColumn( name = "CI_CC_ID2", foreignKey = @ForeignKey( name = "FK_CI_CC2" ) )
public List<CreditCard> cards2;
@ManyToOne
@JoinColumn( name = "CI_CC_ID3", foreignKey = @ForeignKey( name = "FK_CI_CC3" ) )
public CreditCard cards3;
@ManyToOne
@JoinColumn( name = "CI_CC_ID4", foreignKey = @ForeignKey( name = "FK_CI_CC4" ) )
public CreditCard cards4;
}
@Entity(name = "Company")
@AssociationOverrides({
@AssociationOverride(
name = "owner",
joinColumns = @JoinColumn( name = "OWNER_PERSON_ID" ),
foreignKey = @ForeignKey( name = "FK_COMPANY_OWNER" )
),
@AssociationOverride(
name = "owner2",
joinColumns = @JoinColumn( name = "OWNER_PERSON_ID2" ),
foreignKey = @ForeignKey( name = "FK_COMPANY_OWNER2", value = ConstraintMode.NO_CONSTRAINT )
),
@AssociationOverride(
name = "creditCard",
joinColumns = @JoinColumn( name = "CREDIT_CARD_ID" ),
foreignKey = @ForeignKey( name = "FK_COMPANY_CREDIT_CARD" )
),
@AssociationOverride(
name = "creditCard2",
joinColumns = @JoinColumn( name = "CREDIT_CARD_ID2" ),
foreignKey = @ForeignKey( name = "FK_COMPANY_CREDIT_CARD2", value = ConstraintMode.NO_CONSTRAINT )
),
@AssociationOverride(
name = "creditCards1",
joinColumns = @JoinColumn( name = "CREDIT_CARD_ID3" ),
foreignKey = @ForeignKey( name = "FK_COMPANY_CREDIT_CARD3" )
),
@AssociationOverride(
name = "creditCards2",
joinColumns = @JoinColumn( name = "CREDIT_CARD_ID4" ),
foreignKey = @ForeignKey( name = "FK_COMPANY_CREDIT_CARD4", value = ConstraintMode.NO_CONSTRAINT )
)
})
public static class Company extends AbstractCompany {
@Embedded
@AssociationOverrides({
@AssociationOverride(
name = "cards",
joinColumns = @JoinColumn( name = "AO_CI_CC_ID" ),
foreignKey = @ForeignKey( name = "FK_COMPANY_CARD" )
),
@AssociationOverride(
name = "cards2",
joinColumns = @JoinColumn( name = "AO_CI_CC_ID2" ),
foreignKey = @ForeignKey( name = "FK_COMPANY_CARD2", value = ConstraintMode.NO_CONSTRAINT )
),
@AssociationOverride(
name = "cards3",
joinColumns = @JoinColumn( name = "AO_CI_CC_ID3" ),
foreignKey = @ForeignKey( name = "FK_COMPANY_CARD3" )
),
@AssociationOverride(
name = "cards4",
joinColumns = @JoinColumn( name = "AO_CI_CC_ID4" ),
foreignKey = @ForeignKey( name = "FK_COMPANY_CARD4", value = ConstraintMode.NO_CONSTRAINT )
)
})
public CompanyInfo info;
}
}