如何对一个同时被用作一对多外键的复合主键属性进行“insert='false' update='false'”映射?

54
我正在处理一个遗留代码库,其中存在一个现有的数据库模式。现有代码使用SQL和PL/SQL在数据库上执行查询。我们被要求使项目的一小部分与数据库引擎无关(首先更改所有内容)。我们选择使用Hibernate 3.3.2.GA和“*.hbm.xml”映射文件(而不是注释)。不幸的是,由于不能回退任何遗留功能,因此无法更改现有模式。
我遇到的问题是,当我尝试映射单向一对多关系并且FK也是复合PK的一部分时。以下是相关类和映射文件...
CompanyEntity.java
public class CompanyEntity {
    private Integer id;
    private Set<CompanyNameEntity> names;
    ...
}

CompanyNameEntity.java

public class CompanyNameEntity implements Serializable {
    private Integer id;
    private String languageId;
    private String name;
    ...
}

CompanyNameEntity.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.jboss.org/dtd/hibernate/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.example">

    <class name="com.example.CompanyEntity" table="COMPANY">
        <id name="id" column="COMPANY_ID"/>
        <set name="names" table="COMPANY_NAME" cascade="all-delete-orphan" fetch="join" batch-size="1" lazy="false">
            <key column="COMPANY_ID"/>
            <one-to-many entity-name="vendorName"/>
        </set>
    </class>

    <class entity-name="companyName" name="com.example.CompanyNameEntity" table="COMPANY_NAME">
        <composite-id>
            <key-property name="id" column="COMPANY_ID"/>
            <key-property name="languageId" column="LANGUAGE_ID"/>
        </composite-id>
        <property name="name" column="NAME" length="255"/>
    </class>

</hibernate-mapping>

当我使用这段代码进行带有名称的公司的SELECT和INSERT时,它完全可以正常工作。但是当我尝试更新现有记录时,遇到了问题。我收到了一个BatchUpdateException异常,并在查看SQL日志后发现Hibernate试图做一些愚蠢的事情...

update COMPANY_NAME set COMPANY_ID=null where COMPANY_ID=?

Hibernate在更新子记录之前尝试取消关联它们。问题在于此字段是PK的一部分,而且不能为空。我找到了一个快速解决方案,让Hibernate不这样做是在父映射中添加"not-null='true'"到"key"元素。因此,现在我的映射看起来像这样...

CompanyNameEntity.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.jboss.org/dtd/hibernate/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.example">

    <class name="com.example.CompanyEntity" table="COMPANY">
        <id name="id" column="COMPANY_ID"/>
        <set name="names" table="COMPANY_NAME" cascade="all-delete-orphan" fetch="join" batch-size="1" lazy="false">
            <key column="COMPANY_ID" not-null="true"/>
            <one-to-many entity-name="vendorName"/>
        </set>
    </class>

    <class entity-name="companyName" name="com.example.CompanyNameEntity" table="COMPANY_NAME">
        <composite-id>
            <key-property name="id" column="COMPANY_ID"/>
            <key-property name="languageId" column="LANGUAGE_ID"/>
        </composite-id>
        <property name="name" column="NAME" length="255"/>
    </class>

</hibernate-mapping>

这个映射会抛出异常...

org.hibernate.MappingException: Repeated column in mapping for entity: companyName column: COMPANY_ID (should be mapped with insert="false" update="false")

我的问题现在是,我尝试将这些属性添加到key-property元素中,但这不受DTD支持。我还尝试将其更改为key-many-to-one元素,但也没有起作用。那么...

如何将“insert ='false' update ='false'”映射到一个组合ID key-property,该属性还用于一个一对多的外键?


在查看了Hibernate注释文档之后,我发现我可以使用JPA/Hibernate注释来创建所需的映射。我会再做一些实验并发布我的结果。 - Jesse Webb
我试图指定的映射无法使用*.hbm.xml文件实现。唯一的方法是使用注释,如mcyalcin在下面的答案中提到的那样。 - Jesse Webb
2
我猜这篇帖子可能会解决XML配置中的问题,https://dev59.com/vmox5IYBdhLWcg3wLhUA - Dino Tw
@DinoTw,你提供的链接提示真的很有用。将“inverse=true”标记设置为真的确实允许具有复合键的表负责更新和插入,并解决了XML映射中的问题。 - Narendran Solai Sridharan
2个回答

109

我认为你在寻找的注释是:

public class CompanyName implements Serializable {
//...
@JoinColumn(name = "COMPANY_ID", referencedColumnName = "COMPANY_ID", insertable = false, updatable = false)
private Company company;

你应该能够使用类似的映射在一个hbm.xml文件中,就像在这里展示的一样(在23.4.2节):

http://docs.jboss.org/hibernate/core/3.3/reference/en/html/example-mappings.html


1
谢谢您的回答。在您提供的JBoss文档链接中,没有包含我遇到问题的任何示例。我尝试按照他们展示的方式使用映射,但仍然无法解决我的问题。看起来我正在尝试完成的特定映射在当前的HBM DTD规范中是不可能的。话虽如此,您提供的注释解决方案并没有面临我所遇到的同样问题,因此对于任何有使用注释选项的人来说都是一个好的解决方案。我通常建议每个人都使用注释。 - Jesse Webb
有趣。到目前为止,我只在Hibernate中使用了注释,发现这更方便,但我认为注释功能是xml映射定义的子集,而不是相反。如果确实存在缺失功能,则应报告错误。我很高兴这个答案有所帮助! - mcyalcin
1
但是如果我想要插入和更新,有没有其他方法可以摆脱这个问题?因为我认为,insertable = false 和 updatable=false 不会允许我更改“公司”。 - kinshuk4
如果您不想在“CompanyName”中使用“Company”对象引用怎么办?我的意思是,“CompanyName”只是一个值,关系已经由“one-to-many”定义。在“CompanyName”中拥有“Company”属性毫无意义。 - plalx
CompanyName只是一个例子。如果你使用的对象真的“只是一个值”,你就不需要为它创建一个表/类,而是将其作为列/字段添加到父对象中,并完成相应的操作即可。 - mcyalcin

0
"Dino TW" 提供了评论链接 Hibernate Mapping Exception : Repeated column in mapping for entity,其中包含重要信息。
该链接提示在 set 映射中提供 "inverse=true",我尝试了一下,它确实有效。这是一个非常罕见的情况,其中 Set 和 Composite key 结合在一起。通过设置 inverse=true,我们将表格的插入和更新留给 Composite key 自己处理。
以下是所需的映射:
<class name="com.example.CompanyEntity" table="COMPANY">
    <id name="id" column="COMPANY_ID"/>
    <set name="names" inverse="true" table="COMPANY_NAME" cascade="all-delete-orphan" fetch="join" batch-size="1" lazy="false">
        <key column="COMPANY_ID" not-null="true"/>
        <one-to-many entity-name="vendorName"/>
    </set>
</class>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接