简体   繁体   中英

Hibernate: Referencing multiple tables through a single join table

Say I have some table A each of which has many B 's each of which has many C 's and so on for however many levels.

Now there is also some table X which is positioned at some level in this hierarchy (eg a particular X is associated with a B and thus also an A , but not a C or a D ).

I am trying to maintain this relationship via a join table, call it hierarchy_mapping , which has columns: a_id , b_id , c_id , and so on where all but one are not null and insertions into X always also inserts into hierarchy_mapping , given an existing a_id , b_id , or c_id .

First of all, is this is a proper way to model such a hierarchy where X can be inserted at any level of this hierarchy?

Secondly, obviously, my implementation below doesn't work with Hibernate and I'm not entirely sure why. It works fine when the entity definition for X only references one of A , B , or C via the join table hierarchy_mapping (ie if I removed the member variables b and c from the POJO definition below):

@Entity
@Table(name = "X")
public class X {
    @Id
    @Column(name = "x_id")
    private Integer xId;

    @JoinTable(
            name = "hierarchy_mapping",
            joinColumns = @JoinColumn(name = "x_id", referencedColumnName = "x_id"),
            inverseJoinColumns = @JoinColumn(name = "a_id", referencedColumnName = "a_id")
    )
    @OneToOne(fetch = FetchType.LAZY)
    private A a;

    @JoinTable(
            name = "hierarchy_mapping",
            joinColumns = @JoinColumn(name = "x_id", referencedColumnName = "x_id"),
            inverseJoinColumns = @JoinColumn(name = "b_id", referencedColumnName = "b_id")
    )
    @OneToOne(fetch = FetchType.LAZY)
    private B b;

    @JoinTable(
            name = "hierarchy_mapping",
            joinColumns = @JoinColumn(name = "x_id", referencedColumnName = "x_id"),
            inverseJoinColumns = @JoinColumn(name = "c_id", referencedColumnName = "c_id")
    )
    @OneToOne(fetch = FetchType.LAZY)
    private C c;
}

EDIT: I see that with my implementation above, when I query for a single X via a JpaRepository implementation, Hibernate performs a left outer join as such:

SELECT x.x_id, x.name, .., m.b_id FROM TABLE_X x LEFT OUTER JOIN hierarchy_mapping m ON x.x_id = m.x_id WHERE x.name = ?

To note is that it for some reason it only selects b_id from hierarchy_mapping and doesn't also select a_id and c_id as well. Then the most important thing is that Hibernate is not performing a subsequent query like it normally does to get the information from the B table for the row associated with b_id so that it could populate that member variable b in the instance of X that is returned. (eg a subsequent query like SELECT b.b_id, b.description, .. FROM TABLE_B b where b._id = ? )

Why are both of these happening?

I am not telling what is the best way to accomplish what you are trying since the actual problem you are trying to solve is not clear at all.

This solution is based on assumption that with this X you just want to attach some generic data to any of A,B ... Z

Looking at your code I would suggest you to consider JPA inheritance to have this working and to have code more clear and manageable (well, at least in my opinion).

Create a base entity that A,B ... Z inherit.

@Entity
@Getter
@Inheritance(strategy=InheritanceType.JOINED) // needs to be joined to work
public class Alphabet {
   @Id @GeneratedValue
   private Long id;
   // X is here to have relationship bi-directional
   // leave it out if not needed
   @OneToOne(mappedBy="alphabet", cascade=CascadeType.PERSIST) 
   @Setter
   private X x;
}

Then let every entity in A,B ... Z to inherit this and make the needed other relationships, for example A & B

Entity A

@Entity
@Getter
public class A extends Alphabet {   
   private Character character = 'A'; // just some field, add the needed fields

   @OneToMany(cascade=CascadeType.PERSIST, mappedBy="a")
   private Collection<B> bs = new ArrayList<>();
}

Entity B

@Entity
@Getter
public class B extends Alphabet {
   private Character character = 'B';

   @ManyToOne(cascade=CascadeType.PERSIST)
   @Setter
   private A a;

}

Then simply :

Entity X

@Entity
@Table(name="hierarchy_mapping")
@Getter
public class X {

   @Id @GeneratedValue
   private Long id;

   @OneToOne(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
   @Setter
   private Alphabet alphabet;

   // and possible other data you want to add in your `X`
}

Example :

A a = new A();
B b = new B();
b.setA(a);
a.getBs().add(b);   

X xa = new X(), xb = new X();

xa.setAlphabet(a);
a.setX(xa); 

xb.setAlphabet(b);
b.setX(xb);

em.persist(a);

When setting the alphabet there is no need to think if it is A , D or Q . Just setAlphabet(..)

When getting the alphabet you could create a utility method to X like

@SuppressWarnings("unchecked")
public <T> T getAlphabet(Class<T> classT) {
    Alphabet at = getAlphabet();
    if(null!=at && classT.isAssignableFrom(at.getClass()))
        return (T)at;
    return null;
}

Above method can be used like

B b = x.getAlphabet(B.class);

You anyway need to know what you are digging from X . And if it does not exist you will get null instead.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM