I have a problem with saving one-to-many relationships. I have an entity called fund which contains a list of categories. A category has a composite key pointing to a fund and a category-code. The cascading between the fund and categories is set to save-update.
I create a brand new list of categories and assign them to a fund. For each category I manually create the composite key classes. If I make the fund persistent using save or update the sql code generated by hibernate seems to be updating the category table. No inserts occur in the category table. If I dont manually create the composite key class for each category, I get the following hibernate exception
org.springframework.orm.hibernate.HibernateSystemException: ids for this class must be manually assigned before calling save():
It seems to me that hibernate is expecting me to create a composite key and assign it to a category. However, when I presist the fund, hibernate assumes that the category already exists because the primary key for a category is already created
Is there a way to save the categories by saving the fund and let hibernate take care of the creation of the composite-key
Hibernate version:
hibernate 2.1.3
Mapping documents:
Fund.hbm.xml
<?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>
<!--
Created by the Middlegen Hibernate plugin 2.1
http://boss.bekk.no/boss/middlegen/
http://www.hibernate.org/
-->
<class
name="com.citigroup.cai.mf.vo.Fund"
table="MF_FUND_MASTER"
>
<id
name="fundId"
type="java.lang.Long"
column="FUND_ID"
>
<generator class="sequence">
<param name="sequence">MF_FUND_SEQ</param>
</generator>
</id>
<property
name="shortName"
type="java.lang.String"
column="SHORT_NAME"
not-null="true"
length="20"
>
<meta attribute="use-in-tostring">true</meta>
<meta attribute="use-in-equals">true</meta>
</property>
<property
name="longName"
type="java.lang.String"
column="LONG_NAME"
length="60"
>
<meta attribute="use-in-tostring">true</meta>
</property>
<property
name="inceptionDate"
type="java.sql.Timestamp"
column="INCEPTION_DATE"
length="7"
>
<meta attribute="use-in-tostring">true</meta>
</property>
<property
name="taxid"
type="java.lang.String"
column="TAXID"
length="15"
/>
<property
name="comments"
type="java.lang.String"
column="COMMENTS"
length="60"
/>
<property
name="userid"
type="java.lang.String"
column="USERID"
not-null="true"
length="10"
>
<meta attribute="use-in-tostring">true</meta>
</property>
<property
name="timestamp"
type="java.sql.Timestamp"
column="TIMESTAMP"
not-null="true"
length="10"
>
<meta attribute="use-in-tostring">true</meta>
</property>
<!-- Associations -->
<!-- bi-directional one-to-many association to FundCta -->
<set
name="fundCtas"
lazy="true"
inverse="true"
cascade="none"
>
<key>
<column name="FUND_ID" />
</key>
<one-to-many
class="com.citigroup.cai.mf.vo.FundCta"
/>
</set>
<!-- uni-directional one-to-many association to Identifier -->
<set
name="identifiers"
lazy="true"
cascade="none"
>
<key>
<column name="FUND_ID" />
</key>
<one-to-many
class="com.citigroup.cai.mf.vo.Identifier"
/>
</set>
<!-- uni-directional one-to-many association to Category -->
<set
name="categories"
outer-join="true"
cascade="save-update"
lazy="false"
>
<key>
<column name="FUND_ID" />
</key>
<one-to-many
class="com.citigroup.cai.mf.vo.Category"
/>
</set>
</class>
</hibernate-mapping>
------------------------------------------------------
category.hbm.xml
<?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>
<!--
Created by the Middlegen Hibernate plugin 2.1
http://boss.bekk.no/boss/middlegen/
http://www.hibernate.org/
-->
<class
name="com.citigroup.cai.mf.vo.Category"
table="MF_FUND_CATEGORY"
>
<composite-id name="categoryPK" class="com.citigroup.cai.mf.vo.pk.CategoryPK">
<key-property
name="fundId"
column="FUND_ID"
type="java.lang.Long"
length="22"
/>
<key-property
name="categoryId"
column="CATEGORY_ID"
type="java.lang.Long"
length="22"
/>
</composite-id>
<property
name="categoryIndicator"
type="java.lang.String"
column="CATEGORY_INDICATOR"
not-null="true"
length="1"
>
<meta attribute="use-in-tostring">true</meta>
</property>
<property
name="userid"
type="java.lang.String"
column="USERID"
not-null="true"
length="10"
>
<meta attribute="use-in-tostring">true</meta>
</property>
<property
name="timestamp"
type="java.sql.Timestamp"
column="TIMESTAMP"
length="7"
>
<meta attribute="use-in-tostring">true</meta>
</property>
<!-- Associations -->
<!-- derived association(s) for compound key -->
<!-- uni-directional many-to-one association to CategoryCode -->
<many-to-one
name="categoryCode"
class="com.citigroup.cai.mf.vo.CategoryCode"
update="false"
insert="false"
>
<column name="CATEGORY_ID" />
</many-to-one>
<!-- end of derived association(s) -->
</class>
</hibernate-mapping>
---------------------------------------------------------
categoryCode.hbm.xml
<?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>
<!--
Created by the Middlegen Hibernate plugin 2.1
http://boss.bekk.no/boss/middlegen/
http://www.hibernate.org/
-->
<class
name="com.citigroup.cai.mf.vo.CategoryCode"
table="MF_FUND_CATEGORY_CODES"
mutable="false"
>
<id
name="categoryId"
type="java.lang.Long"
column="CATEGORY_ID"
>
<generator class="sequence">
<param name="sequence">MF_FUND_CATEGORY_SEQ</param>
</generator>
</id>
<property
name="description"
type="java.lang.String"
column="DESCRIPTION"
length="50"
>
<meta attribute="use-in-tostring">true</meta>
<meta attribute="use-in-equals">true</meta>
</property>
<property
name="userid"
type="java.lang.String"
column="USERID"
length="10"
>
<meta attribute="use-in-tostring">true</meta>
</property>
<property
name="timestamp"
type="java.sql.Timestamp"
column="TIMESTAMP"
not-null="true"
length="7"
>
<meta attribute="use-in-tostring">true</meta>
</property>
<!-- Associations -->
</class>
</hibernate-mapping>
Code between sessionFactory.openSession() and session.close():
Fund f = new Fund();
f.setShortName("CitiDev 1");
f.setLongName("Citigroup Dev");
f.setUserid("unitTest");
f.setTimestamp(new Date());
dao.create(f);
Category c = new Category();
// when I commented out the next line I got the exception
CategoryPK pk = new CategoryPK(f.getFundId(), new Long(3));
c.setCategoryIndicator("Y");
c.setCategoryPK(pk);
c.setUserid("unitTest");
c.setTimestamp(new Date());
// This is another version of the code that I have tried and got the same exception
//Category c = new Category();
//CategoryCode code = new CategoryCode();
//code.setCategoryId(new Long(2));
//c.setCategoryCode(code);
//c.setCategoryIndicator("Y");
//c.setUserid("unitTest");
//c.setTimestamp(new Date());
//f.addCategory(c);
//dao.create(f);
f.addCategory(c);
dao.update(f);
Full stack trace of any exception that occurs:
org.springframework.orm.hibernate.HibernateSystemException: ids for this class must be manually assigned before calling save(): com.citigroup.cai.mf.vo.Category; nested exception is net.sf.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): com.citigroup.cai.mf.vo.Category
net.sf.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): com.citigroup.cai.mf.vo.Category
at net.sf.hibernate.id.Assigned.generate(Assigned.java:26)
at net.sf.hibernate.impl.SessionImpl.saveWithGeneratedIdentifier(SessionImpl.java:765)
at net.sf.hibernate.impl.SessionImpl.save(SessionImpl.java:738)
at net.sf.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:1388)
at net.sf.hibernate.engine.Cascades$4.cascade(Cascades.java:114)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:436)
at net.sf.hibernate.engine.Cascades.cascadeCollection(Cascades.java:526)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:452)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:503)
at net.sf.hibernate.impl.SessionImpl.doSave(SessionImpl.java:952)
at net.sf.hibernate.impl.SessionImpl.doSave(SessionImpl.java:857)
at net.sf.hibernate.impl.SessionImpl.saveWithGeneratedIdentifier(SessionImpl.java:779)
at net.sf.hibernate.impl.SessionImpl.save(SessionImpl.java:738)
at net.sf.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:1388)
at org.springframework.orm.hibernate.HibernateTemplate$14.doInHibernate(HibernateTemplate.java:390)
at org.springframework.orm.hibernate.HibernateTemplate.execute(HibernateTemplate.java:228)
at org.springframework.orm.hibernate.HibernateTemplate.saveOrUpdate(HibernateTemplate.java:387)
at com.citigroup.cai.mf.dao.implement.FundDAOImp.create(FundDAOImp.java:65)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:295)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:163)
at $Proxy0.create(Unknown Source)
at com.citigroup.cai.mf.dao.FundDAOTest.testCreateFundWithCategories(FundDAOTest.java:129)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at junit.framework.TestCase.runTest(TestCase.java:154)
at junit.framework.TestCase.runBare(TestCase.java:127)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:118)
at junit.framework.TestSuite.runTest(TestSuite.java:208)
at junit.framework.TestSuite.run(TestSuite.java:203)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:421)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:305)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:186)
Name and version of the database you are using:
Oracle 9
The generated SQL (show_sql=true):
Hibernate: select MF_FUND_SEQ.nextval from dual
Hibernate: insert into MF_FUND_MASTER (SHORT_NAME, LONG_NAME, INCEPTION_DATE, TAXID, COMMENTS, USERID, TIMESTAMP, FUND_ID) values (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: update MF_FUND_CATEGORY set CATEGORY_INDICATOR=?, USERID=?, TIMESTAMP=? where FUND_ID=? and CATEGORY_ID=?
Hibernate: update MF_FUND_CATEGORY set FUND_ID=? where FUND_ID=? and CATEGORY_ID=?
Debug level Hibernate log excerpt: