Хибернация на рекурсивна асоциация много към много със същия обект

Още един въпрос за Hibernate... :P

Използвайки рамката за пояснения на Hibernate, имам User обект. Всеки User може да има колекция от приятели: колекция от други User. Въпреки това не успях да разбера как да създам асоциация много към много в рамките на класа User, състоящ се от списък от Users (използвайки междинна таблица потребител-приятели).

Ето потребителския клас и неговите анотации:

@Entity
@Table(name="tbl_users")
public class User {

    @Id
    @GeneratedValue
    @Column(name="uid")
    private Integer uid;

    ...

    @ManyToMany(
            cascade={CascadeType.PERSIST, CascadeType.MERGE},
            targetEntity=org.beans.User.class
    )
    @JoinTable(
            name="tbl_friends",
            joinColumns=@JoinColumn(name="personId"),
            inverseJoinColumns=@JoinColumn(name="friendId")
    )
    private List<User> friends;
}

Таблицата за съпоставяне на потребителя има само две колони, като и двете са външни ключове към колоната uid на таблицата tbl_users. Двете колони са personId (която трябва да съответства на текущия потребител) и friendId (която указва идентификатора на приятеля на текущия потребител).

Проблемът е, че полето „приятели“ продължава да излиза нулево, въпреки че предварително съм попълнил таблицата с приятели, така че всички потребители в системата да са приятели с всички останали потребители. Дори се опитах да превключа връзката към @OneToMany и тя все още излиза нула (въпреки че изходът за отстраняване на грешки в Hibernate показва SELECT * FROM tbl_friends WHERE personId = ? AND friendId = ? заявка, но нищо друго).

Някакви идеи как да попълня този списък? Благодаря ти!


person Magsol    schedule 01.11.2009    source източник


Отговори (3)


@ManyToMany to self е доста объркващо, защото начинът, по който обикновено моделирате това, се различава от начина на „хибернация“. Проблемът ви е, че ви липсва друга колекция.

Помислете за това по следния начин - ако картографирате "автор" / "книга" като много към много, имате нужда от колекция "автори" на Книга и колекция "книги" на Автор. В този случай вашият обект "Потребител" представлява двата края на връзка; така че имате нужда от колекции „моите приятели“ и „приятел на“:

@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;

Все още можете да използвате същата таблица за асоцииране, но имайте предвид, че колоните за присъединяване / обратен Jon се разменят в колекциите.

Колекциите „friends“ и „friendOf“ могат или не могат да съвпадат (в зависимост от това дали вашето „приятелство“ винаги е взаимно) и не е нужно да ги излагате по този начин във вашия API, разбира се, но това е начинът за картографиране в Hibernate.

person ChssPly76    schedule 01.11.2009
comment

Потребителската регистрация и потребителското удостоверяване обикновено не са проблем. Наличието на много потребители, които са влезли, обаче може да бъде проблем. Drupal не прави много кеширане за влезли потребители. Причината е, че страниците ще изглеждат малко по-различно за всеки потребител, когато показват специфични за потребителя неща. Можете да кеширате части от страница, която е еднаква за всички, за да намалите натоварването. Самият аз нямам опит с него, но съм чувал за настройка, която го е направила. Да направите това обаче няма да е толкова лесно.

- person Magsol; 01.11.2009
comment
Мога ли да комбинирам приятели и friendOf? И ако не, тогава имам нужда от помощ с jsonmanagedreference и jsonbackreference в един обект в този въпрос: stackoverflow.com/questions/24563066/ - person dikkini; 05.07.2014
comment
Използвам Hibernate 5 и една колекция е достатъчна - използването на две създава дублирани редове в tbl_friends. - person mdziob; 26.05.2017
comment
Как мога да имам уникално ограничение в tbl_friends, използвайки както friendId, така и personId? - person Tayfun Yaşar; 07.04.2018
comment
Мисля, че си струва да добавим, че ако използвате една и съща таблица, една от тях трябва да има mappedBy и да няма @JoinTable. friendOf например: @ManyToMany(mappedBy = "friends"). В противен случай hibernate ще се опита да запази и двете и ще доведе до java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'x-y' for key 'PRIMARY' - person Lucas Noetzold; 12.09.2019

Приетият отговор изглежда прекалено сложен с анотациите @JoinTable. Малко по-проста реализация се нуждае само от mappedBy. Използването на mappedBy показва притежаващия Entity или собственост, която вероятно трябва да бъде referencesTo, тъй като това ще се счита за "приятели". Връзката ManyToMany може да създаде много сложна графика. Използването на mappedBy прави кода така:

@Entity
public 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
}

И за да го използвате, трябва да имате предвид, че притежаваният имот е referencesTo. Трябва само да поставите връзки в това свойство, за да могат да бъдат препращани. Когато прочетете Entity обратно, ако приемем, че направите fetch join, JPA ще създаде колекциите за резултата. Когато изтриете обект, JPA ще изтрие всички препратки към него.

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()) );

Което дава следния лог изход (винаги проверявайте генерираните SQL изрази):

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 1
Hibernate: alter table Recursion_Recursion add constraint FKsi0wfuwfs0bl19jjpofw4n8pt foreign key (referencesTo_id) references Recursion
Hibernate: alter table Recursion_Recursion add constraint FKarrkuyh2v1j5qnlui2vbpl7tk foreign key (referencesFrom_id) references Recursion
Hibernate: call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
Hibernate: 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=2
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@6b739528 To=[] From=[]
person K.Nicholas    schedule 13.06.2018
comment
Този отговор трябва да бъде приетото, просто решение с 1 параметър. Той създава точно това, което бихте очаквали в насочена графика. Уникална таблица на отношенията, представляваща ръбове от a до b, като се избягва дублиращата се информация в отделни колони. - person Simplicity's_Strength; 21.07.2021

Всъщност това е много просто и може да се постигне чрез следване, да кажем, че имате следващ обект

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 за същото:

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

И тестов случай

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();
    }

}
person rt.jar    schedule 01.08.2013