HHH-6707 - One-to-One mapping with foreign key in target table and foreign key being the primary key fails with Oracle

This commit is contained in:
Steve Ebersole 2012-06-01 11:59:42 -05:00
parent c02de61f24
commit e26b8be6a5
6 changed files with 252 additions and 25 deletions

View File

@ -2734,7 +2734,8 @@ public final class AnnotationBinder {
boolean cascadeOnDelete, boolean cascadeOnDelete,
XClass targetEntity, XClass targetEntity,
PropertyHolder propertyHolder, PropertyHolder propertyHolder,
PropertyData inferredData, String mappedBy, PropertyData inferredData,
String mappedBy,
boolean trueOneToOne, boolean trueOneToOne,
boolean isIdentifierMapper, boolean isIdentifierMapper,
boolean inSecondPass, boolean inSecondPass,

View File

@ -22,6 +22,7 @@
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*/ */
package org.hibernate.mapping; package org.hibernate.mapping;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -35,7 +36,6 @@ import org.hibernate.HibernateException;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.Mapping;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.tool.hbm2ddl.ColumnMetadata; import org.hibernate.tool.hbm2ddl.ColumnMetadata;
import org.hibernate.tool.hbm2ddl.TableMetadata; import org.hibernate.tool.hbm2ddl.TableMetadata;
@ -57,7 +57,7 @@ public class Table implements RelationalModel, Serializable {
private PrimaryKey primaryKey; private PrimaryKey primaryKey;
private Map indexes = new HashMap(); private Map indexes = new HashMap();
private Map foreignKeys = new HashMap(); private Map foreignKeys = new HashMap();
private Map uniqueKeys = new HashMap(); private Map<String,UniqueKey> uniqueKeys = new HashMap<String,UniqueKey>();
private final int uniqueInteger; private final int uniqueInteger;
private boolean quoted; private boolean quoted;
private boolean schemaQuoted; private boolean schemaQuoted;
@ -248,34 +248,83 @@ public class Table implements RelationalModel, Serializable {
} }
Map getUniqueKeys() { Map getUniqueKeys() {
if ( uniqueKeys.size() > 1 ) { cleanseUniqueKeyMapIfNeeded();
//deduplicate unique constraints sharing the same columns return uniqueKeys;
//this is needed by Hibernate Annotations since it creates automagically }
// unique constraints for the user
Iterator it = uniqueKeys.entrySet().iterator(); private int sizeOfUniqueKeyMapOnLastCleanse = 0;
Map finalUniqueKeys = new HashMap( uniqueKeys.size() );
while ( it.hasNext() ) { private void cleanseUniqueKeyMapIfNeeded() {
Map.Entry entry = (Map.Entry) it.next(); if ( uniqueKeys.size() == sizeOfUniqueKeyMapOnLastCleanse ) {
UniqueKey uk = (UniqueKey) entry.getValue(); // nothing to do
List columns = uk.getColumns(); return;
int size = finalUniqueKeys.size(); }
boolean skip = false; cleanseUniqueKeyMap();
Iterator tempUks = finalUniqueKeys.entrySet().iterator(); sizeOfUniqueKeyMapOnLastCleanse = uniqueKeys.size();
while ( tempUks.hasNext() ) { }
final UniqueKey currentUk = (UniqueKey) ( (Map.Entry) tempUks.next() ).getValue();
if ( currentUk.getColumns().containsAll( columns ) && columns private void cleanseUniqueKeyMap() {
.containsAll( currentUk.getColumns() ) ) { // We need to account for a few conditions here...
skip = true; // 1) If there are multiple unique keys contained in the uniqueKeys Map, we need to deduplicate
// any sharing the same columns as other defined unique keys; this is needed for the annotation
// processor since it creates unique constraints automagically for the user
// 2) Remove any unique keys that share the same columns as the primary key; again, this is
// needed for the annotation processor to handle @Id @OneToOne cases. In such cases the
// unique key is unnecessary because a primary key is already unique by definition. We handle
// this case specifically because some databases fail if you try to apply a unique key to
// the primary key columns which causes schema export to fail in these cases.
if ( uniqueKeys.isEmpty() ) {
// nothing to do
return;
}
else if ( uniqueKeys.size() == 1 ) {
// we have to worry about condition 2 above, but not condition 1
final Map.Entry<String,UniqueKey> uniqueKeyEntry = uniqueKeys.entrySet().iterator().next();
if ( isSameAsPrimaryKeyColumns( uniqueKeyEntry.getValue() ) ) {
uniqueKeys.remove( uniqueKeyEntry.getKey() );
}
}
else {
// we have to check both conditions 1 and 2
final Iterator<Map.Entry<String,UniqueKey>> uniqueKeyEntries = uniqueKeys.entrySet().iterator();
while ( uniqueKeyEntries.hasNext() ) {
final Map.Entry<String,UniqueKey> uniqueKeyEntry = uniqueKeyEntries.next();
final UniqueKey uniqueKey = uniqueKeyEntry.getValue();
boolean removeIt = false;
// condition 1 : check against other unique keys
for ( UniqueKey otherUniqueKey : uniqueKeys.values() ) {
// make sure its not the same unique key
if ( uniqueKeyEntry.getValue() == otherUniqueKey ) {
continue;
}
if ( otherUniqueKey.getColumns().containsAll( uniqueKey.getColumns() )
&& uniqueKey.getColumns().containsAll( otherUniqueKey.getColumns() ) ) {
removeIt = true;
break; break;
} }
} }
if ( !skip ) finalUniqueKeys.put( entry.getKey(), uk );
// condition 2 : check against pk
if ( isSameAsPrimaryKeyColumns( uniqueKeyEntry.getValue() ) ) {
removeIt = true;
} }
return finalUniqueKeys;
if ( removeIt ) {
uniqueKeys.remove( uniqueKeyEntry.getKey() );
} }
else {
return uniqueKeys;
} }
}
}
private boolean isSameAsPrimaryKeyColumns(UniqueKey uniqueKey) {
if ( primaryKey == null || ! primaryKey.columnIterator().hasNext() ) {
// happens for many-to-many tables
return false;
}
return primaryKey.getColumns().containsAll( uniqueKey.getColumns() )
&& uniqueKey.getColumns().containsAll( primaryKey.getColumns() );
} }
public void validateColumns(Dialect dialect, Mapping mapping, TableMetadata tableInfo) { public void validateColumns(Dialect dialect, Mapping mapping, TableMetadata tableInfo) {

View File

@ -0,0 +1,52 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, 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.onetoone.basic;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
/**
* @author Florian Rampp
* @author Steve Ebersole
*/
@Entity
@Table( name = "CHILD")
public class Child {
@Id
// A @OneToOne here results in the following DDL: create table child ([...] primary key
// (parent), unique (parent)).
// Oracle does not like a unique constraint and a PK on the same column (results in ORA-02261)
@OneToOne(optional = false)
private Parent parent;
public void setParent(Parent parent) {
this.parent = parent;
}
}

View File

@ -0,0 +1,19 @@
<hibernate-mapping package="org.hibernate.test.onetoone.basic" default-access="field">
<class name="Parent">
<id name="id"/>
<one-to-one name="child" cascade="all" constrained="false" outer-join="false" lazy="proxy"/>
</class>
<class name="Child">
<id name=""
</class>
<class name="Address">
<id name="entityName"/>
<property name="street"/>
<property name="state"/>
<property name="zip"/>
</class>
</hibernate-mapping>

View File

@ -0,0 +1,57 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, 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.onetoone.basic;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.mapping.Table;
import org.junit.Test;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import static org.junit.Assert.assertFalse;
/**
* @author Steve Ebersole
*/
public class OneToOneSchemaTest extends BaseUnitTestCase {
@Test
public void testUniqueKeyNotGeneratedViaAnnotations() throws Exception {
Configuration cfg = new Configuration()
.addAnnotatedClass( Parent.class )
.addAnnotatedClass( Child.class )
.setProperty( Environment.HBM2DDL_AUTO, "create" );
probeForUniqueKey( cfg );
}
private void probeForUniqueKey(Configuration cfg) {
cfg.buildMappings();
Table childTable = cfg.createMappings().getTable( null, null, "CHILD" );
assertFalse( "UniqueKey was generated when it should not", childTable.getUniqueKeyIterator().hasNext() );
}
}

View File

@ -0,0 +1,49 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, 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.onetoone.basic;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
/**
* @author Florian Rampp
* @author Steve Ebersole
*/
@Entity
public class Parent {
@Id
Long id;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "parent")
Child child;
void setChild(Child child) {
this.child = child;
child.setParent(this);
}
}