Hi,
I am facing a similar challenge with a legacy database (that of course cannot be altered). I have 2 tables that have 2 natural composite keys each and a join table. The 2 tables share one of the composite keys just like the previous posting.
However, instead of a one-to-many relation, we were trying to implement a many-to-many with a join table and were getting the "org.hibernate.MappingException: Repeated column in mapping for collection: ..." error.
After researching the Forum, we tried the workaround in the previous posting (replacing the <column> with the <formula> tag) on the repeated key and this resulted in a NullPointerException.
Oddly enough it worked when we replaced <column> with <formula> on BOTH keys, but this only worked on read operations. When inserting, it tries to put NULL into the other key column.
I have all the details to recreate this. I tried to boil this down to a simple runnable example. Here are the table defs:
Code:
CREATE TABLE `table1` (
`id_a` int(11) NOT NULL default '0',
`id_b` int(11) NOT NULL default '0',
`data` char(20) default NULL,
PRIMARY KEY (`id_a`,`id_b`)
)
CREATE TABLE `table2` (
`id_b` int(11) NOT NULL default '0',
`id_c` int(11) NOT NULL default '0',
`data` char(20) default NULL,
PRIMARY KEY (`id_b`,`id_c`)
)
CREATE TABLE `join12` (
`id_a` int(11) NOT NULL default '0',
`id_b` int(11) NOT NULL default '0',
`id_c` int(11) NOT NULL default '0',
PRIMARY KEY (`id_a`,`id_b`,`id_c`)
)
Here are the mappings for Table1 and Table2
Code:
<hibernate-mapping package="domain" >
<class name="Table1Class" table="TABLE1" >
<composite-id>
<key-property name="id_a" column="ID_A" type="integer" />
<key-property name="id_b" column="ID_B" type="integer" />
</composite-id>
<property name="data" column="DATA" type="string" />
<bag name="table2Items" table="join12">
<key>
<column name="id_a" />
<column name="id_b"/>
</key>
<many-to-many class="Table2Class">
<formula>id_b</formula>
<column name="id_c"/>
</many-to-many>
</bag>
</class>
</hibernate-mapping>
<hibernate-mapping package="domain" >
<class name="Table2Class" table="TABLE2">
<composite-id>
<key-property name="id_b" column="ID_B" type="integer" />
<key-property name="id_c" column="ID_C" type="integer" />
</composite-id>
<property name="data" column="DATA" type="string" />
</class>
</hibernate-mapping>
To save space the classes are not listed. We did make sure they were Serializable and that both Equals and hashCode were implemented correctly.
When running this you get the following stack trace: (please note that the stack trace is from our production code rather than the example above. Everything above buildSessionFactory is the same.)
Code:
java.lang.NullPointerException
at org.hibernate.util.StringHelper.replace(StringHelper.java:78)
at org.hibernate.util.StringHelper.replace(StringHelper.java:66)
at org.hibernate.util.StringHelper.replace(StringHelper.java:72)
at org.hibernate.persister.collection.AbstractCollectionPersister.getElementColumnNames(AbstractCollectionPersister.java:796)
at org.hibernate.loader.OuterJoinLoader.walkCollectionTree(OuterJoinLoader.java:250)
at org.hibernate.loader.OuterJoinLoader.walkCollectionTree(OuterJoinLoader.java:217)
at org.hibernate.loader.collection.CollectionLoader.<init>(CollectionLoader.java:75)
at org.hibernate.loader.collection.CollectionLoader.<init>(CollectionLoader.java:58)
at org.hibernate.loader.collection.CollectionLoader.<init>(CollectionLoader.java:49)
at org.hibernate.loader.collection.BatchingCollectionInitializer.createBatchingCollectionInitializer(BatchingCollectionInitializer.java:92)
at org.hibernate.persister.collection.BasicCollectionPersister.createCollectionInitializer(BasicCollectionPersister.java:272)
at org.hibernate.persister.collection.AbstractCollectionPersister.postInstantiate(AbstractCollectionPersister.java:472)
at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:248)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1005)
at com.nppc.mes.hibernate.HibernateConnector.<init>(HibernateConnector.java:127)
at com.nppc.mes.hibernate.HibernateConnector.createHibernateConnector(HibernateConnector.java:91)
at com.nppc.mes.hibernate.HibernateConnector.createDefault(HibernateConnector.java:87)
at com.nppc.mes.batching.carma.Carma.initializeApplication(Carma.java:242)
at com.nppc.mes.AbstractApplicationStartup.start(AbstractApplicationStartup.java:454)
at com.nppc.mes.batching.carma.Carma.main(Carma.java:145)
I have debugged this and found that, if it is a bug, that it involves the elementFormulaTemplates array member of the AbstractCollectionPersister class. The array is initialized in the constructor from a column iterator. I noticed here that all non-formulas are skipped and the value at the corresponding index is left as null in the elementFormulaTemplates array. Later from the getElementColumnNames() method (same class), it calls down into several StringHelper.replace() methods that assume that the template String argument will never be null when it calls template.indexOf().
I realize that 99% of these are actually user error and not bugs in the system. If I'm making a conceptual mistake, could you please steer me in the right direction? If this is a bug that has been addressed then my apologies for missing it in my JIRA search.
We are reshuffling some of our stories in our project to buy us some time in hopes of being able to find a workaround for this. Please let me know if you need any more information. Just wanted to say that Hibernate is a most excellent tool!
Thanks very much for your help in this!
Jerry