Hibernate recursive many-to-many association with the same entity Hibernate recursive many-to-many association with the same entity java java

Hibernate recursive many-to-many association with the same entity


@ManyToMany to self is rather confusing because the way you'd normally model this differs from the "Hibernate" way. Your problem is you're missing another collection.

Think of it this way - if you're mapping "author" / "book" as many-to-many, you need "authors" collection on Book and "books" collection on Author. In this case, your "User" entity represents both ends of a relationship; so you need "my friends" and "friend of" collections:

@ManyToMany@JoinTable(name="tbl_friends", joinColumns=@JoinColumn(name="personId"), inverseJoinColumns=@JoinColumn(name="friendId"))private List<User> friends;@ManyToMany@JoinTable(name="tbl_friends", joinColumns=@JoinColumn(name="friendId"), inverseJoinColumns=@JoinColumn(name="personId"))private List<User> friendOf;

You can still use the same association table, but note that join / inverseJon columns are swapped on collections.

The "friends" and "friendOf" collections may or may not match (depending on whether your "friendship" is always mutual) and you don't have to expose them this way in your API, of course, but that's the way to map it in Hibernate.


The accepted answer seems overly complicated with the @JoinTable annotations. A slightly simpler implementation needs only a mappedBy. Using mappedBy indicates the owning Entity, or property, which should probably be the referencesTo since that would be considered the "friends". A ManyToMany relationship can create a very complicated graph. Using mappedBy makes the code as so:

@Entitypublic class Recursion {    @Id @GeneratedValue    private Integer id;    // what entities does this entity reference?    @ManyToMany    private Set<Recursion> referencesTo;    // what entities is this entity referenced from?    @ManyToMany(mappedBy="referencesTo")    private Set<Recursion> referencesFrom;    public Recursion init() {        referencesTo = new HashSet<>();        return this;    }    // getters, setters}

And to use it you need to consider the owning property is the referencesTo. You only need to put relationships in that property in order for them to be referenced. When you read an Entity back, assuming you do a fetch join, JPA will create the collections for the result. When you delete an Entity, JPA will delete all the references to it.

tx.begin();Recursion r0 = new Recursion().init();Recursion r1 = new Recursion().init();Recursion r2 = new Recursion().init();r0.getReferencesTo().add(r1);r1.getReferencesTo().add(r2);em.persist(r0);em.persist(r1);em.persist(r2);tx.commit();// required so that existing entities with null referencesFrom will be removed from cache.em.clear();for ( int i=1; i <= 3; ++i ) {    Recursion r = em.createQuery("select distinct r from Recursion r left join fetch r.referencesTo left join fetch r.referencesFrom where id = :id", Recursion.class).setParameter("id",  i).getSingleResult();    System.out.println(r + " To=" + Arrays.toString(r.getReferencesTo().toArray()) + " From=" + Arrays.toString(r.getReferencesFrom().toArray()) );}tx.begin();em.createQuery("delete from Recursion where id = 2").executeUpdate();tx.commit();// required so that existing entities with referencesTo will be removed from cache.em.clear();Recursion r = em.createQuery("select distinct r from Recursion r left join fetch r.referencesTo left join fetch r.referencesFrom where id = :id", Recursion.class).setParameter("id",  1).getSingleResult();System.out.println(r + " To=" + Arrays.toString(r.getReferencesTo().toArray()) + " From=" + Arrays.toString(r.getReferencesFrom().toArray()) );

Which gives the following log output (always check the generated SQL statements):

Hibernate: create table Recursion (id integer not null, primary key (id))Hibernate: create table Recursion_Recursion (referencesFrom_id integer not null, referencesTo_id integer not null, primary key (referencesFrom_id, referencesTo_id))Hibernate: create sequence hibernate_sequence start with 1 increment by 1Hibernate: alter table Recursion_Recursion add constraint FKsi0wfuwfs0bl19jjpofw4n8pt foreign key (referencesTo_id) references RecursionHibernate: alter table Recursion_Recursion add constraint FKarrkuyh2v1j5qnlui2vbpl7tk foreign key (referencesFrom_id) references RecursionHibernate: call next value for hibernate_sequenceHibernate: call next value for hibernate_sequenceHibernate: call next value for hibernate_sequenceHibernate: insert into Recursion (id) values (?)Hibernate: insert into Recursion (id) values (?)Hibernate: insert into Recursion (id) values (?)Hibernate: insert into Recursion_Recursion (referencesFrom_id, referencesTo_id) values (?, ?)Hibernate: insert into Recursion_Recursion (referencesFrom_id, referencesTo_id) values (?, ?)Hibernate: select distinct recursion0_.id as id1_2_0_, recursion2_.id as id1_2_1_, recursion4_.id as id1_2_2_, references1_.referencesFrom_id as referenc1_3_0__, references1_.referencesTo_id as referenc2_3_0__, references3_.referencesTo_id as referenc2_3_1__, references3_.referencesFrom_id as referenc1_3_1__ from Recursion recursion0_ left outer join Recursion_Recursion references1_ on recursion0_.id=references1_.referencesFrom_id left outer join Recursion recursion2_ on references1_.referencesTo_id=recursion2_.id left outer join Recursion_Recursion references3_ on recursion0_.id=references3_.referencesTo_id left outer join Recursion recursion4_ on references3_.referencesFrom_id=recursion4_.id where id=?model.Recursion@7bdf6bb7 To=[model.Recursion@1bc53649] From=[]Hibernate: select distinct recursion0_.id as id1_2_0_, recursion2_.id as id1_2_1_, recursion4_.id as id1_2_2_, references1_.referencesFrom_id as referenc1_3_0__, references1_.referencesTo_id as referenc2_3_0__, references3_.referencesTo_id as referenc2_3_1__, references3_.referencesFrom_id as referenc1_3_1__ from Recursion recursion0_ left outer join Recursion_Recursion references1_ on recursion0_.id=references1_.referencesFrom_id left outer join Recursion recursion2_ on references1_.referencesTo_id=recursion2_.id left outer join Recursion_Recursion references3_ on recursion0_.id=references3_.referencesTo_id left outer join Recursion recursion4_ on references3_.referencesFrom_id=recursion4_.id where id=?model.Recursion@1bc53649 To=[model.Recursion@42deb43a] From=[model.Recursion@7bdf6bb7]Hibernate: select distinct recursion0_.id as id1_2_0_, recursion2_.id as id1_2_1_, recursion4_.id as id1_2_2_, references1_.referencesFrom_id as referenc1_3_0__, references1_.referencesTo_id as referenc2_3_0__, references3_.referencesTo_id as referenc2_3_1__, references3_.referencesFrom_id as referenc1_3_1__ from Recursion recursion0_ left outer join Recursion_Recursion references1_ on recursion0_.id=references1_.referencesFrom_id left outer join Recursion recursion2_ on references1_.referencesTo_id=recursion2_.id left outer join Recursion_Recursion references3_ on recursion0_.id=references3_.referencesTo_id left outer join Recursion recursion4_ on references3_.referencesFrom_id=recursion4_.id where id=?model.Recursion@42deb43a To=[] From=[model.Recursion@1bc53649]Hibernate: delete from Recursion_Recursion where (referencesTo_id) in (select id from Recursion where id=2)Hibernate: delete from Recursion_Recursion where (referencesFrom_id) in (select id from Recursion where id=2)Hibernate: delete from Recursion where id=2Hibernate: select distinct recursion0_.id as id1_2_0_, recursion2_.id as id1_2_1_, recursion4_.id as id1_2_2_, references1_.referencesFrom_id as referenc1_3_0__, references1_.referencesTo_id as referenc2_3_0__, references3_.referencesTo_id as referenc2_3_1__, references3_.referencesFrom_id as referenc1_3_1__ from Recursion recursion0_ left outer join Recursion_Recursion references1_ on recursion0_.id=references1_.referencesFrom_id left outer join Recursion recursion2_ on references1_.referencesTo_id=recursion2_.id left outer join Recursion_Recursion references3_ on recursion0_.id=references3_.referencesTo_id left outer join Recursion recursion4_ on references3_.referencesFrom_id=recursion4_.id where id=?model.Recursion@6b739528 To=[] From=[]


Actually its very simple and could be achieved by following say you have following entity

public class Human {    int id;    short age;    String name;    List<Human> relatives;    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public short getAge() {        return age;    }    public void setAge(short age) {        this.age = age;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public List<Human> getRelatives() {        return relatives;    }    public void setRelatives(List<Human> relatives) {        this.relatives = relatives;    }    public void addRelative(Human relative){        if(relatives == null)            relatives = new ArrayList<Human>();        relatives.add(relative);    }}

HBM for same:

<hibernate-mapping>    <class name="org.know.july31.hb.Human" table="Human">        <id name="id" type="java.lang.Integer">            <column name="H_ID" />            <generator class="increment" />        </id>        <property name="age" type="short">            <column name="age" />        </property>        <property name="name" type="string">            <column name="NAME" length="200"/>        </property>        <list name="relatives" table="relatives" cascade="all">         <key column="H_ID"/>         <index column="U_ID"/>         <many-to-many class="org.know.july31.hb.Human" column="relation"/>      </list>    </class></hibernate-mapping>

And test case

import org.junit.Test;import org.know.common.HBUtil;import org.know.july31.hb.Human;public class SimpleTest {    @Test    public void test() {        Human h1 = new Human();        short s = 23;        h1.setAge(s);        h1.setName("Ratnesh Kumar singh");        Human h2 = new Human();        h2.setAge(s);        h2.setName("Praveen Kumar singh");        h1.addRelative(h2);        Human h3 = new Human();        h3.setAge(s);        h3.setName("Sumit Kumar singh");        h2.addRelative(h3);        Human dk = new Human();        dk.setAge(s);        dk.setName("D Kumar singh");        h3.addRelative(dk);        HBUtil.getSessionFactory().getCurrentSession().beginTransaction();        HBUtil.getSessionFactory().getCurrentSession().save(h1);        HBUtil.getSessionFactory().getCurrentSession().getTransaction().commit();        HBUtil.getSessionFactory().getCurrentSession().beginTransaction();        h1 = (Human)HBUtil.getSessionFactory().getCurrentSession().load(Human.class, 1);        System.out.println(h1.getRelatives().get(0).getName());        HBUtil.shutdown();    }}