Hibernate, single table inheritance and using field from superclass as discriminator column
Asked Answered
T

4

10

I have following kinds of classes for hibernate entity hierarchy. I am trying to have two concrete sub classes Sub1Class and Sub2Class. They are separated by a discriminator column (field) that is defined in MappedSuperClass. There is a abstract entity class EntitySuperClass which is referenced by other entities. The other entities should not care if they are actually referencing Sub1Class or Sub2Class.

It this actually possible? Currently I get this error (because column definition is inherited twice in Sub1Class and in EntitySuperClass) :

Repeated column in mapping for entity: my.package.Sub1Class column: field (should be mapped with insert="false" update="false")

If I add @MappedSuperClass to EntitySuperClass, then I get assertion error from hiberante: it does not like if a class is both Entity and a mapped super class. If I remove @Entity from EntitySuperClass, the class is no longer entity and can't be referenced from other entities:

MappedSuperClass is a part of external package, so if possible it should not be changed.

My classes:

@MappedSuperclass
public class MappedSuperClass {
    private static final String ID_SEQ = "dummy_id_seq";
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = ID_SEQ)
    @GenericGenerator(name=ID_SEQ, strategy="sequence")

    @Column(name = "id", unique = true, nullable = false, insertable = true, updatable = false)
    private Integer id;

    @Column(name="field", nullable=false, length=8)
    private String field;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getField() {
        return field;
    }
    public void setField(String field) {
        this.field = field;
    }
}


@Entity
@Table(name = "ACTOR")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="field", discriminatorType=DiscriminatorType.STRING)
abstract public class EntitySuperClass extends MappedSuperClass {


    @Column(name="description", nullable=false, length=8)
    private String description;

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

@Entity
@DiscriminatorValue("sub1")
public class Sub1Class extends EntitySuperClass {

}


@Entity
@DiscriminatorValue("sub2")
public class Sub2Class extends EntitySuperClass {

}


@Entity
public class ReferencingEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Integer id;

    @Column
    private Integer value;

    @ManyToOne
    private EntitySuperClass entitySuperClass;


    public Integer getId() {
        return id;
    }

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

    public Integer getValue() {
        return value;
    }

    public void setValue(Integer value) {
        this.value = value;
    }

    public EntitySuperClass getEntitySuperClass() {
        return entitySuperClass;
    }

    public void setEntitySuperClass(EntitySuperClass entitySuperClass) {
        this.entitySuperClass = entitySuperClass;
    }

}
Torosian answered 14/7, 2010 at 10:13 Comment(2)
But why do you want to expose the discriminator column? This column is typically an "hidden" implementation detail that you don't want to expose.Synonymous
The discriminator column has already exposed in MappedSuperClass which is a part of external package. A package I want to avoid modifying, if possible.Sidonie
B
16

In my project it is done this way:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "field", discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue("dummy")
public class EntitySuperClass {
    // here definitions go 
    // but don't define discriminator column here
}

@Entity
@DiscriminatorValue(value="sub1")
public class Sub1Class extends EntitySuperClass {
    // here definitions go
}

And it works. I think your problem is that you needlessly define discriminator field in your superclass definition. Remove it and it will work.

Binge answered 14/7, 2010 at 10:26 Comment(0)
G
13

In order to use a discriminator column as a normal property you should make this property read-only with insertable = false, updatable = false. Since you can't change MappedSuperClass, you need to use @AttributeOverride:

@Entity 
@Table(name = "ACTOR") 
@Inheritance(strategy=InheritanceType.SINGLE_TABLE) 
@DiscriminatorColumn(name="field", discriminatorType=DiscriminatorType.STRING) 

@AttributeOverride(name = "field", 
    column = @Column(name="field", nullable=false, length=8, 
        insertable = false, updatable = false))

abstract public class EntitySuperClass extends MappedSuperClass { 
    ...
}
Gown answered 14/7, 2010 at 11:10 Comment(0)
T
2

You can map a database column only once as read-write field (a field that has insertable=true and/or updatable=true) and any number times as read-only field (insertable=false and updatable=false). Using a column as @DiscriminatorColumn counts as read-write mapping, so you can't have additional read-write mappings.

Hibernate will set value specified in @DiscriminatorColumn behind the scenes based on the concrete class instance. If you could change that field, it would allow modifying the @DiscriminatorColumn field so that your subclass and value in the field may not match.

Torosian answered 15/7, 2010 at 6:12 Comment(0)
N
-1

One fundamental: You effectively should not need to retrieve your discriminator column from DB. You should already have that information within the code, of which you use in your @DiscriminatorValue tags. If you need read that from DB, reconsider carefully the way you are assigning discriminators.

If you need it in final entity object, one good practice can be to implement an Enum from discriminator value and return store it in a @Transient field:

@Entity
@Table(name="tablename")
@DiscriminatorValue(Discriminators.SubOne.getDisc())
public class SubClassOneEntity extends SuperClassEntity {

    ...

    @Transient
    private Discriminators discriminator;

    // Setter and Getter
    ...
}

public enum Discriminators {
     SubOne ("Sub1"),
     SubOne ("Sub2");

     private String disc;
     private Discriminators(String disc) { this.disc = disc; }
     public String getDisc() { return this.disc; }
}
Nuzzi answered 22/11, 2013 at 20:6 Comment(1)
I don't think that calling a method from within an annotation would work in Java.Mainland

© 2022 - 2024 — McMap. All rights reserved.