Java 没有好PK的Oracle遗留表:如何休眠?

Java 没有好PK的Oracle遗留表:如何休眠?,java,oracle,hibernate,Java,Oracle,Hibernate,我希望通过Hibernate从Java应用程序访问Oracle数据库中的遗留表。问题是:这些表没有好的主键。例如,表的外观如下所示: create table employee ( first_name varchar(64) not null, last_name varchar(64) not null, hired_at date, department varchar(64) ); create unique index emp_uidx o

我希望通过Hibernate从Java应用程序访问Oracle数据库中的遗留表。问题是:这些表没有好的主键。例如,表的外观如下所示:

create table employee (
  first_name    varchar(64) not null,
  last_name     varchar(64) not null,
  hired_at      date,
  department    varchar(64)
);
create unique index emp_uidx on employee (firstname, lastname, hired_at);
<hibernate-mapping>
  <class name="com.snakeoil.personnel" table="employee">
    <composite-id class="com.snakeoil.personnel.EmpId" name="id">
      <key-property name="firstName" column="first_name" type="string"/>
      <key-property name="lastName" column="last_name" type="string"/>
    </composite-id>
    <property name="hiredAt" column="hired_at" type="date"/>
    <property name="department" type="string">
  </class>
</hibernate-mapping>
最初的设计师决定使用中的列作为状态字段,认为数据库永远不需要区分公司外的约翰·史密斯一家,但可以通过他们在日期雇佣的员工识别同名员工。显然,这会创建一个部分可为空且可修改的主键。由于数据库不允许将其作为主键,因此他使用了唯一索引

现在,如果我尝试为此编写一个Hibernate映射,我可以得出如下结果:

create table employee (
  first_name    varchar(64) not null,
  last_name     varchar(64) not null,
  hired_at      date,
  department    varchar(64)
);
create unique index emp_uidx on employee (firstname, lastname, hired_at);
<hibernate-mapping>
  <class name="com.snakeoil.personnel" table="employee">
    <composite-id class="com.snakeoil.personnel.EmpId" name="id">
      <key-property name="firstName" column="first_name" type="string"/>
      <key-property name="lastName" column="last_name" type="string"/>
    </composite-id>
    <property name="hiredAt" column="hired_at" type="date"/>
    <property name="department" type="string">
  </class>
</hibernate-mapping>

这适用于读取数据,但当我创建仅在hiredAt日期上不同的新条目时,我会遇到org.hibernate.ununiqueObjectExceptions。或者,我可能会考虑Oracle的ROWID特性:

<id column="ROWID" name="id" type="string">
   <generator class="native"/>
</id>

这也适用于读取数据。但是很明显,您不能持久化新对象,因为Hibernate现在抛出org.Hibernate.exception.sqlgrammareexception:在尝试存储实体时无法获取下一个序列值

显而易见的解决方法是通过代理键。然而,这将需要更改数据库模式,这将使我们回到“遗留”问题


我忽略了什么?有没有办法让Hibernate与Oracle的ROWID协作,或者使用不同的生成器?或者有没有一种方法可以让第一个想法起作用,也许是使用聪明的DAO,它可以大量地逐出和重新加载实体,并隐藏应用程序的复杂性?或者我应该寻找一种替代Hibernate的方法吗,Ibatis也许可以?

我会添加一个带有备份序列的代理密钥。它不会改变任何遗留语义,Hibernate也会感到满意


只是好奇——如果索引有名字和姓氏以及雇佣日期,为什么复合键不包括雇佣日期?我希望在复合键中也能看到索引中的所有字段。

您不希望使用
ROWID
作为主键,因为它不能保证在一行的生命周期内保持稳定

添加合成密钥是最佳选择。使用触发器用序列值填充列


您在遗留方面有点模糊,因此很难确定其含义是什么。添加列将中断任何未显式列出目标列的insert语句。它还可能中断任何
SELECT*
查询(除非他们选择使用
%ROWTYPE
关键字声明的变量。但您不必更改任何其他应用程序,使其使用新的主键而不是现有列,除非您真的想这样做。

如果我这样做,Hibernate将返回空值。因此,如果存在(Smith,John,2009-10-01)和(Smith,John,null)和(Smith,Jack,null),一个来自Employee的查询
,其中lastName='Smith'
将返回三个条目,一个(Smith,John,2009-10-01)和两个空指针。这就是为什么我将可空字段作为一个普通属性,希望能够“在DAO中修复它。”是的,这似乎是最好的主意。所有其他选项似乎都需要粗糙的刀魔法。甲骨文ROWID是“稳定的”。唯一的问题是何时(引用):例如,如果使用导入和导出实用程序删除并重新插入一行,则其rowid可能会更改。如果删除一行,则Oracle可能会将其rowid重新分配给以后插入的新行。@xmedeko-故意省略了该段的第一句话“不应将rowid用作表的主键”@APC-不,我不是故意漏掉它的。我同意你的看法,ROWID一般不应该被用作主键。我也不使用它。但在某些特殊情况下可以使用它。例如,PL/SQL Developer是一款非常好的、可靠的软件,它利用ROWID进行CRUD操作。@xmedeko-在CRUD操作中使用ROWID是非常困难的与将其用作主键不同。