-->
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.  [ 2 posts ] 
Author Message
 Post subject: Composite id in hibernate+postgres breaks due to returned co
PostPosted: Sun Feb 21, 2010 11:05 pm 
Newbie

Joined: Wed May 18, 2005 3:02 pm
Posts: 18
I have a parent object with composite-id (legacy db - can't modify this). I have a child object that is a bidirectional one-to-many (parent-to-children) relationship. The mapping is correct, as I can load an instance of either entity and correctly navigate across the relationship. My problem comes when I store the parent and it cascades to the child. The Postgres dialect is issuing a query of the form:

"insert into tablename (column1, column2, column3, column4) values (value1, value2, value3, value4) returning *"

Which is a nice postgres shortcut for returning all values of the row just inserted. However, columns come back in arbitrary order set by the db, though it is a standard resultset with all of the column metadata included. However, postgres appears to be assuming that the columns coming back are in some arbitrary order.

The table in question has a btime and mtime field which are updated via a trigger on insert. Both are timestamp columns. Those are the first two columns that come back. I spent a while attempting to debug hibernate, but it is a slow process. I believe that what is happening is that the first timestamp column is assumed to be the generated id column, and it is failing when it attempts to convert the timestamp string into a Long. In fact, the generated id shows up as the 4th column.

Code:
Caused by: org.postgresql.util.PSQLException: Bad value for type long : 2010-02-21 18:11:19.774362
at org.postgresql.jdbc2.AbstractJdbc2ResultSet.toLong(AbstractJdbc2ResultSet.java:2796)
at org.postgresql.jdbc2.AbstractJdbc2ResultSet.getLong(AbstractJdbc2ResultSet.java:2019)
at org.hibernate.id.IdentifierGeneratorFactory.get(IdentifierGeneratorFactory.java:104)
at org.hibernate.id.IdentifierGeneratorFactory.getGeneratedIdentity(IdentifierGeneratorFactory.java:92)
at org.hibernate.id.IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(IdentityGenerator.java:98)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:57)

I believe that this is somehow related to using a composite key as I've used a nearly identical setup for btime and mtime columns in another application, which has a hibernate-specific schema that uses generated long ids everywhere. Because btime and mtime come from a parent table that all other tables in the db inherit, there is no way to change the order of the columns.

The net result is that both the parent insert and the cascaded child insert do succeed, but then hibernate throws an exception after it fails to load the generated field(s) for the child entity. This feels very much like a bug in hibernate, and it is one which has stopped me cold. I'm hoping someone knows a workaround or a bug fix.

I'm using hibernate-3.3.1-GA as distributed by springsource in the package of dependencies with the latest spring 3.0.1 release

Code:
CREATE TABLE parent_stat (
      btime timestamp DEFAULT NOW() NOT NULL,  -- Birth Time or Creation Time
      mtime timestamp DEFAULT NOW() NOT NULL,   -- Modified Time
      enabled boolean DEFAULT true NOT NULL
);

CREATE TABLE portal.parent_persistent (
    version  int   DEFAULT 1     NOT NULL   -- Version Number
);


CREATE TABLE portal.customers
(
  customer_id int NOT NULL,
  zone_id int NOT NULL,
  <other properties go here>
  CONSTRAINT pk_customers PRIMARY KEY (zone_id, customer_id)
) INHERITS (portal.parent_stat, portal.parent_persistent);

CREATE TABLE portal.users
(
  user_id bigserial NOT NULL,
  customer_zone_id int NOT NULL,
  customer_id int NOT NULL,
  <more properties here>
  CONSTRAINT pk_users_user_id PRIMARY KEY (user_id),
  CONSTRAINT fk_users_customers FOREIGN KEY (customer_zone_id, customer_id) REFERENCES customers(zone_id, customer_id) ON UPDATE RESTRICT ON DELETE RESTRICT
) INHERITS (portal.parent_stat, portal.parent_persistent);


Code:
<hibernate-mapping>
    <class name="CustomerImpl" proxy="Customer" schema="portal" table="customers">
        <composite-id name="key" class="CustomerKeyImpl">
            <key-property name="zoneId" type="int" column="zone_id"/>
            <key-property name="customerId" type="int" column="customer_id"/>
        </composite-id>
        &version;
        &auditable;
        <set name="users" lazy="true" inverse="true" order-by="lower(email) asc" cascade="save-update,delete">
            <key>
                <column name="customer_zone_id"/>
                <column name="customer_id"/>
            </key>
            <one-to-many class="UserImpl"/>
        </set>
    </class>
</hibernate-mapping>

<hibernate-mapping default-lazy="true">
    <class name="UserImpl" proxy="User" schema="portal" table="users">
        <id name="id" type="java.lang.Long" column="user_id">
            <generator class="identity"/>
        </id>
        &version;
        &auditable;
        <many-to-one name="customer" class="CustomerImpl" not-null="true" cascade="save-update">
            <column name="customer_zone_id"/>
            <column name="customer_id"/>
        </many-to-one>
    </class>
</hibernate-mapping>

The version entity and the auditable entity are defined as follows:

Code:
<version name="version" column="version" unsaved-value="null" type="java.lang.Long"/>

and

Code:
<property name="created" type="java.util.Calendar" column="btime" generated="insert" insert="false" update="false"/>
<property name="modified" type="java.util.Calendar" column="mtime" generated="always" insert="false" update="false"/>

Finally, the following stored procedure is set up to execute before insert on both tables, which is how the mtime gets updated.

Code:
CREATE OR REPLACE FUNCTION touchrow() RETURNS TRIGGER AS $$
DECLARE
mtime timestamp NOT NULL DEFAULT NOW();
BEGIN

NEW.mtime := mtime;

RAISE DEBUG 'mtime=%', NEW.mtime;

RETURN NEW;
END;
$$ LANGUAGE plpgsql;

All of the this functionality, including the parent tables and the stored proc for updating the mtime have been used in other apps. The only difference is the composite key of the parent object.

Note: I can store the parent object without a reference to the child without difficulty. If I look at my sql logs, I can see that hibernate issues a separate select after performing the insert in that case - I presume it is the difference between cascaded save and not, or else the different between composite primary key and composite foreign key. The dialect only issues the "insert...returning *" syntax on the child object, and it does it whether I save the parent first, then add the child before saving the child, or if I just let the parent cascade to the child (or vice versa).


Top
 Profile  
 
 Post subject: Re: Composite id in hibernate+postgres breaks due to returned co
PostPosted: Mon Feb 22, 2010 12:23 am 
Newbie

Joined: Wed May 18, 2005 3:02 pm
Posts: 18
I have a little more information. I'm still not entirely sure how the "RETURNING *" bit is being appended to the query. By the time the store() method executes, the prepared statement is already created, and I haven't figured out where the prepared statements are getting built. However, I have found that the source of the problem is that the IdentityGenerator assumes the result set will contain only the identifier, or that it will at least be the first column. In:

IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(PreparedStatement insert)

the following is called:

IdentifierGeneratorFactory.getGeneratedIdentity(rs,persister.getIdentifierType());

which tries to force the first column in the resultset to the type (a Long, in my case). It really needs to pull the name AND the type from persister and then get the identity column by name instead of position. Easy enough to write code to do so, but I have no idea how to inject it into the codebase short of classpath manipulations


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

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.