hibernate+postgres 中的复合 id 由于返回的列顺序而中断

发布于 2024-08-23 04:12:46 字数 4915 浏览 5 评论 0原文

我有一个带有composite-id 的父对象(旧版数据库 - 无法修改它)。我有一个子对象,它是双向一对多(父对子)关系。映射是正确的,因为我可以加载任一实体的实例并正确地跨关系导航。当我存储父级并将其级联到子级时,我的问题就出现了。 Postgres 方言发出以下形式的查询:

“insert into tablename (column1、column2、column3、column4) 值(值1,值2,值3,值4)返回*“

这是一个很好的postgres快捷方式,用于返回刚刚插入的行的所有值。但是,列以数据库设置的任意顺序返回,尽管它是包含所有值的标准结果集然而,postgres 似乎假设返回的列是按任意顺序排列的,

该表有一个 btime 和 mtime 字段,它们都是通过插入触发器进行更新的。返回的前两列我花了一段时间尝试调试 hibernate,但这是一个缓慢的过程,我认为发生的情况是第一个时间戳列被假定为生成的 id 列,并且当它失败时。它尝试将时间戳字符串转换为 Long。事实上,生成的 id 显示为第四列,

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 的架构,该架构在任何地方都使用生成的长 id。因为 btime 和 mtime 来自数据库中所有其他表继承的父表,所以无法更改列的顺序。

最终结果是父插入和级联子插入都成功,但 hibernate 在加载子实体生成的字段失败后抛出异常。这感觉很像冬眠中的一个错误,它让我停止了寒冷。我希望有人知道解决方法或错误修复。

我正在使用 springsource 在最新的 spring 3.0.1 版本的依赖包中分发的 hibernate-3.3.1-GA

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在执行插入后发出一个单独的选择,在这种情况下 - 我认为这是级联保存和不级联保存之间的区别,或者是复合主键和复合外键之间的区别。该方言仅在子对象上发出“插入...返回*”语法,无论我先保存父对象,然后在保存子对象之前添加子对象,还是只是让父对象级联到子对象,它都会执行此操作(或反之亦然)。

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.

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

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>

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

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

and

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

蓝色星空 2024-08-30 04:12:46

我一直无法找到解决这个问题的办法。唯一合理的解决方法是停止使用身份密钥生成并转而使用序列 ID 生成器。这导致 hibernate 不尝试使用插入/更新语句的“返回”子句。

I was never able to figure out a fix for this problem. The only reasonable workaround was to stop using identity key generation and go to a sequence id generator instead. That caused hibernate to not attempt to use the 'returning' clause of an insert/update statement.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文