diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index fe8b6424a5..d7cc019179 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -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, diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java index 9b99f59a7a..ca9b021b5a 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Table.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Table.java @@ -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 uniqueKeys = new HashMap(); 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 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> uniqueKeyEntries = uniqueKeys.entrySet().iterator(); + while ( uniqueKeyEntries.hasNext() ) { + final Map.Entry 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) { diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/onetoone/basic/Child.java b/hibernate-core/src/matrix/java/org/hibernate/test/onetoone/basic/Child.java new file mode 100644 index 0000000000..2749c7c03a --- /dev/null +++ b/hibernate-core/src/matrix/java/org/hibernate/test/onetoone/basic/Child.java @@ -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; + } + +} diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/onetoone/basic/Mapping.hbm.xml b/hibernate-core/src/matrix/java/org/hibernate/test/onetoone/basic/Mapping.hbm.xml new file mode 100644 index 0000000000..05abf0da8f --- /dev/null +++ b/hibernate-core/src/matrix/java/org/hibernate/test/onetoone/basic/Mapping.hbm.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/onetoone/basic/OneToOneSchemaTest.java b/hibernate-core/src/matrix/java/org/hibernate/test/onetoone/basic/OneToOneSchemaTest.java new file mode 100644 index 0000000000..569d3e2d98 --- /dev/null +++ b/hibernate-core/src/matrix/java/org/hibernate/test/onetoone/basic/OneToOneSchemaTest.java @@ -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() ); + } +} diff --git a/hibernate-core/src/matrix/java/org/hibernate/test/onetoone/basic/Parent.java b/hibernate-core/src/matrix/java/org/hibernate/test/onetoone/basic/Parent.java new file mode 100644 index 0000000000..7f76495a9d --- /dev/null +++ b/hibernate-core/src/matrix/java/org/hibernate/test/onetoone/basic/Parent.java @@ -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); + } + +}