JPA/Hibernate - Embedding an Attribute

Component (e.g. @Embeddable) inheritance is not supported and most likely never will be. There is a good reason for that - entity identifier plays a critical role in all inheritance strategies supported by Hibernate and components don't have (mapped) identifiers.

You have three choices:

A) Map PartNumber (and all its descendants) as entities. PartNumber may remain abstract:

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="part_type", discriminatorType=DiscriminatorType.STRING)
public abstract class PartNumber {
...
}

@Entity
@DiscriminatorValue("Ford")
public class FordPartNumber extends PartNumber {
...
}

B) Based on your example it seems that all PartNumber descendants differ in behavior only (they don't introduce any new properties to be stored). If that's indeed the case, you can map PartNumber properties plus your own discriminator value (so you know which class to instantiate) as @Embedded private property and have get/setPartNumber() accessors in Part class marshall / unmarshall appropriate subclasses. You can even write your own Hibernate custom type to do that for you (it's pretty straightforward).

C) If PartNumber descendants DO differ in properties that have to be stored and mapping them as entities is unacceptable for whatever reason, you can use marshall / unmarshall them to string (as XML or anything else that fits the bill) and store that. I'm using XStream for this exact purpose and I wrote a simple Hibernate type to go with it. Your Part mapping would look something like

@Type(type="xmlBean")
public PartNumber getPartNumber() {
    return partNumber;
}
public void setPartNumber(PartNumber partNumber) {
    this.partNumber = partNumber;
}

and PartNumber descendants won't have to be mapped at all. The downside, of course, is that dealing with XML in the database is a bit more of a hassle so that may not be the ideal approach for something you would potentially need to report on. OTOH, I'm using this for storing plugin settings and it saved me a lot of trouble with mappings / DB maintenance.


I have a similar problem in my own schema so what I've resorted to at the moment is like this:

Parent class:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@SequenceGenerator(name="SEQ", sequenceName="part_id_seq", initialValue=1, allocationSize=1)
public abstract class BasePart {
    @Id
    @Column(name="part_id")
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ")
    protected Long partId;

    @YourBusinessKeyAnnotation
    @Column(name="part_number")
    protected String partNumber
    ...
}

Child classes:

@Entity
public class FordPart extends BasePart {
    ...
}

@Entity
public class ChevyPart extends BasePart {
    ...
}

Now I could then manipulate the biz key however I needed to and this worked out well because each of the different part types got their own table (which is useful for us).

You also could use @Embedded with @AttributeOverrides I think to specify the column names differently however you needed... There is an example from the annotation docs.

@Entity
public class Person implements Serializable {

    // Persistent component using defaults
    Address homeAddress;

    @Embedded
    @AttributeOverrides( {
            @AttributeOverride(name="iso2", column = @Column(name="bornIso2") ),
            @AttributeOverride(name="name", column = @Column(name="bornCountryName") )
    } )
    Country bornIn;
    ...
}

...

@Entity
public class Person implements Serializable {

    // Persistent component using defaults
    Address homeAddress;

    @Embedded
    @AttributeOverrides( {
            @AttributeOverride(name="iso2", column = @Column(name="bornIso2") ),
            @AttributeOverride(name="name", column = @Column(name="bornCountryName") )
    } )
    Country bornIn;
    ...
}

...

@Embedded
@AttributeOverrides( {
        @AttributeOverride(name="city", column = @Column(name="fld_city") ),
        @AttributeOverride(name="nationality.iso2", column = @Column(name="nat_Iso2") ),
        @AttributeOverride(name="nationality.name", column = @Column(name="nat_CountryName") )
        //nationality columns in homeAddress are overridden
} )
Address homeAddress;

You may be able to abuse this enough that you won't care...