Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Many to Many relationships using Spring Boot, Jackson and Hibernate

I'm working on a rest project using Spring Boot and Hibernate and am currently trying to figure out how to handle my json-serialization.

enter image description here

The schema shown in the ERD above is mapped by Hibernate and works fine.

The problem arises when I make a get request to a controller. My understanding is that Spring now tries to serialize the object-chain using Jackson. Because both the parent and child objects have one another as an attribute, we find ourselves hitting an infinite recursion loop.

Now I've looked into @JsonIgnore, @JsonView, @JsonManagedReference and @JsonBackReference but these only seem to work for one-to-many relationships.

What I'm looking for is a situation where when I for instance make a GET request to /users/{id}, I get the user object including all it's relationship attributes (let's call it the full object), but the relationship attributes themselves don't show their relationship-attributes (minimized objects). This works fine with the annotations mentioned above, but how do I make this work the other way as well?

Desired response for: /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
    }
}

Desired response for /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
        }
    ]
}

In general: I'd like a full object when the request is made to the entity directly and a minimized object when requested indirectly.

Any Ideas? I hope my explanation is clear enough.


UPDATE

The Area, User and UserRole POJO's as requested in the comment sections.

User

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

UserRole

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

Area

@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;
    }
}
like image 365
Karim Stekelenburg Avatar asked Sep 01 '25 10:09

Karim Stekelenburg


1 Answers

Try use @JsonSerialize on specific points:

For sample:

1 - Map your field

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

2 - Create custom jackson serializer (Here you can control the serialization)

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();
    }
}
like image 162
Ady Junior Avatar answered Sep 03 '25 22:09

Ady Junior