Hello,
Let's take the following example: An Order class has a set of OrderItems, and each OrderItem also references (many-to-one) a Product. So the schema will contain (say) TBL_ORDERITEM with OrderId and ProductId columns, each of which are foreign keys. The collection mapping in Order.hbm.xml looks like:
Code:
<set name="orderItems" table="TBL_ORDERITEM">
<key column="orderId"/>
<composite-element class="OrderItem">
<many-to-one name="product" column="productId"/>
...other properties...
</composite-element>
</set>
So far, so good. My problem is that I'd like TBL_ORDERITEM to have an primary key / unique index on (OrderId, ProductId). This is a pretty common use case: there are multiple OrderItems per Order, but for a given Order you can't have two Items with the same Product.
<set> only generates a primary key for the collection table if all the columns are not-null, and that primary key contains all properties of the collection type. But this schema would allow two rows in TBL_ORDERITEM to have the same OrderId and ProductId, as long as they differed in some other column. Also, I have columns in OrderItem that are logically nullable.
OK, so I shouldn't use <set>. How about <map>? I tried the following:
Code:
<map name="orderItems" table="TBL_ORDERITEM">
<key column="orderId"/>
<map-key-many-to-many column="productId" class="Product"/>
<composite-element class="OrderItem">
...other properties...
</composite-element>
</map>
That does indeed produce a schema with the desired primary key of (OrderId, ProjectId). And being able to look up an OrderItem in the map by Product is useful. The only problem is that the OrderItem class no longer contains a reference to the Product: the <many-to-one> inside the OrderItem composite-element (as I had before in the Set case) can't co-exist with the <map-key-many-to-many>, since they both refer to the same productId column.
I thought I had a clever solution with the following:
Code:
<map name="orderItems" table="TBL_ORDERITEM">
<key column="orderId"/>
<map-key-many-to-many formula="productId" type="long"/>
<composite-element class="OrderItem">
<many-to-one name="product" column="productId"/>
...other properties...
</composite-element>
</map>
...but this generates a primary key containing
all the columns in the OrderItem composite element, not just the key and map-key. In other words, it's behaving like a Set.
Questions:
1) Is there a way to have a Map collection contain a composite-element referencing another entity
and use that property as the key of the map?
2) Is the Hibernate behavior in the final case above correct? The docs lead me to expect that the primary key should contain only the key and map-key, not any other columns, and I don't understand why using the formula attribute should change that.
3) Is there any other way to set up the mappings and get the desired result? I'd rather not turn the OrderItem into an entity class (with a composite-index primary key of OrderId and ProductId), since the semantics of value collections are different than that of associated entities. idbag is another possibility, but again it has somewhat different semantics, and has its own synthetic key which is irrelevant for these purposes.
Thanks in advance for reading this far. (-:
Chris