Thanks Litty,
Unfortunately I'm stuck with a legacy database used by a multitude of processes, so I need to adapt my model to it and I don’t have the flexibility to change the database structure to the approach you propose.
What it’s more, my real case is more complex than what I explained in my first post. My B table is not about country descriptions but a general internationalised description table for many things, being country descriptions just one of them. This gets discriminated by an additional B.TYPE column. A lot of tables use B to get different kind of descriptions, in fact we have thousands of types. If I went for a join table we’d have to create one per table accessing B and we have dozens of those.
But you are correct, the root of the problem is that I'm trying to map a many-to-many relationship as if it was a one-to-many.
After some thinking I've got a working solution that doesn’t require creating new join tables or modify the database, as follows:
a) I’ve created a new entity called BNormaliser, this entity maps to the same table as B and has the same PK. This new entity contains a Map of B and can be used by any table that requires a Map of B
b) I’ve changed the mapping to be A -> BNormaliser -> B
See the new mapping below
Code:
<class name="A" table="A">
....
<many-to-one name="countryDescriptions" class="BNormaliser" not-found="ignore">
<formula>'EN'</formula>
<formula>'country'</formula>
<formula>ISO_CODE</formula>
</many-to-one>
....
</class>
<class name="BNormaliser" table="B">
<composite-id name="id" class="BId">
<key-property name="language" column="LANG"/>
<key-property name="type" column="TYPE"/>
<key-property name="code" column="CODE" />
</composite-id></class>
<component name="foreignKey" class="BId" access="field">
<property name="type" column="TYPE" insert="false" update="false" />
<property name="code" column="CODE" insert="false" update="false" />
</component>
<map name="descriptions">
<key property-ref="foreignKey">
<column name="TYPE" />
<column name="CODE" />
</key>
<map-key formula="LANG" type="string" />
<one-to-many class="B" />
</map>
</class>
<class name="B" table="B">
<composite-id name="id" class="BId">
<key-property name="language" column="LANG"/>
<key-property name="type" column="TYPE"/>
<key-property name="code" column="CODE" />
</composite-id></class>
<property name="description" column="DESCR" not-null="true" />
</class>
Basically I select a record from the table B (into BNormaliser) for a language I know I always have a description (in my case, EN), just so I can have my A->BNormaliser working properly as a many-to-one relationship. The fact that BNormaliser will be linked to the EN language is irrelevant. Then I get a map from BNormaliser to B that includes all languages.
As you can see, there is a bit of extra mapping in BNormaliser (the component "foreignKey"), which I had to put in order to use composite primary keys, which unfortunately are quite common in legacy databases.
I’d normally use <formula> for this, but <key> doesn’t allow that and if one uses <key> without property-ref, it requires matching PK columns between both tables, this would have returned a single record because of the EN language in BNormaliser. The fact that you can use property-ref with a component, in order to map to a composite key, has been a major discovery I’ve just come across! But it seems to work fine and opens the door to mappings that looked impossible to me before. I actually used to work with Hibernate filters to achieve this!
Finally, I’ve hidden all this new complexity from my model, by keeping the same interface in A (and the other entities that use B) by doing
Code:
return null == countryDescriptions ? null : countryDescriptions.getDescriptions();
where I used to just return the map before.
Hopefully this solution will help other people with similar problems. It’d be great if Hibernate allowed this kind of relationship straight away, without so much complication. If someone has a neater solution that doesn’t require using a work around like “BNormaliser”, please let me know.
Juan