【问题标题】:Issue: Table per sub class using discriminator问题:使用鉴别器的每个子类的表
【发布时间】:2010-10-05 13:11:08
【问题描述】:

问题的简短摘要

Hibernate 假设每当有人使用子类时,所有列都应该在类层次结构的顶部根中创建,而不管提供了什么表名。 有人可以解释一下它背后的原因吗如果有办法绕过此代码并以某种方式避免在事务表中创建 invoice_id ???

详情

我的域模型中有一个非常深的类层次结构,并希望使用鉴别标记来支持继承。

最初我使用连接子类映射我的类,但是所涉及的表有数十万条记录,结果证明这是一个性能瓶颈。所以本质上我想在不触及域的情况下扁平化我的表结构,这就是我们转向鉴别器标签的原因。

我的休眠映射看起来像(为简单起见删除了额外的列)

交易映射

<class name="Transaction" table="transaction" abstract="true">
   ...
    <discriminator column="transaction_type" type="string"/>
</class>

CashBasedTrsansaction映射

<subclass name="CashBasedTransaction" extends="Transaction" discriminator-value="CASH">
    <join table="cash_based_transaction">
        <key column="id" />
    </join>
    <subclass discriminator-value="BILLING" name="BillingTransaction">
        <join table="billing_transaction">
            <key column="id" />
            <many-to-one name="invoice" column="invoice_id" cascade="all" access="field" lazy="false">
            </many-to-one>
        </join>
        <subclass name="ChildBillingTransaction" discriminator-value="UPT">
            <join table="billing_transaction">
                <key column="id" />
                ...
            </join>
        </subclass>
        <subclass abstract="true" name="AnotherChildOfBillingTransaction" discriminator-value="LPT">
             <subclass name="SuperChildOfBillingTransaction" discriminator-value="OCLPT">
                 <join table="billing_transaction">
                     <key column="id" />
                     ...
                 </join>
             </subclass>
             <subclass name="AnoherSuperChildOfBillingTransaction" discriminator-value="SLPT">
                 <join table="billing_transaction">
                     <key column="id" />
                     ...
                 </join>
             </subclass>
         </subclass><!--End Of AnotherChildOfBillingTransaction-->
     </subclass><!--End Of BillingTransaction-->
</subclass><!--End Of CashBasedTransaction-->

发票映射

<class name="Invoice" table="invoice">
    ...
    <bag name="billingTransactions" access="field" cascade="all" inverse="true" table="billing_transaction">
        <key column="invoice_id" />
        <one-to-many class="BillingTransaction" />
    </bag>
</class>

我想要实现的目标:我想在 billing_transaction 之后展平表结构。换句话说,我希望数据库中只有三个表

  • 交易
  • cash_based_transaction
  • billing_transaction(此表应在展平所有子类后包含所有列)

P.S:请注意,我不是从聚合根(读取事务)而是从我的类层次结构中的某处展开表结构,在这种情况下为 billing_transaction。

问题:Hibernate 正在事务表(这是错误的)和 billing_transaction(这是正确的)中创建一个列“invoice_id”。在进一步调试中,我发现了一些有趣的结果,需要一些反馈/建议。

  • Hibernate 在 billing_transaction 中创建了我想要的列 invoice_id。

  • Hibernate 还会在事务表中创建另一个具有相同名称的列,即 invoice_id,这不是我想要的。

现在这令人沮丧。即使我在 invoice.hbm 中提到了表名(Billing_Transaction)并设置了 inverse="true",但休眠继续并在事务表中创建列 invoice_id,即使 Billing_Transaction 中已经有一个列。我期待,因为我给它表名 hibernate 应该采用该名称并检查 billing_transaction 是否具有 invoice_id ......相反,hibernate 正在做完全相反的事情。它完全忽略了我提供的表名并接触到了最超级的类,即事务。当它发现不存在这样的列时,它会在那里创建 invoice_id 列。结果,我的表​​中有两个 invoice_id 列。一个在 billing_transaction 中我希望它存在的位置,另一个在我不希望它存在的事务表中。

我找到了导致它的代码。在 org.hibernate.mapping.Subclass 表中使用下面给出的代码标识

public Property getIdentifierProperty() {
  return getSuperclass().getIdentifierProperty();

}

换句话说,hibernate 假设每当有人使用子类时,所有列都应该在类层次结构的顶级根中创建,而与提供的表名无关。有人可以解释一下吗?背后的推理以及是否有办法绕过此代码并以某种方式避免在事务表中创建 invoice_id ???

【问题讨论】:

  • 如果问题不够清楚请告诉我???我将尝试进一步详细说明......我在这个论坛上听到了很多关于非常迅速的回复,不幸的是到目前为止,回复非常温暖......

标签: hibernate


【解决方案1】:

如果你有

/**
  * MAPPED TO TRANSACTION
  */
public class Transaction {...}

/**
  * MAPPED TO CASH_BASED_TRANSACTION
  */
public class CashBasedTransaction extends Transaction {...}


/**
  * MAPPED TO BILLING_TRANSACTION
  */    
public class BillingTransaction extends CashBasedTransaction {...}

Invoice 需要一个 BillingTransaction 包(以及它的超类 CashBasedTransaction)(以及它的祖父超类 Transaction,对吧)。

这是通过使用外部 INVOICE_ID 来完成的。是的,只需检索 BillingTransaction 就有意义。可以使用其相关的主键检索其超类。所以也许 CashBasedTransaction 和 Transaction 不需要 INVOICE_ID。会不会是bug???可能是可能不是。 Hibernate Team 可能有某些原因生成此 SQL。

牢记:让您的代码保持简单,只映射客户的需求。如果客户需要其他要求,重构(和单元测试)是您最好的朋友。

建议

  • 继承可以映射为 has-a 而不是 is-a

如果你有

/**
  * CashBasedTransaction IS A Transaction
  */
public class CashBasedTransaction extends Transaction {...}

你可以重构为

public class CashBasedTransaction {

   /**
     * CashBasedTransaction HAS A Transaction
     */
    Transaction transaction;

}

更新

避免继承映射的解决方法:删除所有 子类 并使用 join 映射其继承的属性。只是一个简单的映射(适应您的映射)

家长

public class Parent {

    private String parentProperty;
    public String getParentProperty() { return parentProperty; }
    public void setParentProperty(String parentProperty) { this.parentProperty = parentProperty; }

}

儿童

public class Child extends Parent {

    private Integer id;
    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }

}

发票

public class Invoice {

    private Integer id;
    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }

    private Collection<Child> childList = new ArrayList<Child>();
    public  Collection<Child> getChildList() { return childList; }
    public void setChildList( Collection<Child> childList) { this.childList = childList; }

}

映射如下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="mapping._3863924.model.domain">
    <class name="Child" table="CHILD">
        <id name="id">
            <generator class="native"/>
        </id>
        <join table="PARENT">
            <key column="CHILD_ID"/>
            <property name="parentProperty"/>
        </join>
    </class>
    <class name="Invoice" table="INVOICE">
        <id name="id">
            <generator class="native"/>
        </id>
        <bag name="childList" table="CHILD">
            <key column="INVOICE_ID"/>
            <one-to-many class="Child"/>
        </bag>
    </class>
</hibernate-mapping>

标准输出显示

create table CHILD (
    id integer generated by default as identity (start with 1),
    INVOICE_ID integer,
    primary key (id)
)

create table INVOICE (
    id integer generated by default as identity (start with 1),
    primary key (id)
)

create table PARENT (
    CHILD_ID integer not null,
    parentProperty varchar(255),
    primary key (CHILD_ID)
)

注意 PARENT 表不包含 INVOICE_ID。 (这是你想要的,不是吗?)

【讨论】:

  • 首先非常感谢您的回复....是的,您是对的,我们可以使用组合与继承,但是还有其他一些具有事务的对象。现在,如果我们采用建议的方法,我们最终将在同一个对象中引用 BillingTransactions、SomeOtherTransactions 等。现在是正确的做法,可能不是。但我想确保在我继续更改我的类层次结构之前没有任何东西。
  • @user461252 我会尝试一些可以帮助你的场景。通常,初学者 StackOverflow 用户会寻求帮助然后离开。它解释了为什么你的问题没有这么快得到回答。如果我的回答满足您的需求,请单击向上箭头或标记为已接受(“v”图标)
  • 这是我花时间阅读问题...并回答的赞成票。
  • @Pascal Thivent 感谢您的支持,Pascal。周末,我会看到你对敏捷/Scrum 的回答(并收集有用的建议)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多