Reputation: 239
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
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:
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
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