3

I'm currently trying to build a REST API with Spring Boot that supports Clients and Jobs. A Client can have many Jobs, and a Job belongs to a Client. However, when I save a Job with a reference to the Client either by a client_id or trying to find the Client object, I get this error:

{
  "timestamp" : 1479163164739,
  "status" : 400,
  "error" : "Bad Request",
  "exception" : "org.springframework.http.converter.HttpMessageNotReadableException",
  "message" : "Could not read document: Can not construct instance of com.core.model.Client: no String-argument constructor/factory method to deserialize from String value ('3')\n at [Source: java.io.PushbackInputStream@5138cca1; line: 1, column: 12] (through reference chain: com.core.model.Job[\"client\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.core.model.Client: no String-argument constructor/factory method to deserialize from String value ('3')\n at [Source: java.io.PushbackInputStream@5138cca1; line: 1, column: 12] (through reference chain: com.core.model.Job[\"client\"])",
  "path" : "/api/v1/jobs"
}

Including my models and controllers for Job and Client:

My model for Job:

package com.core.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.CascadeType;
import javax.persistence.Table;
import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Id;

@Entity
@Table(name="`job`")
public class Job {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="job_id")
    private Long jobId;

    @ManyToOne(cascade=CascadeType.ALL, targetEntity = Client.class)
    @JoinColumn(name = "client_id", nullable = false)
    private Client client;

    private String address1;
    private String address2;
    private String city;
    private String county;
    private String state;
    private String zip;
    private String country;
    private String description;

    //Note: I tried Long client_id instead of using the Client object here and same thing
    public Job(String description, String address1, String address2, String city, String county,
               String state, String zip, String country, Client client) {
        this.setDescription(description);
        this.setAddress1(address1);
        this.setAddress2(address2);
        ...
        this.setClient(client);
    }

    public Job() {}

    public Long getId() { return jobId; }
    public void setId(Long id) { this.jobId = id; }
    ...
    public Client getClient() { return client; }
    public void setClient(Client client) { this.client = client; }

}

Controller for the Job:

package com.core.controller;

import com.core.model.Job;
import com.core.repository.JobRepository;
import org.apache.catalina.connector.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.core.repository.JobRepository;
import org.springframework.hateoas.Resource;

import java.util.Collection;
import java.util.List;

@RestController
@RequestMapping("/api/v1/jobs")
public class JobController {

    @Autowired
    private JobRepository repository;

    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity<Collection<Job>> getAllJob(){
        return new ResponseEntity<>((Collection<Job>) repository.findAll(), HttpStatus.OK);
    }
    @RequestMapping(method = RequestMethod.GET, value = "/{id}")
    public ResponseEntity<Job> getEquipmentWithId(@PathVariable Long id) {
        return new ResponseEntity<>(repository.findOne(id),HttpStatus.OK);
    }

    @RequestMapping(value="", method=RequestMethod.POST)
    public ResponseEntity createNew(@RequestBody Job job) {
        repository.save(job);
        return new ResponseEntity(job, HttpStatus.CREATED);
    }
}

and Client model:

package com.core.model;

import javax.persistence.Entity;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Table;
import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.Id;
import javax.persistence.OneToMany;

@Entity
@Table(name="`client`")
public class Client {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="client_id")
    @Access(AccessType.PROPERTY)
    private Long clientId;

    public Client(String description) {
        this.setDescription(description);
    }

    public Client() {}

    public Long getClientId() {
        return clientId;
    }
    public void setClientId(Long id) { this.clientId = id; }
    ...
}

I just want to save that Client id to the Job, what's the right approach here?

albaba
  • 153
  • 4
  • 12

1 Answers1

1

My guess is that you're trying to invoke this method using REST:

@RequestMapping(value="", method=RequestMethod.POST)
public ResponseEntity createNew(@RequestBody Job job) {
        repository.save(job);
        return new ResponseEntity(job, HttpStatus.CREATED);
}

Th exception is pretty clear, it talks about:

com.fasterxml.jackson.databind.JsonMappingException

Which means that whenever you're trying to pass a Job entity within the request body, Jackson is failing to bind it to your Job job parameter on your method controller.

My suggestion is to try to write Jackson deserializer class, which will tell Jackson how exactly to do the deserialization.

Here's an example of how to do it:

Right way to write JSON deserializer in Spring or extend it

Community
  • 1
  • 1
Moshe Arad
  • 3,587
  • 4
  • 18
  • 33
  • Cool, okay. So I made a Deserializer and Serializer for the Job entity and I'm able to grab the value of the client_id just like how that example does it. I guess my question is: From here, should I instantiate a new Job object with a (Long) client_id and try saving it that way (and just use client_id as the member instead of a Client object), or should I instantiate a Client object, try to find that object with the id passed, and then do something to add a new Job as a child to the Client? – albaba Nov 15 '16 at 16:32
  • I went with the latter and that worked! New to Spring and coming from a different world of ORMs so thanks so much for the help! – albaba Nov 15 '16 at 17:01
  • 1
    Cheers ;-) No problem – Moshe Arad Nov 15 '16 at 17:17