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

У меня есть родительский объект с составным идентификатором (устаревшая БД - не могу изменить это). У меня есть дочерний объект, который является двунаправленным отношением один-ко-многим (родитель-ребенок). Отображение правильное, так как я могу загрузить экземпляр любой сущности и правильно перемещаться по взаимосвязи. Моя проблема возникает, когда я храню родителя, и это каскадирует ребенка. Диалект 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 выдает отдельный выбор после выполнения вставки в этом случае - я полагаю, что это разница между каскадным сохранением и нет, или разница между составным первичным ключом и составным внешним ключом. Диалект только выдает синтаксис «вставить ... возвращение *» для дочернего объекта, и он делает это, независимо от того, сначала я сохраняю родительский элемент, затем добавляю дочерний элемент перед сохранением дочернего, или если я просто позволяю родительскому каскаду дочернему объекту (или наоборот).

1 Ответ

1 голос
/ 26 февраля 2010

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

...