I've got a problem writing a mapping to a legacy database of mine. I gave two entities; company and currency. A company can have any number of currencies defined for it. So Company has a standard one to many relationship with it's currencies. Each company also has a single reporting currency - a one to one relationship.
My mappings look like this:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="foo.bar.Company" table="MACPFCO0">
<id name="company" type="integer" column="COCMCD">
<meta attribute="field-description">Company Code</meta>
<meta attribute="use-in-tostring">true</meta>
<generator class="assigned"/>
</id>
<property name="name" column="COCMNM" not-null="true" length="30" type="string">
<meta attribute="field-description">Company Name</meta>
</property>
<map name="currencies" lazy="true" cascade="all-delete-orphan" sort="natural">
<key>
<column name="CCCMCC"/>
</key>
<index column="CCISCD" type="string"/>
<one-to-many class="foo.bar.Currency"/>
</map>
<!-- This is wrong -->
<many-to-one name="reportingCurrency" class="foo.bar.Currency" cascade="save-update" insert="false" update="false" unique="true">
<column name="COCMCD"/>
<column name="CORPCY"/>
</many-to-one>
</class>
</hibernate-mapping>
And:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="foo.bar.Currency" table="MACPFCC0">
<meta attribute="class-description">
Details of a currency.
@author Simon Brunning
</meta>
<composite-id name="CurrencyKey" class="foo.bar.CurrencyKey">
<key-many-to-one name="company" class="foo.bar.Company">
<meta attribute="field-description">Company</meta>
<meta attribute="use-in-tostring">true</meta>
<column name="CCCMCC"/>
</key-many-to-one>
<key-property name="iso" type="string" length="3" column="CCISCD">
<meta attribute="field-description">ISO Currency Code</meta>
<meta attribute="use-in-tostring">true</meta>
</key-property>
</composite-id>
<property name="name" column="CCCYNM" length="30" not-null="true" type="string">
<meta attribute="field-description">Currency Name</meta>
</property>
</class>
</hibernate-mapping>
I can query my companies easily enough with this mapping, but if I query the companies, I get an exception:
Code:
Exception in thread "main" java.lang.StackOverflowError
at java.lang.String.toCharArray(Unknown Source)
at com.ibm.as400.access.JDSQLTokenizer.scanForTokens(JDSQLTokenizer.java:207)
at com.ibm.as400.access.JDSQLTokenizer.<init>(JDSQLTokenizer.java:96)
at com.ibm.as400.access.JDSQLTokenizer.<init>(JDSQLTokenizer.java:81)
at com.ibm.as400.access.JDEscapeClause.parse(JDEscapeClause.java:146)
at com.ibm.as400.access.JDSQLStatement.<init>(JDSQLStatement.java:371)
at com.ibm.as400.access.AS400JDBCConnection.prepareStatement(AS400JDBCConnection.java:1850)
at com.ibm.as400.access.AS400JDBCConnection.prepareStatement(AS400JDBCConnection.java:1677)
at net.sf.hibernate.impl.BatcherImpl.getPreparedStatement(BatcherImpl.java:263)
at net.sf.hibernate.impl.BatcherImpl.getPreparedStatement(BatcherImpl.java:236)
at net.sf.hibernate.impl.BatcherImpl.prepareQueryStatement(BatcherImpl.java:67)
at net.sf.hibernate.loader.Loader.prepareQueryStatement(Loader.java:784)
at net.sf.hibernate.loader.Loader.doQuery(Loader.java:269)
at net.sf.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:138)
at net.sf.hibernate.loader.Loader.loadEntity(Loader.java:941)
at net.sf.hibernate.loader.Loader.loadEntity(Loader.java:961)
at net.sf.hibernate.loader.EntityLoader.load(EntityLoader.java:59)
at net.sf.hibernate.loader.EntityLoader.load(EntityLoader.java:51)
at net.sf.hibernate.persister.EntityPersister.load(EntityPersister.java:413)
at net.sf.hibernate.impl.SessionImpl.doLoad(SessionImpl.java:2131)
at net.sf.hibernate.impl.SessionImpl.doLoadByClass(SessionImpl.java:2001)
at net.sf.hibernate.impl.SessionImpl.internalLoad(SessionImpl.java:1963)
at net.sf.hibernate.type.ManyToOneType.resolveIdentifier(ManyToOneType.java:69)
at net.sf.hibernate.type.EntityType.resolveIdentifier(EntityType.java:208)
at net.sf.hibernate.type.ComponentType.resolveIdentifier(ComponentType.java:405)
at net.sf.hibernate.type.ComponentType.nullSafeGet(ComponentType.java:145)
at net.sf.hibernate.loader.Loader.getKeyFromResultSet(Loader.java:431)
at net.sf.hibernate.loader.Loader.getRowFromResultSet(Loader.java:205)
at net.sf.hibernate.loader.Loader.doQuery(Loader.java:285)
at net.sf.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:138)
at net.sf.hibernate.loader.Loader.loadEntity(Loader.java:941)
at net.sf.hibernate.loader.Loader.loadEntity(Loader.java:961)
at net.sf.hibernate.loader.EntityLoader.load(EntityLoader.java:59)
at net.sf.hibernate.loader.EntityLoader.load(EntityLoader.java:51)
at net.sf.hibernate.persister.EntityPersister.load(EntityPersister.java:413)
at net.sf.hibernate.impl.SessionImpl.doLoad(SessionImpl.java:2131)
at net.sf.hibernate.impl.SessionImpl.doLoadByClass(SessionImpl.java:2001)
at net.sf.hibernate.impl.SessionImpl.internalLoad(SessionImpl.java:1963)
at net.sf.hibernate.type.ManyToOneType.resolveIdentifier(ManyToOneType.java:69)
at net.sf.hibernate.type.EntityType.resolveIdentifier(EntityType.java:208)
at net.sf.hibernate.type.ComponentType.resolveIdentifier(ComponentType.java:405)
at net.sf.hibernate.type.ComponentType.nullSafeGet(ComponentType.java:145)
at net.sf.hibernate.loader.Loader.getKeyFromResultSet(Loader.java:431)
at net.sf.hibernate.loader.Loader.getRowFromResultSet(Loader.java:205)
...
And so on, and on, and on... (You'll notice there's a loop in there.)
For test purposes, I let Hibernate generate a schema for this mapping. (I've occasionally found this useful in the past - it allows you to see what Hibernate is expecting the database to look like. If the legacy database to which you are mapping looks significantly different to what Hibernate comes up with, then you have a problem.) The generated database that Hibernate comes up with in this case is utterly useless; there are constraints in both directions. You can't insert a company, 'cos there is no currency to use as its reporting currency, and you can't insert a currency without a company. The existing legacy schema doesn't have the constraints defined - the constraint is enforced in code - so it all works fine there.
I can see that this relationship is problematical in relational terms, but it makes perfect sense in business terms. Is it possible to write a Hibernate mapping to represent this relationship?