ServletException
ServletException

Reputation: 239

Spring Boot REST Api - Response data is not showing expected results using 1:M relationship between entity classes

So I'm trying to build a website using Spring Boot and React.

So far I have a registration form that is connected to my spring backend at localhost:8080/api/test/customers (Have endpoints for CRUD operations here)

This woks fine, and I can post form data to it from my React form, and I can view it in postman to get the expected response from the API.


But now I have another form on the site (A contact form where a user enters their name, email and message).

I have this API at localhost:8080/api/form/contact for GET and POST requests.


I have mapped the Entities 'Customer' and 'Contact Form' using a 1:M relationship which contains the customer id as a FK in the Contact Form table.

The thing is, since I added this, The response from the API is not what I'm expecting.


The response goes on for like this for 200 lines in postman, just nesting the same stuff

The GET request to the Customer API (registration)

The GET request to the Contact Form API


My Entity Classes

Customer.java - Removed Validation Annotations to make it easier to read

@Entity
@Table(name="customer")
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="customer_id")
    private int customerId;

    @Column(name="first_name")
    private String firstName;

    @Column(name="last_name")
    private String lastName;

    @Column(name="email_address")
    private String emailAddress;

    @Column(name="date_of_birth")
    private Date dateOfBirth;

    @Column(name="mobile_number")
    private String mobileNumber;

    @Column(name="password")
    private String password;

    @OneToMany(mappedBy = "customer")
    private Set<ContactForm> contactForm;

    // default & arg constructor
    // getters and setters

}



ContactForm.java - Removed Validation Annotations to make it easier to read

@Entity
@Table(name="contact_form")
public class ContactForm {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="contact_form_id")
    private int id;

    @Column(name="name")
    private String name;

    @Column(name="email_address")
    private String emailAddress;

    @Column(name="message")
    private String message;

    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;

    // default and arg constructor
    // getters & setters

}

MySQL DDL for these 2 tables

customer


CREATE TABLE `customer` (
  `customer_id` int NOT NULL AUTO_INCREMENT,
  `mobile_number` varchar(12) DEFAULT NULL,
  `date_of_birth` date DEFAULT NULL,
  `email_address` varchar(255) DEFAULT NULL,
  `first_name` varchar(255) DEFAULT NULL,
  `last_name` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`customer_id`)
)

contact_form


CREATE TABLE `contact_form` (
  `contact_form_id` int NOT NULL AUTO_INCREMENT,
  `email_address` varchar(255) NOT NULL,
  `message` varchar(255) NOT NULL,
  `name` varchar(255) NOT NULL,
  `customer_id` int DEFAULT NULL,
  PRIMARY KEY (`contact_form_id`),
  FOREIGN KEY (`customer_id`) REFERENCES `customer` (`customer_id`)
) 

Controller class for customer

Controller Class with standard endpoints, same for ContactForm Controller

I cant post data to either endpoints anymore, Can someone please let me know why this is happening, I would like to have a record of the users and all their previous messages.

Upvotes: 0

Views: 1862

Answers (2)

haja
haja

Reputation: 326

I would highly suggest to look at the DTO pattern to expose your domain objects over REST, with just the necessary information included.

Exposing ORM entities directly is error prone, not abstracting API internals and can even create major security risks.

For example, create UserDto and MessageDto classes:

public class UserDto {
    private int customerId;
    private List<MessageDto> messages;
}

public class MessageDto {
    private String content;
    private String recipient;
    // other relevant fields
}

and return the UserDto (or a list thereof) from your rest controller method instead.

The actual problem you are describing is that your ORM mapping builds a circular graph between Customer and ContactForm. The @OneToMany/@ManyToOne relation seems to be not working correctly.

In general I would suggest to not map Entities in such a cyclic way, but make them navigable in only one direction (e.g. Customer->ContactForm or the other way round).
Cyclic mappings (and mapping big graphs in general) has IMO several issues:

  • It can cause automatic, unexpected loading of very big parts of your database in memory for seemingly small queries.
  • It can lead to unstructured domain model design.

The latter issue is arises when your application grows, and might be harder to fix later on:
Suppose your app and team grows, and multiple people should work on different parts mostly independently. So you refactor your existing code base, and create two modules: customer and contact.
customer holds basic data of customers (name, address, email, ...). It's sole purpose is to manage the lifecycle of customers (creating customers, changing the address or name, searching for specific customers, ...).
contact implements means to interact with your company: a contact form for customers, a contact form for people that are not your customers yet and a way for your employees to reply to those messages.

Now these two modules should be decoupled, so changes in one module impact the other module as little as possible.
To achieve that, you should have only well defined dependencies between the two modules, and no cyclic dependencies between modules in general.

If the code you built until that point navigates back and forth between entities of different "domains" via ORM graphs (or other methods for that matter), you will have a hard time splitting those things up.

Now, all of this is a bit far into the future, and still doesn't solve your problem at hand (in contrast to @Flavalacious answer).
I would only suggest to think about navigable mappings in general, if the dependency introduced between the classes is necessary and useful and what the implications could be further down the line.

Edit: elaborate on issues with cyclic mappings and deeper mappings in general.

Upvotes: 2

Achmad Afriza
Achmad Afriza

Reputation: 125

Regardless of using DTO or not, you referred Customer which refers ContactForm which refers Customer again, causing a recursive reference to the object.

A simple way to avoid this is to use @JsonManagedReference and @JsonBackReference, which is described here.

Another way to do it is to @JsonIgnore on the child object, depending on how your API interacts.

Both of these would tell Jackson to ignore the child object on Serialization.

Upvotes: 1

Related Questions