Mike Pone
Mike Pone

Reputation: 19320

Hibernate mappings with a discriminator

I have a table with one field that can point to a foreign key in one of 3 other tables based on what the descriminator value is (Project, TimeKeep, or CostCenter. Usually this is implemented with subclasses, and I am wondering if what I have below will work. Note the subclass name is the same as the parent class and the noteObject property is mapped to an instance variable of type java.lang.Object so it should accept either a Project, TimeKeep or CostCenter object as long as we cast to the correct type. Will hibernate allow this? Thanks.

<hibernate-mapping package="com.tlr.finance.mappings">

 <class name="AdminNotes" table="admin_notes">
    <id name="adminNoteId" column="admin_note_id" type="integer">
      <generator class="identity" />
    </id>

<discriminator column="note_type" type="string" />

<!-- make this property an enumerated type.  It is the discriminator -->
<property name="adminNoteType" column="note_type" type="string" not-null="true" />
<property name="adminNote" column="note" type="string" not-null="true" />
<property name="adminNoteAdded" column="note_date" type="timestamp"
  not-null="true" /> 

<subclass name="AdminNotes" discriminator-value="project" >
  <many-to-one name="noteObject" column="object_id" class="PsData" /><!-- Project -->
</subclass>

<subclass name="AdminNotes" discriminator-value="user" >
  <!-- rename timekeep to user -->
  <many-to-one name="noteObject" column="object_id" class="Timekeep" /><!-- user -->
</subclass>

<subclass name="AdminNotes" discriminator-value="costCenter" >
  <!-- rename timekeep to user -->
  <many-to-one name="noteObject" column="object_id" class="CostCenter" /><!-- cost center -->
</subclass>

  </class>

</hibernate-mapping>

Upvotes: 8

Views: 43229

Answers (4)

skaffman
skaffman

Reputation: 403441

Discriminators are used for storing class hierarchies in a single table. What you have there is a single class with multiple meanings.

http://docs.jboss.org/hibernate/core/3.5/reference/en-US/html/mapping.html#mapping-declaration-discriminator

The element is required for polymorphic persistence using the table-per-class-hierarchy mapping strategy and declares a discriminator column of the table. The discriminator column contains marker values that tell the persistence layer what subclass to instantiate for a particular row.

I don't think you'll be able to use a single AdminNote class for each of those different meanings. The discriminator is used at the database level to help distinguish one subclass from another - it's not actually part of the java object model.

You'll need to define multiple subclasses of AdminNote, one for each discriminator value.

Upvotes: 9

Discriminator as integer

Normally, if you specify the discriminator-value as integer in subclass, you get the error

Could not format discriminator value 'TYPE' to SQL string using the (...)

If you wish to use the discriminator as an integer value, you need to first of all specify it for the base class as an integer by setting the discriminator-value attribute in the class element:

<class name="AdminNotes" table="admin_notes" abstract="true" discriminator-value= "-1">

This replaces the default behavior where the discriminator is a class name when a value is not found.

<hibernate-mapping package="com.tlr.finance.mappings">

    <class name="AdminNotes" table="admin_notes" abstract="true" discriminator-value= "-1">
        <id name="adminNoteId" column="admin_note_id" type="integer">
            <generator class="identity" />
        </id>
        <discriminator column="note_type" type="integer" />

        <!-- Make this property an enumerated type. It is the discriminator. -->
        <property name="adminNoteType" column="note_type" type="string" not-null="true" />
        <property name="adminNote" column="note" type="string" not-null="true" />
        <property name="adminNoteAdded" column="note_date" type="timestamp"
                  not-null="true" />

        <subclass name="AdminNotes" discriminator-value="0" entity-name="project">
            <many-to-one name="noteObject" column="object_id" class="PsData" /><!-- Project -->
        </subclass>

        <subclass name="AdminNotes" discriminator-value="1" entity-name="user">
            <!-- Rename timekeep to user -->
            <many-to-one name="noteObject" column="object_id" class="Timekeep" /><!-- user -->
        </subclass>

        <subclass name="AdminNotes" discriminator-value="2" entity-name="costCenter">
            <!-- Rename timekeep to user -->
            <many-to-one name="noteObject" column="object_id" class="CostCenter" /><!-- cost center -->
        </subclass>
    </class>
</hibernate-mapping>

Upvotes: 2

iammichael
iammichael

Reputation: 9757

Instead of trying to use the inheritance capabilities to get the reference to the right class, you should instead consider using the <any/> mapping type where you use the note_type to determine the type that the object_id is referencing and thus setting your noteObject value to the proper object reference.

For details on <any/>, see 'Any type mappings' in the NHibernate documentation, and the blog post NHibernate Mapping.

Upvotes: 0

Anton
Anton

Reputation: 106

AFAIK, you can reuse Java class names for subclass mappings if you use entity-names.

Try the mapping below. Here the super class mapping itself is abstract. The subclasses use the same Java class and an entity-name for each subclass. You might need to put entity-name to the super class mapping. However, I would personally use separate Java subclasses for each subclass mapping, as with entity-names you would have to provide entity-name when you need to persist an object via the Session API, or implement entity-names resolution in Tuplizer (Hibernate 3.3.2) based on the adminNoteType field.

<hibernate-mapping package="com.tlr.finance.mappings">

    <class name="AdminNotes" table="admin_notes" abstract="true">
        <id name="adminNoteId" column="admin_note_id" type="integer">
          <generator class="identity" />
        </id>

        <discriminator column="note_type" type="string" />

        <!-- Make this property an enumerated type. It is the discriminator. -->
        <property name="adminNoteType" column="note_type" type="string" not-null="true" />
        <property name="adminNote" column="note" type="string" not-null="true" />
        <property name="adminNoteAdded" column="note_date" type="timestamp"
          not-null="true" />

        <subclass name="AdminNotes" discriminator-value="project" entity-name="project">
            <many-to-one name="noteObject" column="object_id" class="PsData" /><!-- Project -->
        </subclass>

        <subclass name="AdminNotes" discriminator-value="user" entity-name="user">
            <!-- rename timekeep to user -->
            <many-to-one name="noteObject" column="object_id" class="Timekeep" /><!-- user -->
        </subclass>

        <subclass name="AdminNotes" discriminator-value="costCenter" entity-name="costCenter">
            <!-- rename timekeep to user -->
            <many-to-one name="noteObject" column="object_id" class="CostCenter" /><!-- cost center -->
        </subclass>
    </class>
</hibernate-mapping>

Upvotes: 0

Related Questions