JPA ManyToMany возвращает только первый объект весной

#json #spring #spring-boot #jpa

Вопрос:

Привет, у меня есть приведенный ниже код, который возвращает все книги и соответствующую информацию об издателе. Но я вижу только первый результат, в первом и третьем объектах просто есть записи книгоиздателей, но во 2-м объекте нет вложенного объекта.

Что-то не так с сериализацией JPA или JSON? Нужно ли что-то изменить для целей сериализации?

Решения Пробовали:

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

Код:

 @Data
@NoArgsConstructor
@Entity
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id",
    scope = Book.class)
public class Book implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;

  private String name;

  @OneToMany(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true)
  private Set<BookPublisher> bookPublishers = new HashSet<>();

  public Book(String name) {
    this.name = name;
  }

}


@Data
@NoArgsConstructor
@Entity
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id",
    scope = Publisher.class)
public class Publisher implements Serializable {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;

  private String name;

  @OneToMany(mappedBy = "publisher")
  private Set<BookPublisher> bookPublishers = new HashSet<>();

  public Publisher(String name) {
    this.name = name;
  }

}


@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "book_publisher")
@AssociationOverrides({
    @AssociationOverride(name = "id.bookId", joinColumns = @JoinColumn(name = "book_id")),
    @AssociationOverride(name = "id.publisherId",
        joinColumns = @JoinColumn(name = "publisher_id"))})
public class BookPublisher implements Serializable {

  @EmbeddedId
  private BookPublisherId id;

  @ManyToOne
  @MapsId("bookId")
  @JoinColumn(name = "book_id")
  private Book book;

  @ManyToOne
  @MapsId("publisherId")
  @JoinColumn(name = "publisher_id")
  private Publisher publisher;

  @Column(name = "published_date")
  private Date publishedDate;

  public BookPublisher(Book book, Publisher publisher, Date publishedDate) {
    this.id = new BookPublisherId(book.getId(), publisher.getId());
    this.book = book;
    this.publisher = publisher;
    this.publishedDate = publishedDate;
  }

}


@Data
@AllArgsConstructor
@NoArgsConstructor
@Embeddable
public class BookPublisherId implements Serializable {

  @Column(name = "book_id")
  private Integer bookId;

  @Column(name = "publisher_id")
  private Integer publisherId;

}
 

Ожидаемый:

 [
  {
    "id": 1,
    "name": "Book1",
    "bookPublishers": [
      {
        "publisher": {
          "id": 2,
          "name": "Pub2"
        },
        "publishedDate": "2021-06-06T14:42:30.754 00:00"
      },
      {
        "publisher": {
          "id": 1,
          "name": "Pub1"
        },
        "publishedDate": "2021-06-06T14:42:30.754 00:00"
      }
    ]
  },
  {
    "id": 2,
    "name": "Book2",
    "bookPublishers": [
      {
        "publisher": {
          "id": 1,
          "name": "Pub1"
        },
        "publishedDate": "2021-06-06T14:42:30.754 00:00"
      },
      {
        "publisher": {
          "id": 2,
          "name": "Pub2"
        },
        "publishedDate": "2021-06-06T14:42:30.754 00:00"
      }
    ]
  },
  {
    "id": 3,
    "name": "Book3",
    "bookPublishers": [
      {
        "publisher": {
          "id": 3,
          "name": "Pub3"
        },
        "publishedDate": "2021-06-06T14:42:30.754 00:00"
      }
    ]
  }
]
 

Actual:

 [
  {
    "id": 1,
    "name": "Book1",
    "bookPublishers": [
      {
        "id": {
          "bookId": 1,
          "publisherId": 2
        },
        "book": 1,
        "publisher": {
          "id": 2,
          "name": "Pub2",
          "bookPublishers": [
            {
              "bookId": 1,
              "publisherId": 2
            },
            {
              "id": {
                "bookId": 2,
                "publisherId": 2
              },
              "book": {
                "id": 2,
                "name": "Book2",
                "bookPublishers": [
                  {
                    "id": {
                      "bookId": 2,
                      "publisherId": 1
                    },
                    "book": 2,
                    "publisher": {
                      "id": 1,
                      "name": "Pub1",
                      "bookPublishers": [
                        {
                          "bookId": 2,
                          "publisherId": 1
                        },
                        {
                          "id": {
                            "bookId": 1,
                            "publisherId": 1
                          },
                          "book": 1,
                          "publisher": 1,
                          "publishedDate": "2021-06-06T14:42:30.754 00:00"
                        }
                      ]
                    },
                    "publishedDate": "2021-06-06T14:42:30.754 00:00"
                  },
                  {
                    "bookId": 2,
                    "publisherId": 2
                  }
                ]
              },
              "publisher": 2,
              "publishedDate": "2021-06-06T14:42:30.754 00:00"
            }
          ]
        },
        "publishedDate": "2021-06-06T14:42:30.754 00:00"
      },
      {
        "bookId": 1,
        "publisherId": 1
      }
    ]
  },
  2,                 <<<<<<<<<<<<<- Here whole object itself missing???
  {
    "id": 3,
    "name": "Book3",
    "bookPublishers": [
      {
        "id": {
          "bookId": 3,
          "publisherId": 3
        },
        "book": 3,
        "publisher": {
          "id": 3,
          "name": "Pub3",
          "bookPublishers": [
            {
              "bookId": 3,
              "publisherId": 3
            }
          ]
        },
        "publishedDate": "2021-06-06T14:42:30.754 00:00"
      }
    ]
  }
]
 

DB Results:

 select * from book;
select * from publisher;
select * from book_publisher;

id  name
1   Book1
2   Book2
3   Book3

id  name
1   Pub1
2   Pub2
3   Pub3

book_id publisher_id    published_date
1         1             2021-06-06 20:12:30.7540000
1         2             2021-06-06 20:12:30.7540000
2         1             2021-06-06 20:12:30.7540000
2         2             2021-06-06 20:12:30.7540000
3         3             2021-06-06 20:12:30.7540000
 

Solutions Tried But still same Problem: Added equals/hashcode for all class
Please find the implementations for each class:

 Book:
  @Override
  public boolean equals(Object o) {
    if (this == o)
      return true;
    if (o == null || getClass() != o.getClass())
      return false;
    Book Book = (Book) o;
    return Objects.equals(name, Book.name);
  }

  @Override
  public int hashCode() {
    return Objects.hash(name);
  }

Publisher:
  @Override
  public boolean equals(Object o) {
    if (this == o)
      return true;

    if (o == null || getClass() != o.getClass())
      return false;

    Publisher Publisher = (Publisher) o;
    return Objects.equals(name, Publisher.name);
  }

  @Override
  public int hashCode() {
    return Objects.hash(name);
  }

BookPublisher:
  @Override
  public boolean equals(Object o) {
    if (this == o)
      return true;

    if (o == null || getClass() != o.getClass())
      return false;

    BookPublisher that = (BookPublisher) o;
    return Objects.equals(book, that.book) amp;amp; Objects.equals(publisher, that.publisher);
  }

  @Override
  public int hashCode() {
    return Objects.hash(book, publisher);
  }

BookPublisherId:
  @Override
  public boolean equals(Object o) {
    if (this == o)
      return true;

    if (o == null || getClass() != o.getClass())
      return false;

    BookPublisherId that = (BookPublisherId) o;
    return Objects.equals(bookId, that.bookId) amp;amp; Objects.equals(publisherId, that.publisherId);
  }

  @Override
  public int hashCode() {
    return Objects.hash(bookId, publisherId);
  }
 

Выходной запрос, сгенерированный для BookRepository.findAll():

 Hibernate: select book0_.id as id1_0_, book0_.name as name2_0_ from Book book0_
Hibernate: select bookpublis0_.book_id as book_id1_1_0_, bookpublis0_.publisher_id as publishe2_1_0_, bookpublis0_.book_id as book_id1_1_1_, bookpublis0_.publisher_id as publishe2_1_1_, bookpublis0_.published_date as publishe3_1_1_, publisher1_.id as id1_3_2_, publisher1_.name as name2_3_2_ from book_publisher bookpublis0_ inner join Publisher publisher1_ on bookpublis0_.publisher_id=publisher1_.id where bookpublis0_.book_id=?
Hibernate: select bookpublis0_.publisher_id as publishe2_1_0_, bookpublis0_.book_id as book_id1_1_0_, bookpublis0_.book_id as book_id1_1_1_, bookpublis0_.publisher_id as publishe2_1_1_, bookpublis0_.published_date as publishe3_1_1_, book1_.id as id1_0_2_, book1_.name as name2_0_2_ from book_publisher bookpublis0_ inner join Book book1_ on bookpublis0_.book_id=book1_.id where bookpublis0_.publisher_id=?
Hibernate: select bookpublis0_.book_id as book_id1_1_0_, bookpublis0_.publisher_id as publishe2_1_0_, bookpublis0_.book_id as book_id1_1_1_, bookpublis0_.publisher_id as publishe2_1_1_, bookpublis0_.published_date as publishe3_1_1_, publisher1_.id as id1_3_2_, publisher1_.name as name2_3_2_ from book_publisher bookpublis0_ inner join Publisher publisher1_ on bookpublis0_.publisher_id=publisher1_.id where bookpublis0_.book_id=?
Hibernate: select bookpublis0_.publisher_id as publishe2_1_0_, bookpublis0_.book_id as book_id1_1_0_, bookpublis0_.book_id as book_id1_1_1_, bookpublis0_.publisher_id as publishe2_1_1_, bookpublis0_.published_date as publishe3_1_1_, book1_.id as id1_0_2_, book1_.name as name2_0_2_ from book_publisher bookpublis0_ inner join Book book1_ on bookpublis0_.book_id=book1_.id where bookpublis0_.publisher_id=?
Hibernate: select bookpublis0_.book_id as book_id1_1_0_, bookpublis0_.publisher_id as publishe2_1_0_, bookpublis0_.book_id as book_id1_1_1_, bookpublis0_.publisher_id as publishe2_1_1_, bookpublis0_.published_date as publishe3_1_1_, publisher1_.id as id1_3_2_, publisher1_.name as name2_3_2_ from book_publisher bookpublis0_ inner join Publisher publisher1_ on bookpublis0_.publisher_id=publisher1_.id where bookpublis0_.book_id=?
Hibernate: select bookpublis0_.publisher_id as publishe2_1_0_, bookpublis0_.book_id as book_id1_1_0_, bookpublis0_.book_id as book_id1_1_1_, bookpublis0_.publisher_id as publishe2_1_1_, bookpublis0_.published_date as publishe3_1_1_, book1_.id as id1_0_2_, book1_.name as name2_0_2_ from book_publisher bookpublis0_ inner join Book book1_ on bookpublis0_.book_id=book1_.id where bookpublis0_.publisher_id=?
 

Комментарии:

1. Все ли ваши внешние ключи присутствуют в базе данных? Это может объяснить, почему у вас есть только некоторые записи.

2. @Pilpo да, у него есть результаты, и я прикрепил результаты базы данных также для всех таблиц в вопросе.

3. Другой момент заключается в том, что вы не написали явные значения/хэш-код, которые необходимы, потому что вы используете Set<> (а также embeded id) (Hibernate нужны для вычисления хэша в соответствии с вашим объектом, а не в соответствии с идентификатором)

4. @Pilpo Добавление данных аннотаций из lambok также не помогает в «@EqualsAndHashCode»…. или мне нужно разместить ручную реализацию Equals amp; Hashcode?

5. Я недостаточно знаю Ломбок, чтобы говорить об этом. Во-первых, вы должны попытаться добавить их вручную. В случае успеха, их протестируют с Ломбоком 🙂