-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 
Author Message
 Post subject: Mapping question for "missing foreign key insert column
PostPosted: Wed Jul 23, 2008 10:04 pm 
Newbie

Joined: Mon Oct 02, 2006 10:23 am
Posts: 3
Location: Wisconsin, USA
This is a common condition which I think I do understand. What I don't understand is the mapping syntax I need to address it. I have a very simple demo with two tables. One contains test items with a compound key and a data field. The other is an associative table with a compound key and foreign keys for two test item records. Note that KEY_FIELD_1 will always match for all records in the same series and need not be cuplicated in the associative table. Here is the DDL for the two tables:

Code:
CREATE TABLE devel.TESTITEM
(
    KEY_FIELD_1  CHAR(20)  NOT NULL,
    KEY_FIELD_2  CHAR(20)  NOT NULL,
    DATA_FIELD   CHAR(20)  NOT NULL,
    CONSTRAINT TESTITEM_PK PRIMARY KEY
      (KEY_FIELD_1, KEY_FIELD_2)
);
         
CREATE TABLE devel.TESTASSN
(
    KEY_FIELD_1   CHAR(20)  NOT NULL,
    KEY_FIELD_3   CHAR(20)  NOT NULL,
    KEY_FIELD_2A  CHAR(20)  NOT NULL,
    KEY_FIELD_2b  CHAR(20)  NOT NULL,
    CONSTRAINT TESTASSN_PK PRIMARY KEY
      (KEY_FIELD_1, KEY_FIELD_3),
    CONSTRAINT TESTASSN_FK0 FOREIGN KEY
      (KEY_FIELD_1, KEY_FIELD_2A) REFERENCES devel.TESTITEM
      (KEY_FIELD_1, KEY_FIELD_2)  MATCH FULL,
    CONSTRAINT TESTITEM_FK1 FOREIGN KEY
      (KEY_FIELD_1, KEY_FIELD_2B) REFERENCES devel.TESTITEM
      (KEY_FIELD_1, KEY_FIELD_2 ) MATCH FULL
);


The mapping files, generated by Eclipse reverse engineering, are shown below, followed by the requested bits of information for a posting. I'll continue with what I've tried at the end of the post.

Hibernate version:
Hibernate-3.2.1

Mapping documents:

Test Item:
Code:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
    Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
    <class name="com.xxxxx.engine.hibernate.Testitem" table="testitem" schema="devel">
        <composite-id name="id" class="com.xxxxx.engine.hibernate.TestitemId">
            <key-property name="keyField1" type="java.lang.String">
                <column name="key_field_1" length="20" />
            </key-property>
            <key-property name="keyField2" type="java.lang.String">
                <column name="key_field_2" length="20" />
            </key-property>
        </composite-id>
        <property name="dataField" type="java.lang.String">
            <column name="data_field" length="20" not-null="true" />
        </property>
        <set name="testassnsForTestassnFk0" inverse="true">
            <key>
                <column name="key_field_1" length="20" not-null="true" />
                <column name="key_field_2a" length="20" not-null="true" />
            </key>
            <one-to-many class="com.xxxxx.engine.hibernate.Testassn" />
        </set>
        <set name="testassnsForTestitemFk1" inverse="true">
            <key>
                <column name="key_field_1" length="20" not-null="true" />
                <column name="key_field_2b" length="20" not-null="true" />
            </key>
            <one-to-many class="com.xxxxx.engine.hibernate.Testassn" />
        </set>
    </class>
</hibernate-mapping>


Test Association (reformatted some to shorten the line lengths for readability)
Code:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
    Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
    <class name="com.xxxx.engine.hibernate.Testassn" table="testassn" schema="devel">
        <composite-id name="id" class="com.xxxx.engine.hibernate.TestassnId">
            <key-property name="keyField1" type="java.lang.String">
                <column name="key_field_1" length="20" />
            </key-property>
            <key-property name="keyField3" type="java.lang.String">
                <column name="key_field_3" length="20" />
            </key-property>
        </composite-id>
       
        <many-to-one name="testitemByTestassnFk0"
                  class="com.xxxx.engine.hibernate.Testitem"
                  update="false" insert="false" fetch="select">
            <column name="key_field_1" length="20" not-null="true" />
            <column name="key_field_2a" length="20" not-null="true" />
        </many-to-one>

       
        <many-to-one name="testitemByTestitemFk1"
                  class="com.xxxx.engine.hibernate.Testitem"
                  update="false" insert="false" fetch="select">
            <column name="key_field_1" length="20" not-null="true" />
            <column name="key_field_2b" length="20" not-null="true" />
        </many-to-one>
    </class>
</hibernate-mapping>



Code between sessionFactory.openSession() and session.close():
Here is the code that creates and inserts two item records, and then creates and attempts to insert an association record linking the two of them.
Code:
Session session = sessionFactory.getCurrentSession();
transaction = session.beginTransaction();
           
// Clear any existing records
session.createQuery("delete from Testitem").executeUpdate();
session.createQuery("delete from Testassn").executeUpdate();
           
// Create the first item record.
TestitemId testitemId1 = new TestitemId("Series 1", "Item 1");
Testitem   testitem1   = new Testitem  (testitemId1, "Data Item 1");
session.save(testitem1);
           
// Create the second item record.
TestitemId testitemId2 = new TestitemId("Series 1", "Item 2");
Testitem   testitem2   = new Testitem  (testitemId2, "Data Item 2");
session.save(testitem2);

// Create an association record.
TestassnId testassnId = new TestassnId ("Series 1", "Association 1");
Testassn   testassn   = new Testassn(testassnId, testitem1, testitem2);
session.save(testassn);

transaction.commit();
session.close();


Full stack trace of any exception that occurs:
Because the foreign keys are marked as inssert-"false", their columns are left off the associative table's insert statement, and the insert fails with null values for non-nulable columns.
Code:
org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
   at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:69)
   at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
   at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:202)
   at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:230)
   at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:140)
   at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:296)
   at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
   at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1009)
   at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:356)
   at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
   at com.xxxx.engine.HibernateTest.insertRows(HibernateTest.java:68)
   at com.xxxx.engine.HibernateTestTest.testHibernateTest(HibernateTestTest.java:24)
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
   at java.lang.reflect.Method.invoke(Unknown Source)
   at junit.framework.TestCase.runTest(TestCase.java:168)
   at junit.framework.TestCase.runBare(TestCase.java:134)
   at junit.framework.TestResult$1.protect(TestResult.java:110)
   at junit.framework.TestResult.runProtected(TestResult.java:128)
   at junit.framework.TestResult.run(TestResult.java:113)
   at junit.framework.TestCase.run(TestCase.java:124)
   at junit.framework.TestSuite.runTest(TestSuite.java:232)
   at junit.framework.TestSuite.run(TestSuite.java:227)
   at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:81)
   at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
   at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: java.sql.BatchUpdateException: Batch entry 0 insert into devel.testassn (key_field_1, key_field_3) values (Series 1, Association 1) was aborted.  Call getNextException to see the cause.
   at org.postgresql.jdbc2.AbstractJdbc2Statement$BatchResultHandler.handleError(AbstractJdbc2Statement.java:2534)
   at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1328)
   at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:352)
   at org.postgresql.jdbc2.AbstractJdbc2Statement.executeBatch(AbstractJdbc2Statement.java:2596)
   at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeBatch(NewProxyPreparedStatement.java:1723)
   at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:58)
   at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:195)
   ... 28 more


Name and version of the database you are using:
PostgreSQL 8.1

The generated SQL (show_sql=true):
Code:
Hibernate: delete from devel.testitem
Hibernate: delete from devel.testassn
Hibernate: insert into devel.testitem (data_field, key_field_1, key_field_2) values (?, ?, ?)
Hibernate: insert into devel.testitem (data_field, key_field_1, key_field_2) values (?, ?, ?)
Hibernate: insert into devel.testassn (key_field_1, key_field_3) values (?, ?)


Debug level Hibernate log excerpt:
Sorry, not at debug level, but we know what the problem is.
Code:
WARN   logExceptions, SQL Error: 0, SQLState: null
ERROR  logExceptions, Batch entry 0 insert into devel.testassn (key_field_1, key_field_3) values (Series 1, Association 1) was aborted.  Call getNextException to see the cause.
WARN   logExceptions, SQL Error: 0, SQLState: 23502
ERROR  logExceptions, ERROR: null value in column "key_field_2a" violates not-null constraint
ERROR  performExecutions, Could not synchronize database state with session


Given all this and a few days searching the Hibernate doc, the forums, and Google, it seems that the solution is to manually set a property for insert="true" and update="true" for the missing column. Unfortunately, all the examples I found were for single field keys that used something like this:

Code:
        <many-to-one name="testitemByTestassnFk0"
                  class="com.xxxx.engine.hibernate.Testitem"
                  update="false" insert="false" fetch="select">
            <column name="key_field_1" length="20" not-null="true" />
            <column name="key_field_2a" length="20" not-null="true" />
        </many-to-one>
        <property name="keyField2" type="java.lang.String" insert="true" update="true" />


This does not work because there are no accessors in "testitemByTestassnFk0" for values inside the key object (com.xxxx.engine.hibernate.TestitemId). I may have gotten closer with something like this:

Code:
        <many-to-one name="testitemByTestassnFk0"
                  class="com.xxxx.engine.hibernate.Testitem"
                  update="false" insert="false" fetch="select">
            <column name="key_field_1" length="20" not-null="true" />
            <column name="key_field_2a" length="20" not-null="true" />
        </many-to-one>
            <component name="id" class="com.xxx.engine.hibernate.TestitemId">
                <property name="keyField2" type="java.lang.String" insert="true" update="true" />
            </component>


however, there is no accessor for "id" in the overall class of Testassn. If I try to wrap another component tag around this, Hibernate compalins that I am defining duplicate property mapping for testitemByTestassnFk0, which is in fact exactly what I am trying to do!

So, given all this background, how do I define a "property" for a field inside an ID object that is a foreign key on a related table? Alternatively, is there some other approach that I have missed completely. This is such a simple and, I would think, common condition that I am amazed I haven't seen anything that addresses this specific structure.

I'd welcome any suggestions or pointers to appropriate doc or other materials that I have obviously missed about this.

Thanks for any suggestions that would get this going again.

JFE.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.