4

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

Andrea Ligios
  • 49,480
  • 26
  • 114
  • 243
  • 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 – Neil Stockton Mar 08 '16 at 16:46
  • @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 – Andrea Ligios Mar 08 '16 at 16:49
  • 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. – K.Nicholas Mar 08 '16 at 17:55
  • 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 :/ – Andrea Ligios Mar 08 '16 at 21:31
  • I'll play around with it a bit more. – K.Nicholas Mar 09 '16 at 22:47

3 Answers3

1

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

Neil Stockton
  • 11,383
  • 3
  • 34
  • 29
1

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.

K.Nicholas
  • 10,956
  • 4
  • 46
  • 66
  • 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 – Andrea Ligios Mar 10 '16 at 08:22
  • I like your archetype... starred :) – Andrea Ligios Mar 10 '16 at 08:30
  • 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. – K.Nicholas Mar 10 '16 at 14:20
  • I suspected that, and we've opted for the 1) yesterday... I *hate* old projects (that are not gonna be rewritten :) – Andrea Ligios Mar 10 '16 at 14:45
  • Sorry to bother you @Nicholas... please take a look at this: http://stackoverflow.com/q/38095016/1654265 – Andrea Ligios Jun 29 '16 at 10:01
1

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

BigMichi1
  • 307
  • 1
  • 11