I'm using Spring Boot 1.5.7, Spring Data REST, Spring JPA, Hibernate, Spring HATEOAS, Spring Validation, Swagge in my web application. This application provides REST endpoints that will be consumed by an Angular client.
Introduction
In my model a lot of entities have a list of Note
. User, Ticket, Refund, etc have a List<Note>
.
According to some documents I found online, this seems a good example in which use @Any.
This would be convenient so I could also avoid the join tables (User_note, Ticket_note,Refund_note, etc).
Let's see an example of the model:
@Entity
public class Note extends AbstractEntity {
private static final long serialVersionUID = -5062313842902549565L;
@Lob
private String text;
@RestResource(rel = "parent")
@Any(fetch = FetchType.LAZY, optional = false, metaColumn = @Column(name ="item_type"))
@AnyMetaDef(idType = "long", metaType = "string", metaValues = {
@MetaValue(targetEntity = User.class, value = "User"),
@MetaValue(targetEntity = Ticket.class, value = "Ticket"),
@MetaValue(targetEntity = Refund.class, value = "Refund") })
@JoinColumn(name = "item_id")
private AbstractEntity parent;
Like you can see the parent is AbstractEntity
that is superclass of every entity bean.
@Entity
public class User extends AbstractEntity {
private String name;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Note> notes = new ArrayList<>();
Doing this, Hibernate create a Note table with two additional columns (id and type) to map the related object.
So far everything is like expected. The problem is on the REST side. Few actions are mainly needed:
- insert a note for a specific parent entity (User, Ticket, etc)
- get the list of notes of a specific entity
- delete a note from the list of a specific entity
Spring Data REST publishes endpoints based on repositories you create. I created a repository for every entities.
The problem
At this point problems arise. If you want to add a note to a User, for example, you would expect to call POST /api/v1/users/{id}/notes
, but unfortunately you can't according to this and this. From the documentation seems you can just post links. And just because a note doens't exist without his parent, I can't save the note before and then use the link to join it to the User. Anyway, even if it would be possible I don't like the idea: I want everything is within a transaction.
Ok, then let's see the enpoint Note. You will see a POST http://localhost:8080/api/v1/notes
that seems perfect to save the Note. And it would be if not you have this exception when you try to persist a note like this:
{
"parent":
"http://localhost:8080/api/v1/users/1"
,
"text": "string",
"version": 0
}
and the exception:
{
"timestamp": "2017-10-15T22:37:06.668+0000",
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "Il valore del campo <null> deve essere di tipo <null>; è stato invece ricevuto il valore <null> di tipo <null>. Correggere il dato e riprovare.",
"path": "/api/v1/notes",
"cause": {
"exception": "com.fasterxml.jackson.databind.JsonMappingException",
"message": "Can not construct instance of it.rebus.server.model.AbstractEntity: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information\n at [Source: org.apache.catalina.connector.CoyoteInputStream@67a66b7d; line: 7, column: 5] (through reference chain: server.model.auditing.Note[\"parent\"])"
}
}
This is understandable. Spring doesn't understand which concrete class of AbstractEntity create. I hoped it could discovering that from the URI, but seems it doesn't.
Let's try to get all notes of the User then. In this case there is the endpoint GET http://localhost:8080/api/v1/users/1/notes
, but I have no luck because the endpoint return a http 405 error (I opened a related question).
Conclusion
I am wondering how to solve these problems I'm experiencing with SDR using polymorphic associations. Is there a convenient way to reach the goal offering the crud actions on Notes for every bean of the model? (I want avoid to have a specific controller for each bean).