JPA/Hibernate - Embedding an Attribute

10,562

Solution 1

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.

Solution 2

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...

Solution 3

Hmm, not sure. Have you tried to put @MappedSuperclasson PartNumber and @Embeddable on the subclasses. See also - https://forum.hibernate.org/viewtopic.php?t=966129

As a side node - have you considered using Hibernate Validator instead of the home grown validate method?

Solution 4

What it looks like you are trying to do is use "Embedable Inheritance" which I don't believe hibernate supports yet.

@Embeddable
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="partType", discriminatorType=DiscriminatorType.STRING)
public PartNumber getPartNumber() 
{
  return partNumber;
}

Since it looks like the dicriminating value between Ford and Chevy looks like a format. I would probably hide the mapping and add a different function to return the specific type you want.

@Embeddable
public PartNumber getPartNumber() 
{
  return partNumber;
}

@Transient
public SpecificPartNumber getSpecificPartNumber()
{
  return PartNumberFactory.create( getPartNumber() );
}

See the links in the comments...

Solution 5

EclipseLink supports Inheritance in Embeddables via @Customizer http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Entities/Embeddable#Inheritance

Share:
10,562
mainstringargs
Author by

mainstringargs

Updated on June 26, 2022

Comments

  • mainstringargs
    mainstringargs almost 2 years

    I am having a trouble mapping an embedded attribute of a class. I have created some classes that are similar to what I am trying to do to illustrate. Basically, I have an @Embeddable class hierarchy that uses Inheritance. The top level class "Part Number" has only one attribute, and the extending classes add no attributes to the "Part Number" class, they only add some validation/logic.

    Here is what I mean:

    PART

    @Entity
    @Table(name="PART")
    public class Part {
        private Integer id;
        private String name;
        private PartNumber partNumber;
    
        @Id
        @GeneratedValue(strategy=GenerationType.SEQUENCE)
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
    
        @Column(name="PART_NAME")
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    
        @Embedded
        public PartNumber getPartNumber() {
            return partNumber;
        }
        public void setPartNumber(PartNumber partNumber) {
            this.partNumber = partNumber;
        }
    
    }
    

    PARTNUMBER

    @Embeddable
    public abstract class PartNumber {
    
        protected String partNumber;
        private String generalPartNumber;
        private String specificPartNumber;
    
        private PartNumber() {
    
        }
    
        public PartNumber(String partNumber) {
            this.partNumber = partNumber;
    
        }
    
        @Column(name = "PART_NUMBER")
        public String getPartNumber() {
            return partNumber;
        }
    
        public void setPartNumber(String partNumber) {
            this.partNumber = partNumber;
        }
    
        /**
         * @param partNumber
         * @return
         */
        public boolean validate(String partNumber) {
            // do some validation
            return true;
        }
    
        /**
         * Returns the first half of the Part Number
         * 
         * @return generalPartNumber
         */
        @Transient
        public String getGeneralPartNumber() {
            return generalPartNumber;
    
        }
    
        /**
         * Returns the last half of the Part Number 
         * which is specific to each Car Brand
         * 
         * @return specificPartNumber
         */
        @Transient
        public String getSpecificPartNumber() {
            return specificPartNumber;
    
        }
    
    }
    

    FORD PARTNUMBER

    public class FordPartNumber extends PartNumber {
    
        /**
         * Ford Part Number is formatted as 1234-#1234
         * 
         * @param partNumber
         */
        public FordPartNumber(String partNumber) {
            super(partNumber);
            validate(partNumber);
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see com.test.PartNumber#validate(java.lang.String)
         */
        @Override
        public boolean validate(String partNumber) {
            // do some validation
            return true;
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see com.test.PartNumber#getGeneralPartNumber()
         */
        @Override
        public String getGeneralPartNumber() {
            return partNumber;
    
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see com.test.PartNumber#getSpecificPartNumber()
         */
        @Override
        public String getSpecificPartNumber() {
            return partNumber;
    
        }
    
    }
    

    CHEVY PARTNUMBER

    public class ChevyPartNumber extends PartNumber {
    
        /**
         * Chevy Part Number is formatted as 1234-$1234
         * 
         * @param partNumber
         */
        public ChevyPartNumber(String partNumber) {
            super(partNumber);
            validate(partNumber);
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see com.test.PartNumber#validate(java.lang.String)
         */
        @Override
        public boolean validate(String partNumber) {
            // do some validation
            return true;
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see com.test.PartNumber#getGeneralPartNumber()
         */
        @Override
        public String getGeneralPartNumber() {
            return partNumber;
    
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see com.test.PartNumber#getSpecificPartNumber()
         */
        @Override
        public String getSpecificPartNumber() {
            return partNumber;
    
        }
    }
    

    Of course this does not work, because Hibernate ignores the Inheritance Hierarchy and doesn't like the fact that PartNumber is abstract. Is there some way to do this using JPA or Hibernate Annotations? I have tried using the @Inheritance JPA annotation.

    I am not able to refactor the "PartNumber" part of the hierarchy because the original Developer wants to be able to extend PartNumber with N many XXXXPartNumber classes.

    Does anyone know if anything like this will be a part of the JPA 2.0 or a new version of Hibernate?

  • skaffman
    skaffman almost 15 years
    Nice call. I'm curious as to how discovers the subclasses, given that the config does not mention them, and the JVM doesn't allow you to discover them.
  • ccclark
    ccclark almost 15 years
  • Hardy
    Hardy almost 15 years
    what do you mean with "JVM doesn't allow you to discover them"?
  • Petriborg
    Petriborg almost 15 years
    @skaffman - I think you need to move the @Embedded tag to the parent class and use the @MappedSuperclass... possibly?
  • ChssPly76
    ChssPly76 almost 15 years
    You just need to implement Hibernate's UserType interface - it's reasonably straightforward. Here's an example from Hibernate docs: (hibernate.org/172.html). In your case you would return PartNumber from "returnedClass()" method and dynamically instantiate appropriate descendant class in "nullSafeGet()" based on a value you store in a column (and get from ResultSet).