From: <hib...@li...> - 2006-04-30 04:08:57
|
Author: epbernard Date: 2006-04-30 00:08:48 -0400 (Sun, 30 Apr 2006) New Revision: 9833 Added: trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/manytoone/BiggestForest.java trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/manytoone/ForestType.java trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/manytoone/ManyToOneJoinTest.java trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/manytoone/TreeType.java Modified: trunk/HibernateExt/metadata/doc/reference/en/modules/entity.xml trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/AbstractPropertyHolder.java trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/AnnotationBinder.java trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/ClassPropertyHolder.java trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/CollectionPropertyHolder.java trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/ComponentPropertyHolder.java trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/Ejb3JoinColumn.java trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/PropertyHolder.java trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/PropertyHolderBuilder.java trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/ToOneMappedBySecondPass.java trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/annotations/CollectionBinder.java trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/annotations/EntityBinder.java trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/annotations/TableBinder.java trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/join/Death.java trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/join/JoinTest.java trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/onetoone/OneToOneTest.java Log: ANN-158 to one associations through an association table (bidirectional) Modified: trunk/HibernateExt/metadata/doc/reference/en/modules/entity.xml =================================================================== --- trunk/HibernateExt/metadata/doc/reference/en/modules/entity.xml 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/doc/reference/en/modules/entity.xml 2006-04-30 04:08:48 UTC (rev 9833) @@ -961,11 +961,13 @@ <title>One-to-one</title> <para>You can associate entity beans through a one-to-one relationship - using <literal>@OneToOne</literal>. There are two cases for one-to-one - associations: either the associated entities share the same primary - keys values or a foreign key is held by one of the entities (note that - this FK column in the database should be constrained unique to - simulate one-to-one multiplicity).</para> + using <literal>@OneToOne</literal>. There are three cases for + one-to-one associations: either the associated entities share the same + primary keys values, a foreign key is held by one of the entities + (note that this FK column in the database should be constrained unique + to simulate one-to-one multiplicity), or a association table is used + to store the link between the 2 entities (a unique constraint has to + be defined on each fk to ensure the one to one multiplicity)</para> <para>First, we map a real one-to-one association using shared primary keys:</para> @@ -1051,6 +1053,53 @@ example <literal>passport_id</literal> because the property name is <literal>passport</literal> and the column id of <literal>Passport </literal>is <literal>id</literal>.</para> + + <para>The third possibility (using an association table) is very + exotic.</para> + + <programlisting> +@Entity +public class Customer implements Serializable { + @OneToOne(cascade = CascadeType.ALL) + <emphasis role="bold">@JoinTable(name = "CustomerPassports" + joinColumns = @JoinColumn(name="customer_fk"), + inverseJoinColumns = @JoinColumns(name="passport_fk")</emphasis> + ) + public Passport getPassport() { + ... + } + +@Entity +public class Passport implements Serializable { + @OneToOne(<emphasis role="bold">mappedBy = "passport"</emphasis>) + public Customer getOwner() { + ... +} + </programlisting> + + <para>A <classname>Customer</classname> is linked to a + <classname>Passport</classname> through a association table named + <literal>CustomerPassports</literal> ; this association table has a + foreign key column named <literal>passport_fk</literal> pointing to + the <literal>Passport</literal> table (materialized by the + <literal>inverseJoinColumn</literal>, and a foreign key column named + <literal>customer_fk</literal> pointing to the + <literal>Customer</literal> table materialized by the + <literal>joinColumns</literal> attribute.</para> + + <para>The association may be bidirectional. In a bidirectional + relationship, one of the sides (and only one) has to be the owner: the + owner is responsible for the association column(s) update. To declare + a side as <emphasis>not</emphasis> responsible for the relationship, + the attribute <literal>mappedBy</literal> is used. + <literal>mappedBy</literal> refers to the property name of the + association on the owner side. In our case, this is + <literal>passport</literal>. As you can see, you don't have to (must + not) declare the join column since it has already been declared on the + owners side.</para> + + <para>You must declare the join table name and the join columns + explicitly in such a mapping.</para> </sect3> <sect3> @@ -1101,6 +1150,29 @@ public interface Company { ... </programlisting> + + <para>You can alse map a many to one association through an + association table. This association table described by the + <literal>@JoinTable</literal> annotation will contains a foreign key + referencing back the entity table (through + <literal>@JoinTable.joinColumns</literal>) and a a foreign key + referencing the target entity table (through + <literal>@JoinTable.inverseJoinColumns</literal>).</para> + + <programlisting> +@Entity() +public class Flight implements Serializable { + @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} ) + <emphasis role="bold">@JoinTable(name="Flight_Company", + joinColumns = @JoinColumn(name="FLIGHT_ID"), + inverseJoinColumns = @JoinColumns(name="COMP_ID") + )</emphasis> + public Company getCompany() { + return company; + } + ... +} + </programlisting> </sect3> <sect3 id="entity-mapping-association-collections"> @@ -1308,8 +1380,8 @@ side as the owning side, you have to remove the <literal>mappedBy</literal> element and set the many to one <literal>@JoinColumn</literal> as insertable and updatable to - false. This solution is obviously not optimized from the number of - needed statements.</para> + false. This solution is obviously not optimized and will produce + some additional UPDATE statements.</para> <programlisting>@Entity public class Troop { @@ -2563,7 +2635,7 @@ <para>EJB3 comes with the <literal>fetch</literal> option to define lazy loading and fetching modes, however Hibernate has a much more option set in this area. To fine tune the lazy loading and fetching - strategies, some additional annotations have been introduced: </para> + strategies, some additional annotations have been introduced:</para> <itemizedlist> <listitem> @@ -2589,7 +2661,7 @@ </listitem> <listitem> - <para> <literal>@Fetch</literal>: defines the fetching strategy + <para><literal>@Fetch</literal>: defines the fetching strategy used to load the association. <literal>FetchMode</literal> can be <literal>SELECT</literal> (a select is triggered when the association needs to be loaded), <literal>SUBSELECT</literal> Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/AbstractPropertyHolder.java =================================================================== --- trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/AbstractPropertyHolder.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/AbstractPropertyHolder.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -13,17 +13,17 @@ import javax.persistence.JoinColumn; import javax.persistence.MappedSuperclass; +import org.hibernate.AssertionFailure; import org.hibernate.reflection.XAnnotatedElement; import org.hibernate.reflection.XClass; import org.hibernate.reflection.XProperty; import org.hibernate.util.StringHelper; -import org.hibernate.AssertionFailure; /** * @author Emmanuel Bernard */ public abstract class AbstractPropertyHolder implements PropertyHolder { - private PropertyHolder parent; + protected PropertyHolder parent; private Map<String, Column[]> holderColumnOverride; private Map<String, Column[]> currentPropertyColumnOverride; private Map<String, JoinColumn[]> holderJoinColumnOverride; @@ -183,6 +183,6 @@ } public void setParentProperty(String parentProperty) { - throw new AssertionFailure("Setting the parent property to a non component"); + throw new AssertionFailure( "Setting the parent property to a non component" ); } } Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/AnnotationBinder.java =================================================================== --- trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/AnnotationBinder.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/AnnotationBinder.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -61,6 +61,7 @@ import org.hibernate.annotations.Check; import org.hibernate.annotations.CollectionOfElements; import org.hibernate.annotations.Columns; +import org.hibernate.annotations.Fetch; import org.hibernate.annotations.Filter; import org.hibernate.annotations.FilterDef; import org.hibernate.annotations.FilterDefs; @@ -68,6 +69,8 @@ import org.hibernate.annotations.Formula; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Index; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.OnDelete; @@ -75,16 +78,13 @@ import org.hibernate.annotations.OrderBy; import org.hibernate.annotations.ParamDef; import org.hibernate.annotations.Parameter; +import org.hibernate.annotations.Parent; import org.hibernate.annotations.Proxy; import org.hibernate.annotations.Sort; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.hibernate.annotations.TypeDefs; import org.hibernate.annotations.Where; -import org.hibernate.annotations.Parent; -import org.hibernate.annotations.LazyToOne; -import org.hibernate.annotations.Fetch; -import org.hibernate.annotations.LazyToOneOption; import org.hibernate.cfg.annotations.CollectionBinder; import org.hibernate.cfg.annotations.EntityBinder; import org.hibernate.cfg.annotations.Nullability; @@ -110,8 +110,8 @@ import org.hibernate.mapping.SingleTableSubclass; import org.hibernate.mapping.Subclass; import org.hibernate.mapping.Table; +import org.hibernate.mapping.ToOne; import org.hibernate.mapping.UnionSubclass; -import org.hibernate.mapping.ToOne; import org.hibernate.persister.entity.JoinedSubclassEntityPersister; import org.hibernate.persister.entity.SingleTableEntityPersister; import org.hibernate.persister.entity.UnionSubclassEntityPersister; @@ -377,6 +377,7 @@ final boolean hasJoinedColumns = inheritanceState.hasParents && InheritanceType.JOINED.equals( inheritanceState.type ); if ( hasJoinedColumns ) { + //@Inheritance(JOINED) subclass need to link back to the super entity PrimaryKeyJoinColumns jcsAnn = annotatedClass.getAnnotation( PrimaryKeyJoinColumns.class ); boolean explicitInheritanceJoinedColumns = jcsAnn != null && jcsAnn.value().length != 0; if ( explicitInheritanceJoinedColumns ) { @@ -386,7 +387,7 @@ for ( int colIndex = 0; colIndex < nbrOfInhJoinedColumns ; colIndex++ ) { jcAnn = jcsAnn.value()[colIndex]; inheritanceJoinedColumns[colIndex] = Ejb3JoinColumn.buildJoinColumn( - jcAnn, superEntity.getIdentifier(), + jcAnn, null, superEntity.getIdentifier(), (Map<String, Join>) null, (PropertyHolder) null, mappings ); } @@ -395,7 +396,7 @@ PrimaryKeyJoinColumn jcAnn = annotatedClass.getAnnotation( PrimaryKeyJoinColumn.class ); inheritanceJoinedColumns = new Ejb3JoinColumn[1]; inheritanceJoinedColumns[0] = Ejb3JoinColumn.buildJoinColumn( - jcAnn, superEntity.getIdentifier(), + jcAnn, null, superEntity.getIdentifier(), (Map<String, Join>) null, (PropertyHolder) null, mappings ); } @@ -498,7 +499,7 @@ PropertyHolder propertyHolder = PropertyHolderBuilder.buildPropertyHolder( clazzToProcess, persistentClass, - entityBinder.getSecondaryTables(), mappings + entityBinder, mappings ); javax.persistence.SecondaryTable secTabAnn = annotatedClass.getAnnotation( @@ -507,9 +508,7 @@ javax.persistence.SecondaryTables secTabsAnn = annotatedClass.getAnnotation( javax.persistence.SecondaryTables.class ); - PrimaryKeyJoinColumn joinColAnn = annotatedClass.getAnnotation( PrimaryKeyJoinColumn.class ); - PrimaryKeyJoinColumns joinColsAnn = annotatedClass.getAnnotation( PrimaryKeyJoinColumns.class ); - entityBinder.firstLevelSecondaryTablesBinding( secTabAnn, secTabsAnn, joinColAnn, joinColsAnn ); + entityBinder.firstLevelSecondaryTablesBinding( secTabAnn, secTabsAnn ); OnDelete onDeleteAnn = annotatedClass.getAnnotation( OnDelete.class ); boolean onDeleteAppropriate = false; @@ -977,8 +976,10 @@ propertyHolder.setParentProperty( property.getName() ); } else { - throw new AnnotationException("@Parent cannot be applied outside an embeddable object: " - + StringHelper.qualify( propertyHolder.getPath(), property.getName() ) ); + throw new AnnotationException( + "@Parent cannot be applied outside an embeddable object: " + + StringHelper.qualify( propertyHolder.getPath(), property.getName() ) + ); } return; } @@ -1026,11 +1027,17 @@ || property.isAnnotationPresent( OneToOne.class ) ) ) { if ( property.isAnnotationPresent( JoinTable.class ) ) { - JoinTable assocTable = property.getAnnotation( JoinTable.class ); - //entityBinder.firstLevelSecondaryTablesBinding(assocTable); - throw new NotYetImplementedException( - "association table on a single ended association is not yet supported" + JoinTable joinTableAnn = property.getAnnotation( JoinTable.class ); + joinColumns = Ejb3JoinColumn.buildJoinColumns( + joinTableAnn.inverseJoinColumns(), null, entityBinder.getSecondaryTables(), + propertyHolder, inferredData.getPropertyName(), mappings ); + if ( StringHelper.isEmpty( joinTableAnn.name() ) ) { + throw new AnnotationException( + "JoinTable.name() on a @ToOne association has to be explicit: " + + StringHelper.qualify( propertyHolder.getPath(), inferredData.getPropertyName() ) + ); + } } else { OneToOne oneToOneAnn = property.getAnnotation( OneToOne.class ); @@ -1178,6 +1185,13 @@ boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE ); OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ); + JoinTable assocTable = property.getAnnotation( JoinTable.class ); + if ( assocTable != null ) { + Join join = propertyHolder.addJoin( assocTable, false ); + for ( Ejb3JoinColumn joinColumn : joinColumns ) { + joinColumn.setSecondaryTableName( join.getTable().getName() ); + } + } bindManyToOne( getCascadeStrategy( ann.cascade(), hibernateCascade ), joinColumns, @@ -1198,6 +1212,13 @@ boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE ); OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ); + JoinTable assocTable = property.getAnnotation( JoinTable.class ); + if ( assocTable != null ) { + Join join = propertyHolder.addJoin( assocTable, false ); + for ( Ejb3JoinColumn joinColumn : joinColumns ) { + joinColumn.setSecondaryTableName( join.getTable().getName() ); + } + } bindOneToOne( getCascadeStrategy( ann.cascade(), hibernateCascade ), joinColumns, @@ -1264,7 +1285,7 @@ collectionBinder.setPropertyAccessorName( inferredData.getDefaultAccess() ); Ejb3Column[] elementColumns = null; - PropertyData wrappedInferredData = new WrappedInferredData(inferredData, "element"); + PropertyData wrappedInferredData = new WrappedInferredData( inferredData, "element" ); if ( property.isAnnotationPresent( Column.class ) || property.isAnnotationPresent( Formula.class ) ) { @@ -1299,8 +1320,10 @@ ); } - org.hibernate.annotations.MapKey hibMapKeyAnn = property.getAnnotation( org.hibernate.annotations.MapKey.class ); - wrappedInferredData = new WrappedInferredData(inferredData, "key"); + org.hibernate.annotations.MapKey hibMapKeyAnn = property.getAnnotation( + org.hibernate.annotations.MapKey.class + ); + wrappedInferredData = new WrappedInferredData( inferredData, "key" ); Ejb3Column[] mapColumns = Ejb3Column.buildColumnFromAnnotation( hibMapKeyAnn != null && hibMapKeyAnn.columns().length > 0 ? hibMapKeyAnn.columns() : null, null, @@ -1310,7 +1333,7 @@ entityBinder.getSecondaryTables(), mappings ); - collectionBinder.setMapKeyColumns(mapColumns); + collectionBinder.setMapKeyColumns( mapColumns ); //potential element collectionBinder.setEmbedded( property.isAnnotationPresent( Embedded.class ) ); @@ -1724,24 +1747,26 @@ ManyToOne manyToOne = property.getAnnotation( ManyToOne.class ); OneToOne oneToOne = property.getAnnotation( OneToOne.class ); FetchType fetchType; - if (manyToOne != null) { + if ( manyToOne != null ) { fetchType = manyToOne.fetch(); } - else if (oneToOne != null) { + else if ( oneToOne != null ) { fetchType = oneToOne.fetch(); } else { - throw new AssertionFailure( "Define fetch strategy on a property not annotated with @OneToMany nor @OneToOne"); + throw new AssertionFailure( + "Define fetch strategy on a property not annotated with @OneToMany nor @OneToOne" + ); } - if (lazy != null) { - toOne.setLazy( ! (lazy.value() == LazyToOneOption.FALSE) ); - toOne.setUnwrapProxy( (lazy.value() == LazyToOneOption.NO_PROXY) ); + if ( lazy != null ) { + toOne.setLazy( ! ( lazy.value() == LazyToOneOption.FALSE ) ); + toOne.setUnwrapProxy( ( lazy.value() == LazyToOneOption.NO_PROXY ) ); } else { toOne.setLazy( fetchType == FetchType.LAZY ); toOne.setUnwrapProxy( false ); } - if (fetch != null) { + if ( fetch != null ) { if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) { toOne.setFetchMode( FetchMode.JOIN ); toOne.setLazy( false ); @@ -1751,7 +1776,7 @@ toOne.setFetchMode( FetchMode.SELECT ); } else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) { - throw new AnnotationException( "Use of FetchMode.SUBSELECT not allowed on ToOne associations"); + throw new AnnotationException( "Use of FetchMode.SUBSELECT not allowed on ToOne associations" ); } else { throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() ); @@ -1798,16 +1823,19 @@ if ( trueOneToOne || mapToPK || ! isDefault( mappedBy ) ) { //is a true one-to-one //FIXME referencedColumnName ignored => ordering may fail. + org.hibernate.mapping.OneToOne value = new org.hibernate.mapping.OneToOne( propertyHolder.getTable(), propertyHolder.getPersistentClass() ); value.setPropertyName( propertyName ); + String referencedEntityName; if ( isDefault( targetEntity, mappings ) ) { - value.setReferencedEntityName( inferredData.getClassOrElementName() ); + referencedEntityName = inferredData.getClassOrElementName(); } else { - value.setReferencedEntityName( targetEntity.getName() ); + referencedEntityName = targetEntity.getName(); } + value.setReferencedEntityName( referencedEntityName ); defineFetchingStrategy( value, inferredData.getProperty() ); //value.setFetchMode( fetchMode ); value.setCascadeDeleteEnabled( cascadeOnDelete ); @@ -1819,7 +1847,12 @@ ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT : ForeignKeyDirection.FOREIGN_KEY_TO_PARENT ); - + PropertyBinder binder = new PropertyBinder(); + binder.setName( propertyName ); + binder.setValue( value ); + binder.setCascade( cascadeStrategy ); + binder.setPropertyAccessorName( inferredData.getDefaultAccess() ); + Property prop = binder.make(); if ( ! isDefault( mappedBy ) ) { mappings.addSecondPass( new ToOneMappedBySecondPass( @@ -1827,7 +1860,7 @@ value, propertyHolder.getEntityName(), propertyName, - mappings + prop, propertyHolder, ignoreNotFound, mappings ) ); } @@ -1840,17 +1873,11 @@ path, mappings ) ); + //no column associated since its a one to one + propertyHolder.addProperty( prop ); } - PropertyBinder binder = new PropertyBinder(); - binder.setName( propertyName ); - binder.setValue( value ); - binder.setCascade( cascadeStrategy ); - binder.setPropertyAccessorName( inferredData.getDefaultAccess() ); - Property prop = binder.make(); - prop.setCascade( cascadeStrategy ); - //no column associated since its a one to one - propertyHolder.addProperty( prop ); + } else { //has a FK on the table Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/ClassPropertyHolder.java =================================================================== --- trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/ClassPropertyHolder.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/ClassPropertyHolder.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -3,7 +3,9 @@ import java.util.HashMap; import java.util.Map; +import javax.persistence.JoinTable; +import org.hibernate.cfg.annotations.EntityBinder; import org.hibernate.mapping.Component; import org.hibernate.mapping.Join; import org.hibernate.mapping.KeyValue; @@ -19,6 +21,7 @@ private PersistentClass persistentClass; private Map<String, Join> joins; private transient Map<String, Join> joinsPerRealTableName; + private EntityBinder entityBinder; public ClassPropertyHolder( PersistentClass persistentClass, XClass clazzToProcess, Map<String, Join> joins, ExtendedMappings mappings @@ -28,6 +31,14 @@ this.joins = joins; } + public ClassPropertyHolder( + PersistentClass persistentClass, XClass clazzToProcess, EntityBinder entityBinder, + ExtendedMappings mappings + ) { + this( persistentClass, clazzToProcess, entityBinder.getSecondaryTables(), mappings ); + this.entityBinder = entityBinder; + } + public String getEntityName() { return persistentClass.getEntityName(); } @@ -43,6 +54,12 @@ } } + public Join addJoin(JoinTable joinTableAnn, boolean noDelayInPkColumnCreation) { + Join join = entityBinder.addJoin( joinTableAnn, this, noDelayInPkColumnCreation ); + this.joins = entityBinder.getSecondaryTables(); + return join; + } + public void addProperty(Property prop) { if ( prop.getValue() instanceof Component ) { //TODO handle quote and non quote table comparison Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/CollectionPropertyHolder.java =================================================================== --- trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/CollectionPropertyHolder.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/CollectionPropertyHolder.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -1,8 +1,11 @@ //$Id$ package org.hibernate.cfg; +import javax.persistence.JoinTable; + import org.hibernate.AssertionFailure; import org.hibernate.mapping.Collection; +import org.hibernate.mapping.Join; import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; @@ -57,4 +60,8 @@ //Ejb3Column.checkPropertyConsistency( ); //already called earlier throw new AssertionFailure( "addProperty to a join table of a collection: does it make sense?" ); } + + public Join addJoin(JoinTable joinTableAnn, boolean noDelayInPkColumnCreation) { + throw new AssertionFailure( "Add a <join> in a second pass" ); + } } Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/ComponentPropertyHolder.java =================================================================== --- trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/ComponentPropertyHolder.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/ComponentPropertyHolder.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -3,6 +3,7 @@ import javax.persistence.Column; import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; import org.hibernate.AnnotationException; import org.hibernate.mapping.Component; @@ -10,6 +11,7 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.Table; +import org.hibernate.mapping.Join; /** * Component implementation of property holder @@ -47,6 +49,11 @@ addProperty( prop ); } + public Join addJoin(JoinTable joinTableAnn, boolean noDelayInPkColumnCreation) { + return parent.addJoin( joinTableAnn, noDelayInPkColumnCreation ); + + } + public ComponentPropertyHolder( Component component, String path, PropertyData inferredData, PropertyHolder parent, ExtendedMappings mappings Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/Ejb3JoinColumn.java =================================================================== --- trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/Ejb3JoinColumn.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/Ejb3JoinColumn.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -114,7 +114,7 @@ StringHelper.qualify( propertyHolder.getPath(), propertyName ) ); if ( actualColumns == null ) actualColumns = anns; - if ( actualColumns == null ) { + if ( actualColumns == null || actualColumns.length == 0 ) { return new Ejb3JoinColumn[]{ buildJoinColumn( (JoinColumn) null, mappedBy, joins, propertyHolder, propertyName, mappings ) }; @@ -199,7 +199,8 @@ * Build JoinColumn for a JOINED hierarchy */ public static Ejb3JoinColumn buildJoinColumn( - PrimaryKeyJoinColumn ann, + PrimaryKeyJoinColumn pkJoinAnn, + JoinColumn joinAnn, Value identifier, Map<String, Join> joins, PropertyHolder propertyHolder, ExtendedMappings mappings @@ -207,14 +208,27 @@ Column col = (Column) identifier.getColumnIterator().next(); String defaultName = mappings.getLogicalColumnName( col.getName(), identifier.getTable() ); - if ( ann != null ) { - String sqlType = ann.columnDefinition().equals( "" ) ? null : ann.columnDefinition(); - String name = ann.name().equals( "" ) ? defaultName : ann.name(); + if ( pkJoinAnn != null || joinAnn != null ) { + String colName; + String columnDefinition; + String referencedColumnName; + if ( pkJoinAnn != null ) { + colName = pkJoinAnn.name(); + columnDefinition = pkJoinAnn.columnDefinition(); + referencedColumnName = pkJoinAnn.referencedColumnName(); + } + else { + colName = joinAnn.name(); + columnDefinition = joinAnn.columnDefinition(); + referencedColumnName = joinAnn.referencedColumnName(); + } + String sqlType = "".equals( columnDefinition ) ? null : columnDefinition; + String name = "".equals( colName ) ? defaultName : colName; return new Ejb3JoinColumn( sqlType, name, false, false, true, true, - ann.referencedColumnName(), + referencedColumnName, null, joins, propertyHolder, null, null, false, mappings ); @@ -230,10 +244,11 @@ /** * Override persistent class on oneToMany Cases for late settings + * Must only be used on second level pass binding */ public void setPersistentClass(PersistentClass persistentClass, Map<String, Join> joins) { //FIXME shouldn't we deduce the classname from the persistentclasS? - this.propertyHolder = PropertyHolderBuilder.buildPropertyHolder( null, persistentClass, joins, getMappings() ); + this.propertyHolder = PropertyHolderBuilder.buildPropertyHolder( persistentClass, joins, getMappings() ); } public static void checkIfJoinColumn(Object columns, PropertyHolder holder, PropertyData property) { Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/PropertyHolder.java =================================================================== --- trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/PropertyHolder.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/PropertyHolder.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -2,11 +2,13 @@ import javax.persistence.Column; import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.Table; +import org.hibernate.mapping.Join; /** * Property holder abstract property containers from their direct implementation @@ -43,4 +45,6 @@ String getEntityName(); void addProperty(Property prop, Ejb3Column[] columns); + + Join addJoin(JoinTable joinTableAnn, boolean noDelayInPkColumnCreation); } Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/PropertyHolderBuilder.java =================================================================== --- trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/PropertyHolderBuilder.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/PropertyHolderBuilder.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -3,6 +3,7 @@ import java.util.Map; +import org.hibernate.cfg.annotations.EntityBinder; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Component; import org.hibernate.mapping.Join; @@ -22,9 +23,11 @@ public static PropertyHolder buildPropertyHolder( XClass clazzToProcess, PersistentClass persistentClass, - Map<String, Join> joins, ExtendedMappings mappings + EntityBinder entityBinder, + //Map<String, Join> joins, + ExtendedMappings mappings ) { - return (PropertyHolder) new ClassPropertyHolder( persistentClass, clazzToProcess, joins, mappings ); + return (PropertyHolder) new ClassPropertyHolder( persistentClass, clazzToProcess, entityBinder, mappings ); } /** @@ -52,10 +55,13 @@ return new CollectionPropertyHolder( collection, path, clazzToProcess, property, mappings ); } + /** + * must only be used on second level phases (<join> has to be settled already) + */ public static PropertyHolder buildPropertyHolder( PersistentClass persistentClass, Map<String, Join> joins, ExtendedMappings mappings ) { - return buildPropertyHolder( null, persistentClass, joins, mappings ); + return (PropertyHolder) new ClassPropertyHolder( persistentClass, null, joins, mappings ); } } Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/ToOneMappedBySecondPass.java =================================================================== --- trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/ToOneMappedBySecondPass.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/ToOneMappedBySecondPass.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -1,42 +1,55 @@ //$Id$ package org.hibernate.cfg; +import java.util.Iterator; import java.util.Map; import org.hibernate.AnnotationException; import org.hibernate.MappingException; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.DependantValue; +import org.hibernate.mapping.Join; import org.hibernate.mapping.ManyToOne; import org.hibernate.mapping.OneToOne; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.ToOne; import org.hibernate.util.StringHelper; public class ToOneMappedBySecondPass implements SecondPass { private String mappedBy; private ToOne value; - private Mappings mappings; + private ExtendedMappings mappings; private String ownerEntity; private String ownerProperty; + private PropertyHolder propertyHolder; + private Property property; + private boolean ignoreNotFound; public ToOneMappedBySecondPass( - String mappedBy, ToOne value, String ownerEntity, String ownerProperty, Mappings mappings + String mappedBy, ToOne value, String ownerEntity, String ownerProperty, Property property, + PropertyHolder propertyHolder, boolean ignoreNotFound, + ExtendedMappings mappings ) { this.ownerEntity = ownerEntity; this.ownerProperty = ownerProperty; this.mappedBy = mappedBy; this.value = value; + this.propertyHolder = propertyHolder; this.mappings = mappings; + this.property = property; + this.ignoreNotFound = ignoreNotFound; } public void doSecondPass(Map persistentClasses, Map inheritedMetas) throws MappingException { PersistentClass otherSide = (PersistentClass) persistentClasses.get( value.getReferencedEntityName() ); - Property property; + Property otherSideProperty; try { if ( otherSide == null ) { throw new MappingException( "Unable to find entity: " + value.getReferencedEntityName() ); } - property = otherSide.getProperty( mappedBy ); + otherSideProperty = otherSide.getProperty( mappedBy ); } catch (MappingException e) { throw new AnnotationException( @@ -45,10 +58,56 @@ + StringHelper.qualify( value.getReferencedEntityName(), mappedBy ) ); } - if ( property.getValue() instanceof OneToOne ) { - //do nothing + if ( otherSideProperty.getValue() instanceof OneToOne ) { + propertyHolder.addProperty( property ); } - else if ( property.getValue() instanceof ManyToOne ) { + else if ( otherSideProperty.getValue() instanceof ManyToOne ) { + Iterator it = otherSide.getJoinIterator(); + Join otherSideJoin = null; + while ( it.hasNext() ) { + otherSideJoin = (Join) it.next(); + if ( otherSideJoin.containsProperty( otherSideProperty ) ) { + break; + } + } + if ( otherSideJoin != null ) { + //@OneToOne @JoinTable + Join mappedByJoin = buildJoin( + (PersistentClass) persistentClasses.get( ownerEntity ), otherSideProperty, otherSideJoin + ); + ManyToOne manyToOne = new ManyToOne( mappedByJoin.getTable() ); + //FIXME use ignore not found here + manyToOne.setIgnoreNotFound( ignoreNotFound ); + manyToOne.setCascadeDeleteEnabled( value.isCascadeDeleteEnabled() ); + manyToOne.setEmbedded( value.isEmbedded() ); + manyToOne.setFetchMode( value.getFetchMode() ); + manyToOne.setLazy( value.isLazy() ); + manyToOne.setReferencedEntityName( value.getReferencedEntityName() ); + manyToOne.setUnwrapProxy( value.isUnwrapProxy() ); + property.setValue( manyToOne ); + Iterator otherSideJoinKeyColumns = otherSideJoin.getKey().getColumnIterator(); + while ( otherSideJoinKeyColumns.hasNext() ) { + Column column = (Column) otherSideJoinKeyColumns.next(); + Column copy = new Column(); + copy.setLength( column.getLength() ); + copy.setScale( column.getScale() ); + copy.setValue( manyToOne ); + copy.setName( column.getQuotedName() ); + copy.setNullable( column.isNullable() ); + copy.setPrecision( column.getPrecision() ); + copy.setUnique( column.isUnique() ); + copy.setSqlType( column.getSqlType() ); + copy.setCheckConstraint( column.getCheckConstraint() ); + copy.setComment( column.getComment() ); + copy.setDefaultValue( column.getDefaultValue() ); + manyToOne.addColumn( copy ); + } + mappedByJoin.addProperty( property ); + } + else { + propertyHolder.addProperty( property ); + } + value.setReferencedPropertyName( mappedBy ); String propertyRef = value.getReferencedPropertyName(); @@ -68,4 +127,40 @@ ); } } + + //dirty dupe of EntityBinder.bindSecondaryTable + private Join buildJoin(PersistentClass persistentClass, Property otherSideProperty, Join originalJoin) { + Join join = new Join(); + join.setPersistentClass( persistentClass ); + + //no check constraints available on joins + join.setTable( originalJoin.getTable() ); + join.setInverse( true ); + SimpleValue key = new DependantValue( join.getTable(), persistentClass.getIdentifier() ); + join.setKey( key ); + join.setSequentialSelect( false ); + join.setOptional( true ); //perhaps not quite per-spec, but a Good Thing anyway + key.setCascadeDeleteEnabled( false ); + Iterator mappedByColumns = otherSideProperty.getValue().getColumnIterator(); + while ( mappedByColumns.hasNext() ) { + Column column = (Column) mappedByColumns.next(); + Column copy = new Column(); + copy.setLength( column.getLength() ); + copy.setScale( column.getScale() ); + copy.setValue( key ); + copy.setName( column.getQuotedName() ); + copy.setNullable( column.isNullable() ); + copy.setPrecision( column.getPrecision() ); + copy.setUnique( column.isUnique() ); + copy.setSqlType( column.getSqlType() ); + copy.setCheckConstraint( column.getCheckConstraint() ); + copy.setComment( column.getComment() ); + copy.setDefaultValue( column.getDefaultValue() ); + key.addColumn( copy ); + } + join.createPrimaryKey(); + join.createForeignKey(); + persistentClass.addJoin( join ); + return join; + } } Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/annotations/CollectionBinder.java =================================================================== --- trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/annotations/CollectionBinder.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/annotations/CollectionBinder.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -258,9 +258,13 @@ log.debug( "Collection role: " + StringHelper.qualify( propertyHolder.getPath(), propertyName ) ); collection.setRole( StringHelper.qualify( propertyHolder.getPath(), propertyName ) ); - if (property.isAnnotationPresent( org.hibernate.annotations.MapKey.class) && mapKeyPropertyName != null) { - throw new AnnotationException("Cannot mix @javax.persistence.MapKey and @org.hibernate.annotations.MapKey " - + "on the same collection: " + StringHelper.qualify( propertyHolder.getPath(), propertyName ) ); + if ( property.isAnnotationPresent( org.hibernate.annotations.MapKey.class ) && mapKeyPropertyName != null ) { + throw new AnnotationException( + "Cannot mix @javax.persistence.MapKey and @org.hibernate.annotations.MapKey " + + "on the same collection: " + StringHelper.qualify( + propertyHolder.getPath(), propertyName + ) + ); } //set laziness @@ -382,27 +386,29 @@ ManyToMany manyToMany = property.getAnnotation( ManyToMany.class ); CollectionOfElements elements = property.getAnnotation( CollectionOfElements.class ); FetchType fetchType; - if (oneToMany != null) { + if ( oneToMany != null ) { fetchType = oneToMany.fetch(); } - else if (manyToMany != null) { + else if ( manyToMany != null ) { fetchType = manyToMany.fetch(); } - else if (elements != null) { + else if ( elements != null ) { fetchType = elements.fetch(); } else { - throw new AssertionFailure( "Define fetch strategy on a property not annotated with @ManyToOne nor @OneToMany nor @CollectionOfElements"); + throw new AssertionFailure( + "Define fetch strategy on a property not annotated with @ManyToOne nor @OneToMany nor @CollectionOfElements" + ); } - if (lazy != null) { - collection.setLazy( ! (lazy.value() == LazyCollectionOption.FALSE) ); + if ( lazy != null ) { + collection.setLazy( ! ( lazy.value() == LazyCollectionOption.FALSE ) ); collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA ); } else { collection.setLazy( fetchType == FetchType.LAZY ); collection.setExtraLazy( false ); } - if (fetch != null) { + if ( fetch != null ) { if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) { collection.setFetchMode( FetchMode.JOIN ); collection.setLazy( false ); @@ -412,8 +418,8 @@ } else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) { collection.setFetchMode( FetchMode.SELECT ); - collection.setSubselectLoadable(true); - collection.getOwner().setSubselectLoadableCollections(true); + collection.setSubselectLoadable( true ); + collection.getOwner().setSubselectLoadableCollections( true ); } else { throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() ); @@ -473,12 +479,32 @@ XProperty property, boolean unique, TableBinder associationTableBinder, boolean ignoreNotFound, ExtendedMappings mappings ) { - boolean isEntity = persistentClasses.containsKey( collType ); - if ( isEntity + PersistentClass persistentClass = (PersistentClass) persistentClasses.get( collType ); + boolean reversePropertyInJoin = false; + if ( persistentClass != null && StringHelper.isNotEmpty( this.mappedBy ) ) { + try { + reversePropertyInJoin = 0 != persistentClass.getJoinNumber( + persistentClass.getProperty( this.mappedBy ) + ); + } + catch (MappingException e) { + StringBuilder error = new StringBuilder( 80 ); + error.append( "mappedBy reference an unknown property: " ) + .append( collType ).append( "." ).append( this.mappedBy ) + .append( " in " ) + .append( collection.getOwnerEntityName() ) + .append( "." ) + .append( this.mappedBy ); + throw new AnnotationException( error.toString() ); + } + } + if ( persistentClass != null + && ! reversePropertyInJoin && oneToMany && ! this.isExplicitAssociationTable - && ( joinColumns[0].isImplicit() && ! AnnotationBinder.isDefault( this.mappedBy ) - || ! fkJoinColumns[0].isImplicit() ) ) { + && ( joinColumns[0].isImplicit() && ! AnnotationBinder.isDefault( this.mappedBy ) //implicit @JoinColumn + || ! fkJoinColumns[0].isImplicit() ) //this is an explicit @JoinColumn + ) { //this is a Foreign key bindOneToManySecondPass( getCollection(), @@ -780,7 +806,15 @@ .append( joinColumns[0].getPropertyName() ); throw new AnnotationException( error.toString() ); } - Table table = ( (Collection) otherSideProperty.getValue() ).getCollectionTable(); + Table table; + if ( otherSideProperty.getValue() instanceof Collection ) { + //this is a collection on the other side + table = ( (Collection) otherSideProperty.getValue() ).getCollectionTable(); + } + else { + //This is a ToOne with a @JoinTable or a regular property + table = otherSideProperty.getValue().getTable(); + } collValue.setCollectionTable( table ); String entityName = collectionEntity.getEntityName(); for ( Ejb3JoinColumn column : joinColumns ) { @@ -979,7 +1013,24 @@ final String mappedBy = columns[0].getMappedBy(); if ( StringHelper.isNotEmpty( mappedBy ) ) { final Property property = referencedEntity.getProperty( mappedBy ); - Iterator mappedByColumns = ( (Collection) property.getValue() ).getKey().getColumnIterator(); + Iterator mappedByColumns; + if ( property.getValue() instanceof Collection ) { + mappedByColumns = ( (Collection) property.getValue() ).getKey().getColumnIterator(); + } + else { + //find the appropriate reference key, can be in a join + Iterator joinsIt = referencedEntity.getJoinIterator(); + KeyValue key = null; + while ( joinsIt.hasNext() ) { + Join join = (Join) joinsIt.next(); + if ( join.containsProperty( property ) ) { + key = join.getKey(); + break; + } + } + if ( key == null ) key = property.getPersistentClass().getIdentifier(); + mappedByColumns = key.getColumnIterator(); + } while ( mappedByColumns.hasNext() ) { Column column = (Column) mappedByColumns.next(); columns[0].linkValueUsingAColumnCopy( column, value ); Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/annotations/EntityBinder.java =================================================================== --- trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/annotations/EntityBinder.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/annotations/EntityBinder.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -7,8 +7,9 @@ import java.util.List; import java.util.Map; import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.PrimaryKeyJoinColumns; import javax.persistence.SecondaryTable; import javax.persistence.SecondaryTables; import javax.persistence.UniqueConstraint; @@ -340,37 +341,62 @@ while ( joins.hasNext() ) { Object uncastedColumn = joinColumns.next(); + Join join = (Join) joins.next(); + createPrimaryColumnsToSecondaryTable( uncastedColumn, propertyHolder, join ); + } + mappings.addJoins( persistentClass, secondaryTables ); + } - Ejb3JoinColumn[] ejb3JoinColumns; - PrimaryKeyJoinColumn[] columns = null; - if ( uncastedColumn instanceof PrimaryKeyJoinColumn[] ) { - columns = (PrimaryKeyJoinColumn[]) uncastedColumn; - } - if ( columns == null ) { + private void createPrimaryColumnsToSecondaryTable(Object uncastedColumn, PropertyHolder propertyHolder, Join join) { + Ejb3JoinColumn[] ejb3JoinColumns; + PrimaryKeyJoinColumn[] pkColumnsAnn = null; + JoinColumn[] joinColumnsAnn = null; + if ( uncastedColumn instanceof PrimaryKeyJoinColumn[] ) { + pkColumnsAnn = (PrimaryKeyJoinColumn[]) uncastedColumn; + } + if ( uncastedColumn instanceof JoinColumn[] ) { + joinColumnsAnn = (JoinColumn[]) uncastedColumn; + } + if ( pkColumnsAnn == null && joinColumnsAnn == null ) { + ejb3JoinColumns = new Ejb3JoinColumn[1]; + ejb3JoinColumns[0] = Ejb3JoinColumn.buildJoinColumn( + null, + null, + persistentClass.getIdentifier(), + secondaryTables, + propertyHolder, mappings + ); + } + else { + int nbrOfJoinColumns = pkColumnsAnn != null ? pkColumnsAnn.length : joinColumnsAnn.length; + if ( nbrOfJoinColumns == 0 ) { ejb3JoinColumns = new Ejb3JoinColumn[1]; ejb3JoinColumns[0] = Ejb3JoinColumn.buildJoinColumn( - (PrimaryKeyJoinColumn) uncastedColumn, + null, + null, persistentClass.getIdentifier(), secondaryTables, propertyHolder, mappings ); } else { - int nbrOfJoinColumns = columns.length; - if ( nbrOfJoinColumns == 0 ) { - ejb3JoinColumns = new Ejb3JoinColumn[1]; - ejb3JoinColumns[0] = Ejb3JoinColumn.buildJoinColumn( - (PrimaryKeyJoinColumn) null, - persistentClass.getIdentifier(), - secondaryTables, - propertyHolder, mappings - ); + ejb3JoinColumns = new Ejb3JoinColumn[nbrOfJoinColumns]; + if ( pkColumnsAnn != null ) { + for ( int colIndex = 0; colIndex < nbrOfJoinColumns ; colIndex++ ) { + ejb3JoinColumns[colIndex] = Ejb3JoinColumn.buildJoinColumn( + pkColumnsAnn[colIndex], + null, + persistentClass.getIdentifier(), + secondaryTables, + propertyHolder, mappings + ); + } } else { - ejb3JoinColumns = new Ejb3JoinColumn[nbrOfJoinColumns]; for ( int colIndex = 0; colIndex < nbrOfJoinColumns ; colIndex++ ) { ejb3JoinColumns[colIndex] = Ejb3JoinColumn.buildJoinColumn( - columns[colIndex], + null, + joinColumnsAnn[colIndex], persistentClass.getIdentifier(), secondaryTables, propertyHolder, mappings @@ -378,14 +404,12 @@ } } } + } - for ( Ejb3JoinColumn joinColumn : ejb3JoinColumns ) { - joinColumn.forceNotNull(); - } - Join join = (Join) joins.next(); - bindJoinToPersistentClass( join, ejb3JoinColumns, persistentClass, mappings ); + for ( Ejb3JoinColumn joinColumn : ejb3JoinColumns ) { + joinColumn.forceNotNull(); } - mappings.addJoins( persistentClass, secondaryTables ); + bindJoinToPersistentClass( join, ejb3JoinColumns, persistentClass, mappings ); } public static void bindJoinToPersistentClass( @@ -404,51 +428,80 @@ } public void firstLevelSecondaryTablesBinding( - SecondaryTable secTable, SecondaryTables secTables, PrimaryKeyJoinColumn joinCol, - PrimaryKeyJoinColumns joinCols + SecondaryTable secTable, SecondaryTables secTables ) { if ( secTables != null ) { //loop through it for ( SecondaryTable tab : secTables.value() ) { - bindFirstLevelSecondaryTable( tab, null, null ); + addJoin( tab, null, null, false ); } } else { - if ( secTable != null ) bindFirstLevelSecondaryTable( secTable, joinCol, joinCols ); + if ( secTable != null ) addJoin( secTable, null, null, false ); } } - private void bindFirstLevelSecondaryTable( - SecondaryTable tabAnn, PrimaryKeyJoinColumn joinColAnn, PrimaryKeyJoinColumns joinColsAnn + public Join addJoin(JoinTable joinTable, PropertyHolder holder, boolean noDelayInPkColumnCreation) { + Join join = addJoin( null, joinTable, holder, noDelayInPkColumnCreation ); + join.setOptional( true ); + return join; + } + + /** + * A non null propertyHolder means than we process the Pk creation without delay + */ + private Join addJoin( + SecondaryTable secondaryTable, JoinTable joinTable, PropertyHolder propertyHolder, + boolean noDelayInPkColumnCreation ) { - List uniqueConstraints = new ArrayList(); - if ( tabAnn.uniqueConstraints().length != 0 ) { - for ( UniqueConstraint uc : tabAnn.uniqueConstraints() ) { + Join join = new Join(); + join.setPersistentClass( persistentClass ); + String schema; + String catalog; + String table; + String realTable; + UniqueConstraint[] uniqueCosntraintsAnn; + if ( secondaryTable != null ) { + schema = secondaryTable.schema(); + catalog = secondaryTable.catalog(); + table = secondaryTable.name(); + realTable = mappings.getNamingStrategy().tableName( table ); //always an explicit table name + uniqueCosntraintsAnn = secondaryTable.uniqueConstraints(); + } + else if ( joinTable != null ) { + schema = joinTable.schema(); + catalog = joinTable.catalog(); + table = joinTable.name(); + realTable = mappings.getNamingStrategy().tableName( table ); //always an explicit table name + uniqueCosntraintsAnn = joinTable.uniqueConstraints(); + } + else { + throw new AssertionFailure( "Both JoinTable and SecondaryTable are null" ); + } + List uniqueConstraints = new ArrayList( uniqueCosntraintsAnn == null ? 0 : uniqueCosntraintsAnn.length ); + if ( uniqueCosntraintsAnn != null && uniqueCosntraintsAnn.length != 0 ) { + for ( UniqueConstraint uc : uniqueCosntraintsAnn ) { uniqueConstraints.add( uc.columnNames() ); } } - addSecondaryTable( - tabAnn.schema(), - tabAnn.catalog(), - tabAnn.name(), - uniqueConstraints, - joinColAnn, - joinColsAnn == null ? tabAnn.pkJoinColumns() : joinColsAnn.value() + Table tableMapping = TableBinder.fillTable( + schema, + catalog, + realTable, + table, false, uniqueConstraints, null, null, mappings ); - } + //no check constraints available on joins + join.setTable( tableMapping ); - private void addSecondaryTable( - String schema, String catalog, String table, List uniqueConstraints, PrimaryKeyJoinColumn joinColAnn, - PrimaryKeyJoinColumn[] joinColArray - ) { - Join join = buildJoin( schema, catalog, table, uniqueConstraints, persistentClass, mappings ); //somehow keep joins() for later. //Has to do the work later because it needs persistentClass id! - if ( joinColAnn != null ) { - secondaryTableJoins.put( table, joinColAnn ); + Object joinColumns = null; + //get the appropriate pk columns + if ( secondaryTable != null ) { + joinColumns = secondaryTable.pkJoinColumns(); } - else { - secondaryTableJoins.put( table, joinColArray ); + else if ( joinTable != null ) { + joinColumns = joinTable.joinColumns(); } if ( log.isInfoEnabled() ) { log.info( @@ -456,24 +509,14 @@ .getName() ); } - secondaryTables.put( table, join ); - } + if ( noDelayInPkColumnCreation ) { + createPrimaryColumnsToSecondaryTable( joinColumns, propertyHolder, join ); - private static Join buildJoin( - String schema, String catalog, String table, List uniqueConstraints, PersistentClass persistentClass, - ExtendedMappings mappings - ) { - Join join = new Join(); - join.setPersistentClass( persistentClass ); - String realTableName = mappings.getNamingStrategy().tableName( table ); //always an explicit table name - Table tableMapping = TableBinder.fillTable( - schema, - catalog, - realTableName, - table, false, uniqueConstraints, null, null, mappings - ); - //no check constraints available on joins - join.setTable( tableMapping ); + } + else { + secondaryTables.put( table, join ); + secondaryTableJoins.put( table, joinColumns ); + } return join; } Modified: trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/annotations/TableBinder.java =================================================================== --- trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/annotations/TableBinder.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/java/org/hibernate/cfg/annotations/TableBinder.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -128,7 +128,10 @@ mappings.addUniqueConstraints( table, uniqueConstraints ); } if ( constraints != null ) table.addCheckConstraint( constraints ); - mappings.addTableBinding( schema, catalog, logicalName, realTableName, denormalizedSuperTable ); + //logicalName is null if we are in the second pass + if ( logicalName != null ) { + mappings.addTableBinding( schema, catalog, logicalName, realTableName, denormalizedSuperTable ); + } return table; } Modified: trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/join/Death.java =================================================================== --- trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/join/Death.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/join/Death.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -8,17 +8,17 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.SecondaryTable; import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.SecondaryTable; /** * @author Emmanuel Bernard */ @Entity @SecondaryTable( - name = "ExtendedDeath" + name = "ExtendedDeath", + pkJoinColumns = @PrimaryKeyJoinColumn(name = "DEATH_ID") ) -@PrimaryKeyJoinColumn(name = "DEATH_ID") public class Death implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) Modified: trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/join/JoinTest.java =================================================================== --- trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/join/JoinTest.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/join/JoinTest.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -9,6 +9,7 @@ import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.criterion.Expression; +import org.hibernate.mapping.Join; import org.hibernate.test.annotations.TestCase; /** @@ -21,6 +22,11 @@ } public void testDefaultValue() throws Exception { + Join join = (Join) getCfg().getClassMapping( Life.class.getName() ).getJoinClosureIterator().next(); + assertEquals( "ExtendedLife", join.getTable().getName() ); + org.hibernate.mapping.Column owner = new org.hibernate.mapping.Column(); + owner.setName( "LIFE_ID" ); + assertTrue( join.getTable().getPrimaryKey().containsColumn( owner ) ); Session s = openSession(); Transaction tx = s.beginTransaction(); Life life = new Life(); @@ -40,6 +46,11 @@ } public void testCompositePK() throws Exception { + Join join = (Join) getCfg().getClassMapping( Dog.class.getName() ).getJoinClosureIterator().next(); + assertEquals( "DogThoroughbred", join.getTable().getName() ); + org.hibernate.mapping.Column owner = new org.hibernate.mapping.Column(); + owner.setName( "OWNER_NAME" ); + assertTrue( join.getTable().getPrimaryKey().containsColumn( owner ) ); Session s = openSession(); Transaction tx = s.beginTransaction(); Dog dog = new Dog(); Added: trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/manytoone/BiggestForest.java =================================================================== --- trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/manytoone/BiggestForest.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/manytoone/BiggestForest.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -0,0 +1,34 @@ +//$Id: $ +package org.hibernate.test.annotations.manytoone; + +import javax.persistence.Entity; +import javax.persistence.OneToOne; +import javax.persistence.Id; +import javax.persistence.GeneratedValue; + +/** + * @author Emmanuel Bernard + */ +@Entity +public class BiggestForest { + private Integer id; + private ForestType type; + + @Id @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @OneToOne(mappedBy = "biggestRepresentative") + public ForestType getType() { + return type; + } + + public void setType(ForestType type) { + this.type = type; + } +} Added: trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/manytoone/ForestType.java =================================================================== --- trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/manytoone/ForestType.java 2006-04-28 15:44:49 UTC (rev 9832) +++ trunk/HibernateExt/metadata/src/test/org/hibernate/test/annotations/manytoone/ForestType.java 2006-04-30 04:08:48 UTC (rev 9833) @@ -0,0 +1,61 @@ +//$Id: $ +package org.hibernate.test.annotations.manytoone; + +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.JoinTable; +import javax.persistence.JoinColumn; + +/** + * @author Emmanuel Bernard + */ +@Entity +public class ForestType { + private Integer id; + private String name; + private Set<TreeType> trees; + private BiggestForest biggestRepresentative; + + @OneToOne + @JoinTable(name="BiggestRepresentativePerForestType", + joinColumns = @JoinColumn(name="forest_type"), + inverseJoinColumns = @JoinColumn(name="forest") + ) + public BiggestForest getBiggestRepresentative() { + return biggestRepresentative; + } + + public void setBiggestRepresentative(BiggestForest biggestRepresentative) { + this.biggestRepresentative = biggestRepresentative; + } + + @OneToMany(mappedBy="forestType") + public Set<TreeType> getTrees() { + return trees; + } + + public void setTrees(Set<TreeType> trees) { + this.trees = trees; + } + + @Id @GeneratedValue + public Integer ... [truncated message content] |