Steven.B
Steven.B

Reputation: 118

How to POST entity with relationships in REST endpoint - Kotlin Spring Data

I followed this tutorial to create a basic web app in Kotlin using Spring Boot. However, I fail to POST new entities with a many-to-one relationship to an existing resource.

My code:

@Entity
class Song(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,
    var title: String,
    @ManyToOne(fetch = FetchType.EAGER)
    var addedBy: User)

@Entity
class User(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null,
    var email: String,
    var displayName: String)
@RestController
@RequestMapping("/api/songs")
class SongController(private val repository: SongRepository) {

    @PostMapping("/")
    fun add(@RequestBody song: Song) =
        repository.save(song)

This answer and others point out that you can reference another resource using its URI, but sending the following request:

{
  "title": "Some title",
  "addedBy": "http://localhost:8080/api/users/1"
}

gets me an errors with stack trace org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of 'com.example.springboot.User' (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/users/1'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of 'com.example.springboot.User' (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/users/1')\n at [Source: (PushbackInputStream); line: 6, column: 13] (through reference chain: com.example.springboot.Song[\"addedBy\"])

I got out of this that somewhere between Jackson/Hibernate/Spring Data it fails to convert the User resource URI into a User entity, but I'm in the dark where this magic should happen.

It seems to be an issue that occurs with Kotlin specifically. All the suggestions here on SO do not solve this specific error and the tutorial itself stops just short of dealing with relationships. If it's not the right approach at all to handle relationships this way I'd be eager to know what the preferred practice would be.

Upvotes: 0

Views: 1276

Answers (2)

Daniel Rafael Wosch
Daniel Rafael Wosch

Reputation: 1064

The tutorial is using HATEOAS. See the request body where they are referencing the corresponding child entity by using

"books" : {       "href" : "http://localhost:8080/authors/1/books"     }

Meaning you should also apply this pattern to your request. Otherwise this will not work. HATEOAS allows you to directly reference the related child entities by their corresponding resource path but you need to keep the necessary structure which your posted request body is missing. Further you must support HATEOAS in your WebService / WebApi / Spring Boot Project.

What you could do:

{
  "title": "Some title",
  "addedByUserId": "1"
}

Then

@PostMapping("/")
    fun add(@RequestBody song: Song) =
        val userEntity = userRepository.findById(song.getAddedByUserId())
        Song newSong = new SongEntity();
        // map props
        newSong.setUser(userEntity)
        repository.save(song)

That code does not work but I hope you get the idea.

Further

In your code you are treating the Request Body as an Entity. Please consider to separate your incoming Class and your Entity class. This would make several things easier.

Upvotes: 2

Yayotrón
Yayotrón

Reputation: 1869

I think you're missing jackson's kotlin module, it's exactly what it was created for.

https://github.com/FasterXML/jackson-module-kotlin

Just adding this dependency in your project will cause spring to autoconfigure your object mapper with this new module. If you have a Bean with your own created objectMapper then you need to configure it manually, there's a section about this in module's README.md

Upvotes: 0

Related Questions