Составной идентификатор в hibernate+ разрывы postgres из-за возвращенного порядка столбцов

У меня есть родительский объект с составным идентификатором (устаревшая БД - не могу изменить это). У меня есть дочерний объект, который является двунаправленным отношением один-ко-многим (родитель-ребенок). Отображение правильное, так как я могу загрузить экземпляр любой сущности и правильно перемещаться по взаимосвязи. Моя проблема возникает, когда я храню родителя, и это каскадирует ребенка. Диалект Postgres выдает запрос в форме:

"вставить в имя таблицы (столбец1, столбец2, столбец3, столбец4) значения (значение1, значение2, значение3, значение4), возвращая *"

Это хороший ярлык postgres для возврата всех значений только что вставленной строки. Однако столбцы возвращаются в произвольном порядке, установленном БД, хотя это стандартный набор результатов со всеми включенными метаданными столбца. Тем не менее, postgres предполагает, что возвращающиеся столбцы находятся в произвольном порядке.

В рассматриваемой таблице есть поля btime и mtime, которые обновляются через триггер при вставке. Оба являются столбцами меток времени. Это первые два столбца, которые возвращаются. Я потратил некоторое время, пытаясь отладить спящий режим, но это медленный процесс. Я полагаю, что происходит то, что первый столбец метки времени считается сгенерированным столбцом идентификатора, и он терпит неудачу, когда пытается преобразовать строку метки времени в Long. На самом деле, сгенерированный идентификатор отображается в 4-м столбце.

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)

Я полагаю, что это как-то связано с использованием составного ключа, поскольку я использовал почти идентичные настройки для столбцов btime и mtime в другом приложении, которое имеет специфичную для hibernate схему, которая везде использует сгенерированные длинные идентификаторы. Поскольку btime и mtime происходят из родительской таблицы, которую наследуют все другие таблицы в базе данных, нет способа изменить порядок столбцов.

Конечным результатом является то, что родительская вставка и каскадная дочерняя вставка успешно выполняются, но затем hibernate генерирует исключение после того, как не удается загрузить сгенерированные поля для дочерней сущности. Это очень похоже на ошибку в спящем режиме, и это остановило меня. Я надеюсь, что кто-то знает обходной путь или исправление ошибки.

Я использую hibernate-3.3.1-GA, распространяемый springsource в пакете зависимостей с последней версией весны 3.0.1

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);

<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>

Сущность версии и проверяемая сущность определяются следующим образом:

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

а также

<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"/>

Наконец, следующая хранимая процедура настроена для выполнения перед вставкой в ​​обе таблицы, как обновляется mtime.

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;

Все эти функции, включая родительские таблицы и хранимый процесс обновления mtime, были использованы в других приложениях. Единственным отличием является составной ключ родительского объекта.

Примечание: я могу без труда хранить родительский объект без ссылки на ребенка. Если я посмотрю на свои журналы sql, то увижу, что hibernate выдает отдельный выбор после выполнения вставки в этом случае - я предполагаю, что это разница между каскадным сохранением и нет, или же разница между составным первичным ключом и составным внешним ключом. Диалект только выдает синтаксис "insert...returning *" для дочернего объекта, и он делает это, независимо от того, сначала я сохраняю родителя, затем добавляю потомка перед сохранением потомка, или если я просто позволяю родительскому каскаду дочернему объекту (или наоборот).

1 ответ

Я так и не смог найти решение этой проблемы. Единственным разумным решением было прекратить использовать генерацию идентификационного ключа и перейти к генератору идентификатора последовательности. Это привело к тому, что hibernate не пытался использовать предложение return для оператора вставки / обновления.

Другие вопросы по тегам