【问题标题】:Foreign key creation in one-to-one table-per-concrete-class structure在每个具体类的一对一表结构中创建外键
【发布时间】:2023-03-03 01:50:01
【问题描述】:

TL;DR 如何强制创建 Hibernate 模式,以在从 AbstractProperty.ownerIdOwner.ownerId 的每个具体类的表设置中为下面显示的结构创建外键约束,而不向AbstractProperty 添加Owner 属性?

我正在从事一个具有以下类结构的项目:

OwnerAbstractProperty 具有一对一的映射关系,该映射由 ConcreteProperty 类扩展(以及像 AnotherProperty 这样的其他类,但这与本问题的其余部分并不真正相关) .

AbstractProperty 实际上只有一个属性,abstractPropertyId。因此,我们希望使用table-per-concrete-class 结构,以表OwnerConcreteProperty 和其他AbstractProperty 扩展类(AnotherProperty) 的表结尾。

为此,我为Owner创建了以下映射:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.example">
    <class name="Owner">
        <id name="ownerId">
            <generator class="identity"/>
        </id>
        <property name="ownerProperty"/>
        <one-to-one name="abstractProperty"/>
    </class>
</hibernate-mapping>

对于AbstractProperty

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.example">
    <class name="AbstractProperty" abstract="true">
        <id name="ownerId">
            <generator class="foreign">
                <param name="property">ownerId</param>
            </generator>
        </id>
        <union-subclass name="ConcreteProperty">
            <property name="concreteProperty"/>
        </union-subclass>
        <union-subclass name="AnotherProperty">
            <property name="anotherProperty"/>
        </union-subclass>
    </class>
</hibernate-mapping>

这行得通。

但是,这是我的问题,使用此映射并让 Hibernate 为我创建架构 (&lt;property name="hbm2ddl.auto"&gt;create&lt;/property&gt;),它不会创建从 ConcreteProperty.ownerId 数据库字段到 Owner.ownerId 字段的外键约束。当我使用AbstractProperty 的此映射创建从AbstractPropertyOwner 的反向约束一对一字段时(其中owner 字段在AbstractProperty java 类中的类型为Owner ):

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.example">
    <class name="AbstractProperty" abstract="true">
        <id name="ownerId">
            <generator class="foreign">
                <param name="property">ownerId</param>
            </generator>
        </id>
        <one-to-one name="owner" constrained="true"/>
        <union-subclass name="ConcreteProperty">
            <property name="concreteProperty"/>
        </union-subclass>
        <union-subclass name="AnotherProperty">
            <property name="anotherProperty"/>
        </union-subclass>
    </class>
</hibernate-mapping>

如果我的AbstractProperty 中没有此Owner 字段,如何强制创建从AbstractProperty.ownerIdOwner.ownerId 的外键?

【问题讨论】:

  • 您为什么期望/需要从*Property 表到Owner 表的数据库列/引用?根据您的方案,我想说 DB 中带外键的唯一引用是从 Owner 表到 *Property...
  • 我无法设置从OwnerabstractPropertyId 的外键,因为我不知道它引用了哪个表(即哪个AbstractProperty 实现)。
  • 不是一个实际的答案,而是一个关于Why-What-How 范围内“为什么”的问题:您是否明确构建了一个实体-属性-值模型?
  • @drvdijk,我们已经实现了类似的东西,如果没有这个 在 Abstractproperty.hbm 中,就无法创建它.xml。我查看了我们 4-5 年前的代码,这正是我们所做的。

标签: java hibernate orm


【解决方案1】:

简单的答案:永远不要让 Hibernate 为实际应用程序创建模式。

Hibernate 是一个对象关系映射器,应该这样对待它。

Hibernate 额外创建模式最多是第一次。但是在第一个版本之后的环境中,您不想让 Hibernate 控制模式。毕竟,您必须处理 SQL 才能拥有迁移脚本(手动或工具支持)。在第一个版本之后,您将在数据库中拥有数据。为了确保生产系统上的数据迁移问题较少,您应该像在开发环境中一样考虑在生产环境中迁移架构和数据的方式。

例外情况可以是任何很少更改数据的应用程序,这些数据可能在数据丢失时可以快速重建。

【讨论】:

    【解决方案2】:

    我们使用标准 JPA(没有特定于休眠的 hack)并且遇到了同样的问题,但我们没有找到好的解决方案。

    假设:

    1. AbstractProperty 是包中的一个类,可在不同应用程序中重用/共享,并且您不希望引用特定于应用程序的Owner 类。
    2. ConcreteProperty & AnotherProperty 是特定于应用程序的。

    在这种情况下,解决方案是将ConcreteProperty 中的引用放入Owner(带有外键),最终使用AnotherProperty 扩展相同的ApplicationProperty,并将abstractPropertyId 设为私有,这样在设置所有者时,它会自动设置。

    【讨论】:

    • 您的意思是AbstractProperty 而不是ApplicationProperty?在我的应用程序中,我宁愿根本不引用Owner,以防止以我不希望的方式解决循环序列化问题。否则,您的解决方案将保留 AbstractProperty Owner-unaware,但是您将在所有子类中复制粘贴 Owner getter 和 setter,这通常由抽象超类解决。
    • 不,我的意思是ApplicationProperty 只是另一个类(特定于应用程序),它扩展AbstractProperty 作为所有Owner 感知具体类的超类型(如ConcreteProperty 和@ 987654340@)
    • 好吧,当然,很好!不幸的是,它仍然没有解决我的循环序列化问题,为此我在AbstractProperty 树中根本无法拥有Owner
    • 您也可以在您的“所有者”中工作,只能使用“所有者”感知的“ApplicationProperty”(当然是“AbstractProperty”),这样您就可以解决序列化问题,不是吗是吗?
    • 这只是介绍了Owner-unaware AbstractProperty,而其余的设置就像我现在拥有的一样(我的AbstractProperty 是你的ApplicationPropery)。问题是我以反射和递归方式序列化Owner,这就是为什么我宁愿拥有从OwnerAbstractProperty 的单边关系。
    【解决方案3】:

    如果将 Abstract Property 上的 Owner 属性定义为“transient”,它不会自动工作吗?

    变量可能被标记为瞬态,以表明它们不是对象持久状态的一部分。

    如果您实现自己的手动序列化,您可以检查字段上的修饰符并忽略它 --> 避免循环序列化问题。

    我看到的唯一另一种方法是将 Owner 属性推送到每个具体的 Property 类并将映射更改为

    <class name="AbstractProperty" abstract="true">
        <id name="ownerId">
            <generator class="foreign">
                <param name="property">ownerId</param>
            </generator>
        </id>
    
        <union-subclass name="ConcreteProperty">
            <property name="concreteProperty"/>
            <one-to-one name="owner" constrained="true"/>
        </union-subclass>
        <union-subclass name="AnotherProperty">
            <property name="anotherProperty"/>
            <one-to-one name="owner" constrained="true"/>
        </union-subclass>
    </class>
    

    创建以下 sql:

    create table AnotherProperty (
        ownerId integer not null,
        anotherProperty varchar(255),
        primary key (ownerId)
    )
    
    create table ConcreteProperty (
        ownerId integer not null,
        concreteProperty varchar(255),
        primary key (ownerId)
    )
    
    create table Owner (
        ownerId integer generated by default as identity,
        ownerProperty varchar(255),
        primary key (ownerId)
    )
    
    alter table AnotherProperty 
        add constraint FK_ceq89n6x2i1ax18bb4gqpq4m5 
        foreign key (ownerId) 
        references Owner
    
    alter table ConcreteProperty 
        add constraint FK_i41buhvtxxtpsim2cc0ur1gxr 
        foreign key (ownerId) 
        references Owner
    

    【讨论】:

    • 实际上,数据库应该存储属性(使用外键),但由于循环序列化,它不应该出现在Java层次结构中。 transient 关键字实际上可以解决问题,也许序列化框架(尽量避免编写手动自定义内容)在序列化时会忽略它,而我仍然可以在 hbm 文件中指定属性。去看看,谢谢你的想法!
    【解决方案4】:

    首先:Hibernate/JPA 能够处理很多场景 - 如果真的有很多人尝试与您相同的方法,我认为现在应该已经解决了 - 这不是春鸡。 ** 这是一个线索 ;-) **

    第二:拥有一个名为 'Owner' 和 'ownerProperty' 的表是另一个线索。这些名称推断出一种内在的关系。

    第三:只需声明您不想要 AbstractProperty 表中的所有者属性,这就为通常称为 catch-22 (http://en.wikipedia.org/wiki/False_dilemma) 的逻辑谬误奠定了基础。

    我的观点 -> 这似乎更像是一个建模/设计问题,而不是技术/框架问题。

    我的建议是从问题中退后一步,重新评估它。例如,如果您只是使用 spring-jdbc 编写直接查询,您希望如何与 SELECT、UPDATE 和 DELETE 操作的数据进行交互? ...如果您解决了这些问题,您的解决方案/需求可能会更清楚地呈现出来。更确切地说,您希望该行为在级联删除上是什么?如果我对单个 Owner 记录发出 DELETE 语句,您是否希望数据库自动从子表中删除记录?递归?隔离后,您就可以弄清楚如何告诉 Hibernate 该做什么 - 不要让团队成员通过过早地限制解决方案来本末倒置。

    例如,(案例 1)如果您真的在处理所有者的“财产”,可以合理地预见您需要存储有关所有者的多个属性(又名:OneToMany)。

    或者,(案例 2)如果您正在处理所有者的“类型”(如在鉴别器字段中),那么您的“AbstractProperty”表应该扩展所有者......在这种情况下,您可以将您的解决方案减少到 3 个表(带鉴别器的所有者、带 ownerId 的 Concrete1、带 ownerId 的 Concrete2)。

    建议的解决方案 1:在这两种情况下,“AbstractProperty”表仍然可以引用它的父/所有者。如果确实如此,我认为 Cascading DELETES 可能会按照您喜欢的方式工作。

    建议的解决方案 2:但是,如果在 Owner 记录的级联删除场景中,您希望 AbstractProperty 中的行作为参考数据保留,那么可以认为您应该放置一个附加表在 Owner 和 AbstractProperty 之间,以保护您的参考数据...作为具有唯一复合键的映射表。

    关注业务需求和用户故事,这有望指导您选择可用的解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-08-05
      • 1970-01-01
      • 2019-11-03
      • 2016-12-02
      • 1970-01-01
      • 2015-03-10
      • 2019-10-15
      相关资源
      最近更新 更多