3

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:

  1. insert a note for a specific parent entity (User, Ticket, etc)
  2. get the list of notes of a specific entity
  3. 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).

drenda
  • 5,846
  • 11
  • 68
  • 141
  • I would use separate endpoints for /users and /notes. Each Note will include user id it belongs to. Get on user shall return just the ids of Notes. You can use framework like HATEOAS for navigating/rendering Note for User. This is pretty basic and clean way of handling entities with no dependencies in URL. As you mentioned you don't want multiple controllers for each bean (not sure why), you can still have one controller and use your own logic to create/manipulate beans in the controller methods. – Zaki Anwar Hamdani Oct 15 '17 at 23:09
  • @ZakiAnwarHamdani thanks for the reply. I was looking for a specific solution for SDR without using controllers but just repositories (that later SDR converts in controllers/endpoints). In that sense I don't want to create separate controller to handle this. Furthemore the question is about polymorphic association, so a Note can refer both to a Customer or a Ticket or another entity of my domain. – drenda Oct 18 '17 at 10:56

1 Answers1

2

I'm pretty sure you solved your problem but I was having the same issue recently. So I'll post how I solved it, hopefully will help anybody else.

I had to specify in the abstract class the deserialization strategy. I used:

@JsonTypeInfo(use = Id.NAME, property = "@entityType")
 @JsonSubTypes({
        @JsonSubTypes.Type(value = User.class, name = "User"),
        @JsonSubTypes.Type(value = Ticket.class, name = "Ticket"),
        @JsonSubTypes.Type(value = Refund.class, name = "Refund"),
})

So at controller level when I receive a POST request I change the received object node a little bit adding the "@entityType" property to the parent subnode based on the "http" property value as you said. Doing that small change now Spring should be able to know which concrete class should create an instance of.