JPA извлекает только одноуровневую ассоциацию

У меня проблема при попытке получить все объекты «Discount», хранящиеся в моем приложении. «Скидка» - это объект, представленный следующим классом

@Entity
@NoArgsConstructor
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "store")
public class Discount {

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

// some fields like a description or a percentage providing info about a discount

@ManyToOne
@JoinColumn(name = "id_store")
private Store store;

@ManyToOne
@JoinColumn(name = "id_brand")
@JsonManagedReference
private Brand brand;

// getters, setters, equals and hashcode overriden below

Как видите, объект Discount может принадлежать Магазину или Бренду. Я гарантирую, что как в моем коде, так и в моей базе данных установлено только одно поле с триггером. В моем приложении Скидка может быть предоставлена ​​как Брендом (поэтому мне не нужно создавать Скидку для каждого Магазина этого Бренда), так и конкретным Магазином может предоставить свою собственную Скидку. Мой класс Store выглядит так

@Entity
@NoArgsConstructor
public class Store extends Entite {

@OneToMany(mappedBy = "store", cascade = CascadeType.ALL)
private Set<Discount> discounts;

@ManyToOne
@JoinColumn(name = "id_brand", nullable = false)
private Brand brand;

// getters, setters, equals and hashcode overriden below

Как видите, Магазин имеет набор скидок и принадлежит бренду. Он расширяет класс Entite, потому что в моем приложении у меня также есть спортивные клубы, и они делятся некоторой информацией, но это не имеет отношения к моей проблеме. Наконец, вот класс Brand

@Entity
@NoArgsConstructor
public class Brand {

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

@OneToMany(mappedBy = "brand", cascade = CascadeType.ALL)
private Set<Store> stores;

@OneToMany(mappedBy = "brand", cascade = CascadeType.ALL)
@JsonBackReference
private Set<Discount> discounts;

// getters, setters, equals and hashcode overriden below

Затем у класса «Мой бренд» есть набор магазинов и набор скидок.

Моя проблема в том, что я не могу получить всю скидку с помощью простого FindAll из моего interface DiscountRepository, расширяющего JpaRepository<Discount, Long>, потому что некоторые из скидок извлекаются внутри ассоциации. Ответ, который я получаю от метода FindAll, следующий

[
{
    // discount fields such as percentage, ruling, etc
    "store": {
        "id": 75,
        "name": "Nike Store",
        "discounts": [
            75
        ],
        "brand": {
            "name": "Nike",
            "store": [
                75
            ]
        }
    },
    "brand": null
},
{
    // discount fields such as percentage, ruling, etc
    "store": null,
    "brand": {
        "name": "Uniqlo",
        "stores": [
            90
        ]
    }
},
{
    // discount fields such as percentage, ruling, etc
    "store": null,
    "brand": {
        "name": "FNAC",
        "stores": [
            {
                "id": 76,
                // some store fields
                // problem here because Discount are retrieved here and therefore not displayed in the list, only here
                "discounts": [
                    {
                        // discount fields such as percentage, ruling, etc 
                        // for an offer that isn't present in the original list returned 
                        // by the find all method but only here, associated to this Store object
                        "store": 76,
                        "brand": null
                    }
                ],
                "brand": {
                    "name": "FNAC",
                    "stores": [
                        76
                    ]
                }
            }
        ]
    }
},
76
]

Проблема здесь в том, что есть выборка Discount с Store, и поэтому эта скидка находится не в исходном списке, который возвращает метод, а в объекте Store, связанном со скидкой. Я попытался добавить FetchType.LAZY в свою ассоциацию «Магазин и скидка», но это не решило проблему. Следовательно, есть ли способ получить «только одноуровневую ассоциацию»? Я имею в виду, что я хотел бы получить свой объект Discount, но только с информацией о бренде и магазине, а не с магазином или брендом и всеми его связями (например, его набором скидок). Я надеюсь, что я был ясен, не стесняйтесь задавать мне любые вопросы.

Проблема не в загрузке. Это связано с тем, что вы возвращаете из службы REST, и с тем, как вы настраиваете сериализатор JSON. Точно определите, что должно содержаться в теле ответа, затем спроектируйте класс, соответствующий желаемой структуре, и преобразуйте свои скидки в экземпляры этого класса. Вы используете JsonIdentityInfo, но не можете справиться с тем, что он делает.

JB Nizet 15.09.2018 12:44

Моя служба REST должна вернуть List<Discount>. Когда вы говорите «спроектируйте класс, соответствующий желаемой структуре», вы говорите, что я должен создать другой класс, имеющий только те поля, которые я хочу иметь? Но когда я получаю свое предложение, я хочу получить информацию о бренде и магазине, но не их ассоциации (которые содержат скидку, и это моя проблема). Кроме того, я не понимаю ваше последнее предложение, что вы имеете в виду, говоря «вы не можете справиться с тем, что делает [JsonIdentityInfo]»?

Quentin Lassalle 15.09.2018 18:57
вы говорите, что я должен создать другой класс, имеющий только те поля, которые я хочу иметь?: да. когда я получаю свое предложение, я хочу получить информацию о бренде и магазине, но не их ассоциации: это хорошая причина для использования отдельного класса, который соответствует структуре, которую вы хотите вернуть, т.е. содержит все, что вы хотите отправить обратно в качестве ответа, но не то, что вы не хотите отправлять обратно.
JB Nizet 15.09.2018 19:03
вы не можете справиться с этим [JsonIdentityInfo]: вы решили использовать эту аннотацию. Его эффект заключается в замене каждого вхождения объекта скидки с идентификатором 76 в дереве, кроме первого, только на идентификатор 76. Но ваш вопрос показывает, что такое поведение вас расстраивает. Так что вам вообще не следовало использовать эту аннотацию.
JB Nizet 15.09.2018 19:04

Разработка другого класса только для удаления данных, которые мне не нужны, потому что я не могу получить то, что хочу, - это неплохая практика? Я подумал об этом, когда решил использовать SQL-запрос для получения только тех полей, которые мне нужны, но на самом деле это не кажется действительно чистым, не так ли?

Quentin Lassalle 16.09.2018 19:27

Если я правильно помню, я использовал аннотацию JsonIdendityInfo, потому что она позволяла мне предотвратить StackOverflowException из-за циклических ссылок. Однако я попытался удалить его, и теперь я отлично получаю все свои скидки, но все еще с их магазином и связанными с ними скидками (затем получаю их дважды). Я буду искать способ лениво получать скидки в магазине, чтобы перестать получать их несколько раз. Большое спасибо за вашу помощь

Quentin Lassalle 16.09.2018 19:29

Ленивая загрузка не означает «без загрузки». Проектирование того, что вы хотите отправить, намного лучше, чем слепая отправка, где бы ни содержались ваши сущности, и прерывание работы всех клиентов ваших REST API всякий раз, когда вы что-то меняете на своем уровне сохраняемости. Контент JSON - это публичный контракт вашего REST API. Это не должно зависеть от того, как вы смоделировали свой уровень сохраняемости.

JB Nizet 16.09.2018 19:47

Поэтому, если я хорошо понимаю, мне следует разработать новый класс, содержащий: во-первых, всю информацию из скидки, которую я хочу получить, и во-вторых, информацию о магазине или информацию о бренде в зависимости от того, связана ли скидка с магазином или с брендом. . Должен ли я иметь атрибуты в моем новом классе, такие как storeName, storeAddress, storeCity,…? Поскольку эта информация уже присутствует в моем классе Store, я считаю ее избыточной, и разве этот дизайн не более подвержен поломке, поскольку у меня будут поля, присутствующие как в этом новом классе Discount, так и в классе Store и Adress (т. Е. Для Скидка, связанная с магазином)?

Quentin Lassalle 17.09.2018 08:57

Я не могу сказать вам, что должна вернуть ваша веб-служба. Вы тот, кто должен это решить. Для чего нужна услуга «получить все скидки». Для чего его используют клиенты этого сервиса? Какая информация им нужна, чтобы достичь того, чего они хотят достичь?

JB Nizet 17.09.2018 09:00

При вызове этой услуги клиент должен получить список всех Скидок. Я хочу, чтобы каждый объект в моем списке содержал все поля со скидкой, поля магазина и поля адреса для этого магазина (адрес - это класс, а у магазина только один адрес). Вот что я хочу, чтобы моя служба вернулась, когда ее вызовут.

Quentin Lassalle 17.09.2018 09:25

Затем создайте класс (или набор классов), точно соответствующий JSON, который вы хотите отправить обратно, и преобразуйте свои скидки в экземпляры этого класса: он будет загружать только то, что нужно загрузить, и отправлять то, что нужно отправить.

JB Nizet 17.09.2018 10:29

Итак, вы мне посоветуете создать DiscountDTO, которое будет содержать все поля Discount и дополнительную информацию, такую ​​как String storeName, String storeAddress, String storeCity и так далее, верно? Затем этот класс будет содержать точно всю информацию, которая мне понадобится, и я не буду возвращать непосредственно Entities, поскольку это кажется плохой практикой из того, что я прочитал сегодня в других ответах.

Quentin Lassalle 17.09.2018 21:27

Лучше было бы иметь StoreDTO в DiscountDTO.

JB Nizet 17.09.2018 21:28

Я подумал об этом в первую очередь, но, по логике, у меня должен быть List<Discount> в моем StoreDTO, не так ли?

Quentin Lassalle 17.09.2018 21:29

Нет, поскольку вы, не, хотите отправить это в JSON, а DTO разработан в точном соответствии с тем, что вы хотите отправить в JSON.

JB Nizet 17.09.2018 21:30

Хорошо, но если в будущем мне придется получить весь свой магазин (или конкретный) с их / его скидками, не придется ли мне создавать StoreDTO с List<Discount?

Quentin Lassalle 17.09.2018 21:33

да. Но затем вы создадите другие DTO, точно соответствующие JSON, который вы хотите отправить в этом другом, другом ответе. Или вы будете использовать те же DTO, но заполните их по-другому, решив оставить некоторые поля пустыми, потому что вы не хотите, чтобы они присутствовали в JSON.

JB Nizet 17.09.2018 21:34

Хорошо, тогда для меня это действительно ясно и имеет смысл. Большое спасибо за вашу помощь и ваше время.

Quentin Lassalle 17.09.2018 21:39
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
18
432
0

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