Как определить отношения дружбы с помощью Hibernate?

Мне нужно иметь функции "FriendRequest" и "ListOfFriends". Подобно facebook, в котором показано количество полученных запросов друзей и количество одобренных друзей.

Под "FriendRequest" я хочу иметь список запросов друзей, которые пользователь получает.

В "ListOfFriends" я хочу иметь список друзей пользователя.

Чтобы достичь этого, я определил следующий класс, но когда я попытаюсь найти пользователя с его именем пользователя, будет выведено сообщение об исключении "Stackoverflow". Кажется, он переходит в неопределенный цикл.

Когда я удаляю "FriendRequests" и "Friends" из моего метода toString, он перестает бросать исключение.

Этот вопрос похож на то, что я собираюсь достичь, но разница У меня нет того, чтобы иметь отдельный класс Credential.

Член

@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() + 
       "]";
    }
}

Дружба

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

Ответ 1

Я хотел бы предложить DB Design.

Прямо сейчас, у вас есть таблица друзей и друзей в соответствии с классами POJO. Вместо этого вы можете просто иметь таблицу дружбы с еще одним столбцом boolean isAccepted; (переменная в классе POJO).

Если логическое значение истинно, это означает, что этот член (друг) является другом. Когда вы хотите получить всех друзей, найдите строки дружбы с isAccepted, установленными в true; Если вы хотите получить только friendrequests (или ожидающие запросы для подтверждения), получите все строки с isAccepted, установленными в false.

С существующим дизайном БД или моделью ER, которую вы используете. Вам нужно удалить строку из таблицы дружбы (так как это больше не запрос друга), если человек принимает запрос друга и где-то хранит его. Где вы храните друзей. Согласно вашему существующему дизайну, он хранится в таблице друзей. Но нет колонки, в которой говорится, что строка указывает друга. Это похоже на добавление строки (друга) в таблицу друзей каждый раз, когда один участник принимает запрос другого участника и просто создает избыточные строки.

Ответ 2

Я не понимаю, что у вас есть, хотя я сделал очень мало изменений в вашем коде, чтобы он мог работать с моей настройкой.

/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 (два варианта использования здесь: либо createFriendships для инициализации некоторых данных, либо findMembers для проблемного варианта использования. Обратите внимание на свойство javax.persistence.schema-generation.database.action в файле persistence.xml, как вы хотите создать базу данных в первой, но не в последней)

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

}

Main производит:

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]

Дружба .java. Фактическое фактическое изменение - это имя столбца, на которое я ссылался, от username до id, поскольку я получил исключение could not get a field value by reflection. Кроме того, я считаю это лучше с точки зрения нормализации базы данных:

@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 (здесь нет изменений, кроме 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() + "]";
    }

}

Ответ 3

Я подражал вашей ситуации с последней версией Hibernate 5.2.2.Final. Я получаю такое же исключение. Я попытался заменить EAGER на выбор LAZY, и он начал работать нормально. StackOverFlowError означает, что Hibernate не распознает объект в циклической зависимости, и приступайте к тому, чтобы снова и снова запускать ссылочные строки, создавая новые и новые объекты до тех пор, пока Stack Memory не убежит.

Сильная ситуация:

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

Ситуация с ошибкой (наш случай):

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 и ask дали мне этот ответ:

Все поставщики JPA должны иметь возможность справляться с энергичной загрузкой циклических связи. Любая проблема должна означать, что вы поднимаете ошибку против JPA провайдера.

- Нейл Стоктон

Первое решение:

Поэтому я предлагаю вам использовать инициализацию LAZY. И вручную обрабатывайте операции извлечения FETCH JOIN. См., Пожалуйста, этот ответ.

Второе решение:

Избегайте циклических ссылок. Если первичный объект, который вы сначала вызовете, будет Friendship, тогда закомментируйте объекты Дружбы в объекте, чтобы Friendship извлек Member, и член ничего не узнает о Friendship. Это работает для меня, я тестировал.

К сожалению, ваша архитектура не позволяет делать обратную Friendship doesn't know about Member, поскольку член является первичным ключом. Я предлагаю вам изменить свою архитектуру, это очень сильная связь - плохая практика программирования.


Литература:

Ответ 4

Собственно, отличие от других вопросов не только отбрасывает объект Credentials. Ваш класс-член имеет обратные отношения с Другими, что вызывает циклические зависимости. В другом примере "корневой" объект - это дружба, которая соединяется с членами. Если вы хотите, чтобы член был "root", вы должны создать другую модель.