Проблема с циклом при сериализации объектов в JSON в Spring Boot

Я использую версию Spring Boot 3.2 с базой данных MySQL. У меня есть два объекта

@Getter
@Setter
@Entity
@Table(name = "shops")
public class Shop{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String address;
    @OneToMany(mappedBy = "shop", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Book> books;
}

и

@Getter
@Setter
@Entity
@Table(name = "books")
public class Book{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String description;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "shop_id")
    private Shop shop;
}

Итак, у меня возникла проблема с ошибками, когда я пытаюсь получить все магазины со всеми книгами. У меня есть объект «Магазин со всеми объектами книг» внутри каждой книги, внутри которого есть свой собственный объект магазина со всей книгой и т. д. И это выглядит так:

[
    {
        "id": 4,
        "address": "Street",
        "books": [
                {
                "id": 3,
                "description": "Book1",
                "shop" : {
                       "id": 4,
                       "address": "Street"
                       "books": [
                               {
                                "id": 3,
                                "description": "Book1",
                                "shop" : { ...
                               }
                       ]
                }
        ]
     }
]

Итак, проблема в том, что я хочу использовать один класс для сохранения магазинов/книг и получения данных из базы данных. Затем мне нужно, чтобы в этом магазине все книги были объектами со всеми данными, но книги имели только shopId. Итак, получение всех магазинов должно выглядеть так

[
    {
        "id": 4,
        "address": "Street",
        "books": [
                {
                "id": 3,
                "description": "Book1",
                "shop" : 4
                }
         ]
     }
]

и получение всех книг должно выглядеть так

[
    {
        "id": 3,
        "description": "Book1",
        "shop": 4
    },
    {
        "id": 4,
        "description": "Book2",
        "shop": 5
    },
    {
        "id": 5,
        "description": "Book4",
        "shop": 5
    },
]

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

  1. Я не хочу создавать дополнительные объекты ShopDTO/BookDTO для получения, потому что, если это будет 1 миллион книг, мне нужно каждый раз создавать 1 миллион объектов при получении.
  2. Я не могу добавить еще одну переменную, например Long shopId; потому что это будет ошибка спящего режима Таблица [books] содержит имя физического столбца [shop_id], на которое ссылаются несколько имен логических столбцов: [shop_id], [shopId]
  3. я не могу изменить
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shop_id")
private Shop shop; 

на

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shop_id")
private Long shop;

или вообще удалите @ManyToOne.

Разве книги и магазины не связаны между собой многие-ко-многим (в реальном мире)?

Jim Garrison 28.06.2024 01:16

Для пункта 1 «объекты для получения, потому что если это будет 1 миллион книг, то мне нужно создать 1 миллион объектов» это произойдет в любом случае, если у вас есть 1 миллион книг, вызов книг создаст 1 миллион объектов (в списке). Для 2 «Я не могу добавить еще одну переменную, например Long shopId»; да, можешь, просто добавь комментарий @Transient

Jorge Campos 28.06.2024 01:36

@JimGarrison Я думаю, это правильно: один экземпляр (физическая книга) книги может быть только в одном магазине?

Jorge Campos 28.06.2024 01:37

ОП, это может тебе помочь: baeldung.com/…

Jorge Campos 28.06.2024 01:39

Из кода было неясно, означает ли «книга» название книги или физическую книгу. Вы будете тратить много места на хранение одного и того же заголовка снова и снова. Название книги должно быть сохранено один раз вместе с его метаданными (ISBN и т. д.), а экземпляры книг должны ссылаться на эту таблицу.

Jim Garrison 28.06.2024 02:03

@JorgeCampos да. я получаю все объекты 1M, но если я преобразую Book в BookDTO только для того, чтобы показать shop_Id вместо магазина, я получу 2M объектов вместо 1M. Имеет ли это смысл? Я могу добавить частный временный Long shopId; но в чем смысл?) Он не сериализуется, и я не получаю shopId, пока получаю все книги

Dany 28.06.2024 11:32

@JimGarrison, на самом деле это не имеет значения. В моем проекте есть уникальные книги, и они не могут быть представлены в разных магазинах, поэтому мне подходит «Один ко многим».

Dany 28.06.2024 11:37

Извините, это не имеет смысла, и нет, вы не будете дублировать объекты. Это либо 1 миллион для книг, либо 1 миллион для bookdto. Я не уверен, что заставляет вас думать, что это удвоит количество объектов.

Jorge Campos 28.06.2024 17:23

@JorgeCampos Я только что узнал, что есть решение получить все данные из базы данных в виде класса Shop, а затем преобразовать их в класс ShopDTO, но во время преобразования я получаю только то, что мне нужно показать в JSON.

Dany 30.06.2024 13:16
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
9
63
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

@Entity
@Table(name = "shops")
public class Shop {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String address;

}


@Entity
@Table(name = "books")
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String description;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "shop_id")
    private Shop shop;

}


SELECT s.id, s.address, b.id AS bookId
FROM shops s
LEFT JOIN books b ON s.id = b.shop_id;

А в Магазине вообще нет информации о Книгах?

Dany 28.06.2024 11:29
Ответ принят как подходящий

Итак, я нашел решение. Я добавил @JsonIdentityInfo в свой магазин.

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Shop{...

и добавил то же самое для книги и добавил еще одну аннотацию в свой магазин.

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Book{...
   @JsonIdentityReference(alwaysAsId = true)
   private Shop shop;
}

Рад, что статья, которой я поделился, оказалась полезной. :)

Jorge Campos 28.06.2024 17:24

Другие вопросы по теме