Отношения «многие ко многим» с использованием Spring Boot, Jackson и Hibernate

Я работаю над проектом отдыха, используя Spring Boot и Hibernate, и в настоящее время пытаюсь понять, как справиться с моей json-сериализацией.

введите здесь описание изображения

Схема, показанная в приведенном выше ERD, отображается Hibernate и работает нормально.

Проблема возникает, когда я делаю запрос на получение контроллера. Насколько я понимаю, Spring теперь пытается сериализовать цепочку объектов с помощью Jackson. Поскольку и родительский, и дочерний объекты имеют атрибуты друг друга, мы попадаем в бесконечный цикл рекурсии.

Теперь я изучил @JsonIgnore, @JsonView, @JsonManagedReference и @JsonBackReference, но, похоже, они работают только для отношений «один ко многим».

Я ищу ситуацию, когда, например, когда я делаю запрос GET к /users/{id}, я получаю пользовательский объект, включая все его атрибуты отношений (назовем его полным объектом), но сами атрибуты отношений не показывают их атрибуты отношений (минимизированные объекты). Это прекрасно работает с аннотациями, упомянутыми выше, но как мне заставить это работать и по-другому?

Желаемый ответ для: /users/{id}

{   // full user object
    id: 1,
    username: 'foo',
    // password can be JsonIgnored because of obvious reasons
    role: { // minimized role object
        id: 1,
        name: 'bar'
        // NO USERS LIST
    }
    area: { //minimized area object
        id: 2,
        name: 'some val'
        // NO USERS LIST
        // NO TABLES LIST
    }
}

Желаемый ответ для /userrole/{id}

{ // full role object
    id: 1,
    name: 'waiter'
    users: [
        {   // minmized user object
            id: 1,
            username: 'foo'
            // password can be JsonIgnored because of obvious reasons
            // NO ROLE OBJECT
            // NO AREA OBJECT
        },
        {   // minmized user object
            id: 1,
            username: 'foo'
            // password can be JsonIgnored because of obvious reasons
            // NO ROLE OBJECT
            // NO AREA OBJECT
        }
    ]
}

В общем: мне нужен полный объект, когда запрос делается непосредственно к объекту, и минимизированный объект, когда запрашивается косвенно.

Есть идеи? Надеюсь, мое объяснение достаточно ясно.


ОБНОВИТЬ

POJO Area, User и UserRole, как указано в разделах комментариев.

Пользователь

@Entity
@Table(name = "users", schema = "public", catalog = "PocketOrder")

public class User {
    private int id;
    private String username;
    private String psswrd;
    private List<Area> areas;

    private UserRole Role;

    @Id
    @Column(name = "id", nullable = false)
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Basic
    @Column(name = "username", nullable = false, length = 20)
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Basic
    @JsonIgnore
    @Column(name = "psswrd", nullable = true, length = 40)
    public String getPsswrd() {
        return psswrd;
    }

    public void setPsswrd(String psswrd) {
        this.psswrd = psswrd;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (id != user.id) return false;
        if (username != null ? !username.equals(user.username) : user.username != null) return false;
        if (psswrd != null ? !psswrd.equals(user.psswrd) : user.psswrd != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (username != null ? username.hashCode() : 0);
        result = 31 * result + (psswrd != null ? psswrd.hashCode() : 0);
        return result;
    }

    @ManyToMany(mappedBy = "users")
    public List<Area> getAreas() {
        return areas;
    }

    public void setAreas(List<Area> areas) {
        this.areas = areas;
    }

    @ManyToOne
    @JoinColumn(name = "role_fk", referencedColumnName = "id", nullable = false)
    public UserRole getRole() {
        return Role;
    }

    public void setRole(UserRole role) {
        Role = role;
    }
}

Роль пользователя

@Entity
@javax.persistence.Table(name = "userroles", schema = "public", catalog = "PocketOrder")
public class UserRole {
    private int id;
    private String name;
    private List<User> users;


    @Id
    @Column(name = "id", nullable = false)
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Basic
    @Column(name = "name", nullable = false, length = 20)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        UserRole userRole = (UserRole) o;

        if (id != userRole.id) return false;
        if (name != null ? !name.equals(userRole.name) : userRole.name != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }

    @OneToMany(mappedBy = "role")
    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        users = users;
    }
}

Площадь

@Entity
@javax.persistence.Table(name = "areas", schema = "public", catalog = "PocketOrder")
public class Area {
    private int id;
    private String name;
    private List<User> users;


    private List<Table> tables;

    @Id
    @Column(name = "id", nullable = false)
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Basic
    @Column(name = "name", nullable = false, length = 20)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Area area = (Area) o;

        if (id != area.id) return false;
        if (name != null ? !name.equals(area.name) : area.name != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }

    @ManyToMany
    @JoinTable(name = "areas_users", catalog = "PocketOrder", schema = "public", joinColumns = @JoinColumn(name = "area_fk", referencedColumnName = "id", nullable = false), inverseJoinColumns = @JoinColumn(name = "user_fk", referencedColumnName = "id", nullable = false))
    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }

    @OneToMany(mappedBy = "area")
    public List<Table> getTables() {
        return tables;
    }

    public void setTables(List<Table> tables) {
        this.tables = tables;
    }
}

person Karim Stekelenburg    schedule 01.06.2017    source источник
comment
Пожалуйста, опубликуйте свои POJO.   -  person mrhallak    schedule 01.06.2017
comment
@hallaksec Спасибо за ваш быстрый ответ, я добавил упомянутые POJO к вопросу.   -  person Karim Stekelenburg    schedule 01.06.2017


Ответы (2)


Попробуйте использовать @JsonSerialize в определенных точках:

Для образца:

1 – Сопоставьте свое поле

@JsonSerialize(using = ExampleSampleSerializer.class)
@ManyToOne
private Example example;

2 – Создайте собственный сериализатор Джексона (здесь вы можете управлять сериализацией)

public class ExampleSampleSerializer extends JsonSerializer<Example> {
    @Override
    public void serialize(Example value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException, JsonProcessingException {

        jsonGenerator.writeStartObject();
        jsonGenerator.writeFieldName("first");
        jsonGenerator.writeNumber(value.getFirstValue());
        jsonGenerator.writeFieldName("second");
        jsonGenerator.writeNumber(value.getSecondValue());
        jsonGenerator.writeFieldName("third");
        jsonGenerator.writeNumber(value.getAnyAnotherClass().getThirdValue());
        jsonGenerator.writeEndObject();
    }
}
person Ady Junior    schedule 01.06.2017
comment
Это выглядело слишком хорошо, чтобы быть правдой, поэтому я попробовал это на POJO выше (у меня нет записей таблиц в моей базе данных, связанных с какой-либо областью, поэтому он не будет пытаться сериализовать их). Это возвращает следующую ошибку: Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: org.hibernate.collection.internal.PersistentBag cannot be cast to com.entity.Area (through reference chain: java.util.ArrayList[0]->com.entity.User["areas"]); Это та же самая ошибка, которую я получил при попытке аннотировать с помощью @JsonIdentityInfo ранее. Любая идея, что происходит? - person Karim Stekelenburg; 01.06.2017
comment
Это происходит потому, что Hibernate перегружает отношения @OneToMany с прокси-серверами (класс PersistenseBag). Я нашел эту запись об этом. Вы можете попробовать использовать PersistentCollectionSerializer или HibernateProxySerializer, присутствующие в сообщении + @ JsonSerializer. Например: @ JsonSerializer(using = PersistentCollectionSerializer.class) Любые вопросы комментируйте здесь, мы найдем решение. - person Ady Junior; 02.06.2017
comment
Всем привет! Прошлой ночью я исправил это (довольно некрасиво), используя аннотацию @JsonIdintityInfo. Это дало мне кусок json, в котором объекты были полностью представлены только один раз. Остальные ссылки на тот же объект представлены только своим идентификатором. Это работает, и это действительно хороший способ убедиться, что ненужные данные не отправляются, но на стороне клиента требуется некоторое декодирование. Будет ли ваше решение давать тот же результат или что-то другое? Мне любопытно :). - person Karim Stekelenburg; 02.06.2017
comment
Нет, мое решение приводит к другому результату. Это будет сериализовать только определенные поля, избегая циклических ссылок. - person Ady Junior; 05.06.2017

Способ, которым я работал над этим в отношениях «многие ко многим», заключается в использовании

@JsonIgnore

На одной из Сущностей. Например, у нас есть сущности Person и Child. У одного человека может быть много детей и наоборот. На Person имеем:

public class Person
{
  //Other fields ommited
     @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
     @JoinTable(name = "person_child",
                    joinColumns = {
        @JoinColumn(name = "person_id", referencedColumnName = "id", nullable = false, 
        updatable = false)
         },
                    inverseJoinColumns = {
                @JoinColumn(name = "child_id", referencedColumnName = "id", nullable = 
                false, updatable = false)
        })
private Set<Child> children = new HashSet<>() ;

}

И на Child у нас есть:

public class Child
{
      @JsonIgnore
      @ManyToMany(mappedBy = "children", fetch = FetchType.LAZY)
      private Set<Person> people = new HashSet<>() ;
}

Теперь, когда мы получаем Person, мы также получаем всех его подключенных детей. Но когда мы получаем Child, мы не получаем всех людей, потому что у нас есть аннотация @JsonIgnore. Это устраняет проблему бесконечной рекурсии и поднимает эту.

Мой обходной путь состоял в том, чтобы написать запрос, чтобы получить всех людей, подключенных к определенному child_id. Ниже вы можете увидеть мой код:

public interface PersonDAO extends JpaRepository<Person, Long>
{

        @Query(value = "SELECT * " +
        " FROM person p INNER JOIN person_child j " +
        "ON p.id = j.person_id WHERE j.child_id = ?1 ", nativeQuery = true)
         public List<Person> getPeopleViaChildId(long id);
}

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

person Renis1235    schedule 16.10.2020