Составной идентификатор в 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 для оператора вставки / обновления.