Why is @DiscriminatorColumn ignored when using @JoinTable?

discriminatorformula
how to get discriminator value from hibernate
discriminator column sql
jpa query discriminator column
using default @discriminatorvalue for a discriminator of type char is not safe
discriminatorvalue multiple values
spring data jpa find by discriminator
mappedsuperclass vs embeddable

Suppose I have the following tables:

 ______________________
|       LAWSUIT        |
|______________________|
| ID                   |
| TITLE                |
|______________________|
                        \
                         \
                          \
                             ______________________
                            | PERSONS_IN_THE_CASE  |
                            |______________________|
                            | ID_LAWSUIT           |
                            | ID_PERSON            |
                            |______________________|
                          /
                         /
                        /
 ______________________
|        PERSON        |
|______________________|
| ID                   |
| NAME                 |
| TYPE                 | TYPE values = "Plantiff, Defendant, Lawyer, Judge, ..."
|______________________|

(I know that normalizing the database I could have a table for each person type, but let's stick to the actual structure)

I've subclassed the different Persons with JPA (2.0) as follows:

@Entity
@Table(name="PERSON")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "TYPE")
public abstract class Person {
@Entity
@DiscriminatorValue("Plantiff")
public class Plantiff extends Person {  
@Entity
@DiscriminatorValue("Defendant")
public class Defendant extends Person { 
@Entity
@DiscriminatorValue("Lawyer")
public class Lawyer extends Person {    

This works correctly, because I can query a single category and the filter is automatic, eg findAll on Plantiff will get all the Plantiffs of all times.

Now I'm trying to link them to the Lawsuit through the PERSONS_IN_THE_CASE @JoinTable:

@Entity
@Table(name="LAWSUIT")
public class Lawsuit {

    @Id
    Long id;

    String title;

    @OneToMany
    @JoinTable(name="PERSONS_IN_THE_CASE",
               joinColumns=@JoinColumn(name="ID_LAWSUIT", referencedColumnName="ID"),
        inverseJoinColumns=@JoinColumn(name="ID_PERSON",  referencedColumnName="ID"))
    Plantiff plantiff;


    @ManyToMany
    @JoinTable(name="PERSONS_IN_THE_CASE",
               joinColumns=@JoinColumn(name="ID_LAWSUIT", referencedColumnName="ID"),
        inverseJoinColumns=@JoinColumn(name="ID_PERSON",  referencedColumnName="ID"))
    Set<Defendant> defendants;

    ...
}

Here is where things break: the @DiscriminatorValue is not applied, it seems to be completely bypassed by the @JoinTable.

In fact, the Set<Defendant> does not contain only the defendants, but every person in the case (every record in the PERSONS_IN_THE_CASE relational table. For example, if I have a Plantiff, a Judge, two Defendants and two Lawyers, the above Set<Defendant> will contain 6 persons instead of 2).

How can I make the @DiscriminatorValue working through the @JoinTable binding ?

EDIT: I'm using Hibernate 3.6.6.Final, and (although I always try to avoid it) I'm open to vendor-specific solutions, like @ForceDiscriminator(deprecated), @DiscriminatorOptions(force=true) and so on. I've obviosuly tried before asking (without being able to make it work, though).

There is no way in standard JPA to share a join table, and have an extra column in the join table as a type of distinguisher as to which relation it is for.

You could use a separate join table, or dig into vendor extensions that your JPA provider supports; I know that DataNucleus JPA does allow something like what you need (see this doc, for JDO, but it also works for JPA) and would expect that other providers maybe have some way of doing this - but you then make your app non-portable in terms of JPA provider.

Alternatively redesign the relations, if that is an option for your project

The best way to map the @DiscriminatorColumn with JPA and , The best way to map the @DiscriminatorColumn with JPA and Hibernate. Last modified: Jan 22, 2019 //Getters and setters omitted for brevity. The strategy and the discriminator column are only specified in the root of an entity class hierarchy or subhierarchy in which a different inheritance strategy is applied If the DiscriminatorColumn annotation is missing, and a discriminator column is required, the name of the discriminator column defaults to "DTYPE" and the discriminator type

In short, it seems like @DiscriminatorColumn is not meant to have your hierarchy distinguished for you. In other words, the discriminator column worked at a simple level if everything went into one list. As soon as I started trying to ask it to separate the Entities into separate lists then I stated having trouble, as you did.

I tried essentially what you described without the extra JoinColumn annotations, and I couldn't insert because the join table that was created had two separate ids in it, one for one class, one for another, and any insert could not satisfy both at the same time. With the extra annotations to control the join configuration, there was still issues. I could get things inserted, but not retrieved.

Basically, you could look at @MappedSuperclass as a solution to your design considerations. A Person would be the MappedSuperclass Entity, which would be abstract, and Lawyers, Judges, Plaintiffs, Defendants, etc., would be concrete subclasses.

@MappedSuperclass
public abstract class Content
{
   @Id @GeneratedValue private Integer  id;

   public Integer getId() {
       return id;
   }

}

and as Conrete classes ...

@Entity
public class EnglishContent extends Content {

    @Override
    public String toString() {
        return "EnglishContent:"+getId();
    }
}

and

@Entity
public class SpanishContent extends Content {

    @Override
    public String toString() {
        return "SpanishContent:"+getId();
    }
}

and something to hold them:

@Entity
public class Question {
    @Id @GeneratedValue private Integer id;

    @OneToMany(cascade=CascadeType.ALL)
    List<EnglishContent> englishContents;

    @OneToMany(cascade=CascadeType.ALL)
    List<SpanishContent> spanishContents;
    ... getters, setters, 
}

and this test works OK.

    Question q = new Question();

    SpanishContent sc = new SpanishContent();
    List<SpanishContent> spanishContents = new ArrayList<SpanishContent>();
    spanishContents.add(sc);
    q.setSpanishContents(spanishContents);

    EnglishContent ec = new EnglishContent();
    List<EnglishContent> englishContents = new ArrayList<EnglishContent>();
    englishContents.add(ec);
    q.setEnglishContents(englishContents);

    em.persist(q);

and

    Question q = em.find(Question.class, 1);
    System.out.println(q);

So, not the same classes, but it gives you separate lists, has polymorphism, and seems like a cleaner design anyway. Of course, there are more database tables, but that shouldn't be a major consideration.

Hope this helps.

JPA + Hibernate - Joined Subclass Inheritance Strategy, The annotation @Inheritance is used on the root entity class with strategy = InheritanceType.JOINED . @DiscriminatorColumn is used on the� The @JoinTable annotation is a powerful feature of the Hibernate, it can be applicable to many situations to make your life easier. It can be used with two tables and with three, the last case I have demonstrated in this tutorial.

it is still ignored in hibernate 5.4.2, our work around is using the @Where annotation from hibernate, still not optimal

Complete Guide: Inheritance strategies with JPA and Hibernate, Without the @MappedSuperclass annotation, Hibernate will ignore the mapping information of your superclass. @JoinTable(name = “PublicationAuthor”, joinColumns You can either define the column name with a @ DiscriminatorColumn� 1. Many-to-many table + extra columns in join table. The STOCK and CATEGORY many to many relationship is linked with a third / join table named STOCK_CATEGORY, with extra “created_by” and “created_date” columns.

DiscriminatorColumn (hibernate-jpa-2.1-api 1.0.0.Final API), Specifies the discriminator column for the SINGLE_TABLE and JOINED Inheritance mapping strategies. Ignored for other discriminator types. Default: 31. The strategy and the discriminator column are only specified in the root of an entity class hierarchy or subhierarchy in which a different inheritance strategy is applied If the DiscriminatorColumn annotation is missing, and a discriminator column is required, the name of the discriminator column defaults to "DTYPE" and the discriminator type

Chapter 2. Mapping Entities, It also defines the discriminator column through the @DiscriminatorColumn annotation, non annotated with @MappedSuperclass nor @Entity will be ignored. This association table described by the @JoinTable annotation will contains a� The strategy and the discriminator column are only specified in the root of an entity class hierarchy or subhierarchy in which a different inheritance strategy is applied If the DiscriminatorColumn annotation is missing, and a discriminator column is required, the name of the discriminator column defaults to "DTYPE" and the discriminator type

javax.persistence.DiscriminatorColumn - JPA annotation, (Optional) The column length for String-based discriminator types. Ignored for other discriminator types. Default value: 31. So, it’s trying to use DTYPE (hibernate’s default) as the discriminator column rather than DISCRIMINATOR. Any idea why its ignoring the column specified in the orm.xml and using the hibernate default? When I change the SQL to use DTYPE rather than DISCRIMINATOR, all works fine.

Comments
  • You are trying to REUSE a join table for 2 different things; that will end up with a mess since it doesn't know from the join table which relation it is for, so will misinterpret things. Use a separate join table. Some JPA implementations may support shared join tables (DataNucleus does IIRC, not used that feature in some time), but this is NOT part of the JPA spec
  • @NeilStockton Thank you. In fact, PERSONS_IN_THE_CASE has also a column ROLE that specifies what that Person is doing in that Case. Could it help in some way ? This is an old project (and database) we're now migrating to JPA, it would be better to not change completely the database if possible
  • I haven't been able to make it work exactly either, I get "loaded object was of wrong class class" from hibernate because of its confusion. As an aside, consider that in a class action suit there can be more than one plaintiff, or joinders, or probably other reasons.
  • Sure, the lawsuit is only a fake example, my software is not even near that, but thanks. I'm using Hibernate too (forgot to mention it in the question) and I'm getting your same error :/
  • I'll play around with it a bit more.
  • Thank you Nicholas, that's another way. Our problem is that we can't almost change the database (we can add tables for new usecases, but we can't alter main areas like this one), because other parts (that we can't touch) of the huge application are based on them. I agre that on a new project, this would be a better design
  • I like your archetype... starred :)
  • Well, it seems you may have to two poor choices: 1) to put them all in the same list and use Java to discriminate them into separate lists or, 2) read the result sets yourself, which seems like the same thing.
  • I suspected that, and we've opted for the 1) yesterday... I hate old projects (that are not gonna be rewritten :)
  • Sorry to bother you @Nicholas... please take a look at this: stackoverflow.com/q/38095016/1654265