Reputation: 1212
I have built a REST API using Spring Boot Data REST. I'm using an embeddedId and have also implemented a BackendIdConverter.
Below is my Embeddable class
@Embeddable
public class EmployeeIdentity implements Serializable {
@NotNull
@Size(max = 20)
private String employeeId;
@NotNull
@Size(max = 20)
private String companyId;
public EmployeeIdentity() {}
public EmployeeIdentity(String employeeId, String companyId) {
this.employeeId = employeeId;
this.companyId = companyId;
}
public String getEmployeeId() {
return employeeId;
}
public void setEmployeeId(String employeeId) {
this.employeeId = employeeId;
}
public String getCompanyId() {
return companyId;
}
public void setCompanyId(String companyId) {
this.companyId = companyId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EmployeeIdentity that = (EmployeeIdentity) o;
if (!employeeId.equals(that.employeeId)) return false;
return companyId.equals(that.companyId);
}
@Override
public int hashCode() {
int result = employeeId.hashCode();
result = 31 * result + companyId.hashCode();
return result;
}
}
Here's my Employee model
@Entity
@Table(name = "employees")
public class Employee {
@EmbeddedId
private EmployeeIdentity id;
@NotNull
@Size(max = 60)
private String name;
@NaturalId
@NotNull
@Email
@Size(max = 60)
private String email;
@Size(max = 15)
@Column(name = "phone_number", unique = true)
private String phoneNumber;
public Employee() {}
public Employee(EmployeeIdentity id, String name, String email, String phoneNumber) {
this.id = id;
this.name = name;
this.email = email;
this.phoneNumber = phoneNumber;
}
public EmployeeIdentity getId() {
return id;
}
public void setId(EmployeeIdentity id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
And to have resource links generated properly using my embedded id instead of a qualified class name
@Component
public class EmployeeIdentityIdConverter implements BackendIdConverter {
@Override
public Serializable fromRequestId(String id, Class<?> aClass) {
String[] parts = id.split("_");
return new EmployeeIdentity(parts[0], parts[1]);
}
@Override
public String toRequestId(Serializable source, Class<?> aClass) {
EmployeeIdentity id = (EmployeeIdentity) source;
return String.format("%s_%s", id.getEmployeeId(), id.getCompanyId());
}
@Override
public boolean supports(Class<?> type) {
return Employee.class.equals(type);
}
}
And here's my repository code
@RepositoryRestResource(collectionResourceRel = "employees", path = "employees")
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, EmployeeIdentity> {
}
This works fine with GET requests but I need to be able to POST. The first thing I noticed that when I do a POST with the json
{
"id": {
"employeeId": "E-267",
"companyId": "D-432"
},
"name": "Spider Man",
"email": "[email protected]",
"phoneNumber": "+91-476253455"
}
This doesn't work. EmployeeIdentityIdConverter#fromRequestId throws a null pointer exception because the string parameter is null. So I added a null check and return default EmployeeIdentity when id is null. As described by this answer https://stackoverflow.com/a/41061029/4801462
Modified EmployeeIdentityIdConverter#fromRequestId
@Override
public Serializable fromRequestId(String id, Class<?> aClass) {
if (id == null) {
return new EmployeeIdentity();
}
String[] parts = id.split("_");
return new EmployeeIdentity(parts[0], parts[1]);
}
But this raised another problem. My implementations for hashCode and equals now through null pointer exceptions since the default constructor was used and the employeeId and companyId are null.
In an attempt to fix this, I gave default values to employeeId and companyId
**Modified Employee#Employee() constructor*
public Employee() {
this.employeeId = "";
this.companyId = "";
}
NOTE
I am not even sure of what I was doing above. I was just trying to fix the small problems as they occurred.
By the way if you guessed this didn't work then you're right. While I didn't get an error and the request was successful, I didn't get the behavior I expected. A new entry was created with empty employeeId and companyId.
How do make POST to REST API whose model uses @EmbeddedId with spring boot data rest?
Upvotes: 4
Views: 3991
Reputation: 3423
Here is an other solution. (Still not perfect though.)
Expose the id for your Employee class:
@Configuration
protected class MyRepositoryRestConfigurer implements RepositoryRestConfigurer {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(ThemeMessage.class);
}
}
Add the following line to your converter (during POST requests the id will be null):
@Override
public Serializable fromRequestId(String id, Class<?> aClass) {
if(id==null) {
return null;
}
String[] parts = id.split("_");
return new EmployeeIdentity(parts[0], parts[1]);
}
The following POST
request then will work:
{
"id": {
"employeeId": "E-267",
"companyId": "D-432"
},
"name": "Spider Man",
"email": "[email protected]",
"phoneNumber": "+91-476253455"
}
However, the id field will be exposed in all of the responses. But maybe it's not a real problem, because when you use a composite id it usually means that the id is not only an abstract identifier, but its parts have meaningful content which should appear in the entity body.
Actually, I'm thinking of adding these lines to my own code too .... :)
Upvotes: 3
Reputation: 3423
I had a similar problem and I couldn't find a solution for creating new entities via the POST /entities
endpoint.
However, you can also create a new entity via PUT /entities/{newId}
endpoint. And the converter works fine for these endpoints.
I also completely denied the POST endpoint avoiding the 500 responses:
@PostMapping(value = "/themeMessages")
public ResponseEntity<Void> postThemeMessage() {
return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED);
}
Upvotes: 0