0

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

“插入表名(column1,column2,column3,column4)值(value1,value2,value3,value4)返回*”

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

有问题的表有一个 btime 和 mtime 字段,它们通过插入时的触发器进行更新。两者都是时间戳列。这些是返回的前两列。我花了一段时间尝试调试休眠,但这是一个缓慢的过程。我相信正在发生的事情是第一个时间戳列被假定为生成的 id 列,并且在尝试将时间戳字符串转换为 Long 时失败。事实上,生成的 id 显示为第 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 列使用了几乎相同的设置,该应用程序具有特定于休眠的模式,在任何地方都使用生成的长 ID。因为 btime 和 mtime 来自 db 中所有其他表都继承的父表,所以无法更改列的顺序。

最终结果是父插入和级联子插入都成功了,但是当休眠为子实体加载生成的字段失败后,它会抛出异常。这感觉很像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 会发出单独的选择 - 我认为这是级联保存与非级联保存之间的差异,或者复合主键和复合外键之间的差异。方言只在子对象上发出“插入...返回*”语法,无论我先保存父对象,然后在保存子对象之前添加子对象,还是让父对象级联到子对象,它都会执行此操作(或相反亦然)。

4

1 回答 1

1

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

于 2010-02-25T23:34:02.877 回答