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,
XClass targetEntity,
PropertyHolder propertyHolder,
PropertyData inferredData, String mappedBy,
PropertyData inferredData,
String mappedBy,
boolean trueOneToOne,
boolean isIdentifierMapper,
boolean inSecondPass,

View File

@ -22,6 +22,7 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.mapping;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
@ -35,7 +36,6 @@ import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.tool.hbm2ddl.ColumnMetadata;
import org.hibernate.tool.hbm2ddl.TableMetadata;
@ -57,7 +57,7 @@ public class Table implements RelationalModel, Serializable {
private PrimaryKey primaryKey;
private Map indexes = 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 boolean quoted;
private boolean schemaQuoted;
@ -248,34 +248,83 @@ public class Table implements RelationalModel, Serializable {
}
Map getUniqueKeys() {
if ( uniqueKeys.size() > 1 ) {
//deduplicate unique constraints sharing the same columns
//this is needed by Hibernate Annotations since it creates automagically
// unique constraints for the user
Iterator it = uniqueKeys.entrySet().iterator();
Map finalUniqueKeys = new HashMap( uniqueKeys.size() );
while ( it.hasNext() ) {
Map.Entry entry = (Map.Entry) it.next();
UniqueKey uk = (UniqueKey) entry.getValue();
List columns = uk.getColumns();
int size = finalUniqueKeys.size();
boolean skip = false;
Iterator tempUks = finalUniqueKeys.entrySet().iterator();
while ( tempUks.hasNext() ) {
final UniqueKey currentUk = (UniqueKey) ( (Map.Entry) tempUks.next() ).getValue();
if ( currentUk.getColumns().containsAll( columns ) && columns
.containsAll( currentUk.getColumns() ) ) {
skip = true;
cleanseUniqueKeyMapIfNeeded();
return uniqueKeys;
}
private int sizeOfUniqueKeyMapOnLastCleanse = 0;
private void cleanseUniqueKeyMapIfNeeded() {
if ( uniqueKeys.size() == sizeOfUniqueKeyMapOnLastCleanse ) {
// nothing to do
return;
}
cleanseUniqueKeyMap();
sizeOfUniqueKeyMapOnLastCleanse = uniqueKeys.size();
}
private void cleanseUniqueKeyMap() {
// We need to account for a few conditions here...
// 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;
}
}
if ( !skip ) finalUniqueKeys.put( entry.getKey(), uk );
// condition 2 : check against pk
if ( isSameAsPrimaryKeyColumns( uniqueKeyEntry.getValue() ) ) {
removeIt = true;
}
if ( removeIt ) {
uniqueKeys.remove( uniqueKeyEntry.getKey() );
}
}
return finalUniqueKeys;
}
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) {

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