2016-08-10 12 views
11

Potrzebuję funkcji "FriendRequest" i "ListOfFriends". Podobnie jak Facebook, który pokazuje liczbę otrzymanych próśb przyjaciół i liczbę zatwierdzonych znajomych.Jak zdefiniować relację przyjaźni za pomocą Hibernacji?

Przez "FriendRequest", mam na myśli listę żądań znajomych, które otrzymuje użytkownik.

Przez "ListOfFriends" mam na myśli listę znajomych użytkownika.

Aby to osiągnąć, zdefiniowałem następujące klasy, ale gdy próbuję odzyskać użytkownika za pomocą jego nazwy użytkownika, zostanie wysłany komunikat wyjątku "Stackoverflow". Wygląda na to, że przechodzi w nieskończoną pętlę.

Po usunięciu "FriendRequests" i "Friends" z mojej metody toString przestaje wyrzucać wyjątek.

This question jest podobny do tego, co zamierzam osiągnąć, ale różnica jest Nie mam co mieć oddzielnej klasy Credential.

Użytkownik

@Entity 
public class Member implements Serializable { 
    @Id 
    @GeneratedValue 
    private long id; 
    @Column(name = "username", nullable = false, unique = true) 
    private String username; 
    @Column(nullable = false) 
    private String fname; 
    @Column(nullable = false) 
    private String lname; 
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "requester") 
    private Set<Friendship> friendRequests = new HashSet<Friendship>(); 
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "friend") 
    private Set<Friendship> friends = new HashSet<Friendship>(); 

    getters and setters 

    @Override 
    public String toString() { 
     return "Member [ 
       .....     
       , fname=" + fname + ", lname=" + lname 
       // + friendRequests.size() + ", friends=" + friends.size() + 
     "]"; 
    } 
} 

Przyjaźń

@Entity 
public class Friendship implements Serializable { 
    @Id 
    @ManyToOne 
    @JoinColumn(referencedColumnName = "username") 
    Member requester; 
    @Id 
    @ManyToOne 
    @JoinColumn(referencedColumnName = "username") 
    Member friend; 
    @Temporal(javax.persistence.TemporalType.DATE) 
    Date date; 
    @Column(nullable = false) 
    boolean active; 
+0

Najlepszym sposobem jest utrzymywanie kolejnego stołu z prośbą o przyjaciela, a dla przyjaźni istnieje tabela, która zawiera tylko ID nadawcy i odbiorcę. –

+0

Czy możesz tutaj umieścić swój kod testu? –

+0

@NiravChhatrola Nie mam jeszcze żadnego przypadku testowego. –

Odpowiedz

1

Nie rozumiem problem, który masz, choć nie bardzo niewiele zmian w kodzie, więc może pracować z moja konfiguracja.

/META-INF/persistence.xml

<?xml version="1.0" encoding="UTF-8"?> 
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> 
    <persistence-unit name="hibernate-entitymanager-demo" transaction-type="RESOURCE_LOCAL"> 
    <provider>org.hibernate.ejb.HibernatePersistence</provider> 
    <properties> 
     <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/hibernate4?zeroDateTimeBehavior=convertToNull"/> 
     <property name="javax.persistence.jdbc.password" value="root"/> 
     <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/> 
     <property name="javax.persistence.jdbc.user" value="root"/> 
     <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/> 
     <!-- property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/ --> 
    </properties> 
    </persistence-unit> 
</persistence> 

Main.java przypadki tutaj (dwa użytku: albo createFriendships zainicjować jakieś dane lub findMembers dla problematycznej przypadku użycia zwrócić uwagę. własność javax.persistence.schema-generation.database.action w persistence.xml jak będziemy chcieli, aby utworzyć bazę danych w pierwszym, ale nie w tym ostatnim)

public class Main { 

    public static void main(String[] args) { 
     EntityManagerFactory emf = Persistence.createEntityManagerFactory("hibernate-entitymanager-demo"); 
     EntityManager em = emf.createEntityManager(); 

     EntityTransaction transaction = em.getTransaction(); 
     transaction.begin(); 

     // createFriendships(em); 

     findMembers(em); 

     transaction.commit(); 

     em.close(); 
     emf.close(); 
    } 

    private static void findMembers(EntityManager em) { 
     List<Member> list = em.createQuery("from Member").getResultList(); 
     for (Member m : list) { 
      System.out.println(m); 
     } 
    } 

    private static void createFriendships(EntityManager em) { 
     List<Member> members = createMembers(em); 

     for (int i = 0; i < members.size(); i++) { 
      for (int j = 0; j < members.size(); j++) { 
       if (i != j) { 
        createFriendship(em, members.get(i), members.get(j)); 
       } 
      } 
     } 
    } 

    private static List<Member> createMembers(EntityManager em) { 
     List<Member> members = new ArrayList<>(); 
     members.add(createMember(em, "Roberta", "Williams", "rwilliams")); 
     members.add(createMember(em, "Ken", "Williams", "kwilliams")); 
     members.add(createMember(em, "Dave", "Grossman", "dgrossman")); 
     members.add(createMember(em, "Tim", "Schafer", "tschafer")); 
     members.add(createMember(em, "Ron", "Gilbert", "rgilbert")); 
     return members; 
    } 

    private static Member createMember(EntityManager em, String fname, String lname, String username) { 
     Member m = new Member(); 
     m.setFname(fname); 
     m.setLname(lname); 
     m.setUsername(username); 
     em.persist(m); 
     return m; 
    } 

    private static void createFriendship(EntityManager em, Member requester, Member friend) { 
     Friendship f = new Friendship(); 
     f.setActive(true); 
     f.setDate(new Date()); 
     f.setRequester(requester); 
     f.setFriend(friend); 
     em.persist(f); 
    } 

} 

Główne produkuje:

Member [fname = Roberta, lname = Williams, requests = 4, friends = 4] 
Member [fname = Ken, lname = Williams, requests = 4, friends = 4] 
Member [fname = Dave, lname = Grossman, requests = 4, friends = 4] 
Member [fname = Tim, lname = Schafer, requests = 4, friends = 4] 
Member [fname = Ron, lname = Gilbert, requests = 4, friends = 4] 

Friendship.java Prawdziwy rzeczywista zmiana był określany nazwą kolumny, które zostały zmienione od username do id jak mam could not get a field value by reflection rodzaju wyjątku. Obok myślę, że lepiej w zakresie normalizacji bazy danych:

@Entity 
@Table(name = "FRIENDSHIPS") 
public class Friendship implements Serializable { 

    @Id 
    @ManyToOne 
    @JoinColumn(referencedColumnName = "id") 
    Member requester; 

    @Id 
    @ManyToOne 
    @JoinColumn(referencedColumnName = "id") 
    Member friend; 

    @Temporal(javax.persistence.TemporalType.DATE) 
    Date date; 

    @Column(nullable = false) 
    boolean active; 

    // getters & setters 

} 

Member.java (tu bez zmian, z wyjątkiem toString())

@Entity 
@Table(name = "MEMBERS") 
public class Member implements Serializable { 

    @Id 
    @GeneratedValue 
    private long id; 

    @Column(name = "username", nullable = false, unique = true) 
    private String username; 

    @Column(nullable = false) 
    private String fname; 

    @Column(nullable = false) 
    private String lname; 

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "requester") 
    private Set<Friendship> friendRequests = new HashSet<>(); 

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "friend") 
    private Set<Friendship> friends = new HashSet<>(); 

    // getters & setters 

    @Override 
    public String toString() { 
     return "Member [fname = " + fname + ", lname = " + lname 
       + ", requests = " + friendRequests.size() 
       + ", friends = " + friends.size() + "]"; 
    } 

} 
+0

Dzięki, spróbuję tego. Właśnie przeformułowałem pytanie, ponieważ było niejasne. –

+0

Zastosowałem zmiany, o których wspomniałeś, kiedy tabele są puste, żaden wyjątek nie zostanie zgłoszony, ale kiedy wstawię nowy wiersz do tabeli przyjaźni, kod zgłasza wyjątek Stackoverflow. –

1

I emulowane swoją sytuację z najnowszej wersji Hibernate 5.2.2.Final. Dostaję ten sam wyjątek. Próbowałem zastąpić EAGER pobieraniem do LAZY i zaczęło działać poprawnie. StackOverFlowError oznacza, że ​​Hibernacja nie rozpoznaje obiektu w cyklicznej zależności i rozpoczyna się pobieranie kolejnych wierszy, tworząc nowe i nowe obiekty, dopóki nie zniknie Stack Memory.

Sane sytuacja:

Member[1] --ref-- Friendship[1] --ref-- Member[1] --ref-- ... 
(Here Hibernate recognizes cyclic dependency and creates only two objects) 

sytuacja Bug (w naszym przypadku):

Member[1] --ref-- Friendship[1] --ref-- Member[1] --ref-- ... 
(Here Hibernate doesn't recognize cyclic dependency and creates plenty of objects until Stack Memory licks, which cause StackOverFlowError) 

Googling i asking dał mi taką odpowiedź:

Wszystkie JPA dostawcy powinni być w stanie radzić sobie ze sporym obciążeniem cyklicznych relacji . Każdy problem powinien oznaczać zgłoszenie błędu wobec dostawcy JPA .

- Neil Stockton

Pierwsze rozwiązanie:

więc proponuję korzystania LAZY inicjalizacji. I obsłużyć operacji pobierania ręcznie przez FETCH JOIN. Zobacz proszę ten answer.

drugie rozwiązanie:

unikać odwołań cyklicznych. Jeśli głównym obiektem, który najpierw wywołasz, jest Friendship, a następnie skomentuj obiekty Przyjaźń w obiekcie, aby Friendship pobrał Member, a Członek nie będzie wiedział nic o Friendship. To działa dla mnie, testowałem.

Niestety, architektura nie pozwala na odwrotność od Friendship doesn't know about Member, ponieważ element członkowski jest kluczem podstawowym. Proponuję zmienić swoją architekturę, to bardzo silne sprzężenie - zła praktyka programowania.


Referencje:

+0

To rozwiązanie zadziała w przypadku powyższego problemu. –

3

Chciałbym zaproponować projekt DB.

W tej chwili masz tabelę przyjaźni i tabelę znajomych, jak na zajęciach POJO. Zamiast tego, możesz po prostu mieć tylko tabelę przyjaźni z jeszcze jedną kolumną boolean isAccepted, (zmienną w klasie POJO).

Jeśli wartość logiczna jest prawdziwa, oznacza to, że ten członek (przyjaciel) jest przyjacielem. Gdy chcesz zdobyć wszystkich przyjaciół, pobierz wiersze przyjaźni z ustawieniem isAccepted na true; Jeśli chcesz otrzymywać tylko zaproszenia od znajomych (lub oczekujące żądania do zatwierdzenia), ustaw wszystkie wiersze, dla których wartość jest ustawiona na wartość "Ustaw" na "fałsz".

Przy obecnym projekcie DB lub modelu ER, którego używasz.Musisz usunąć wiersz z tabeli przyjaźni (ponieważ nie jest to już prośba od znajomego), jeśli dana osoba zaakceptuje prośbę o dodanie do znajomych i gdzieś ją zapisze. Gdzie przechowujesz przyjaciół. Zgodnie z obecnym projektem, Przechowuje w tabeli przyjaciół. Ale nie ma kolumny mówiącej, że wiersz wskazuje na kogoś znajomego. To tak jak dodawanie wiersza (przyjaciela) do tabeli znajomych za każdym razem, gdy jeden członek akceptuje prośbę innego członka i po prostu tworzy nadmiarowe wiersze.

0

W rzeczywistości różnica między pozostałymi pytaniami polega nie tylko na porzuceniu obiektu Credentials. Twoja klasa członkowskie ma relacje wsteczne z przyjaźniami, co powoduje cykliczne zależności. W drugim przykładzie obiektem "root" jest przyjaźń, która łączy się z członkami. Jeśli chcesz, aby członek był "rootem", powinieneś zaprojektować inny model.

Powiązane problemy