Error inserting an entity through a REST API with Spring Boot and Neo4j

354 Views Asked by At

I am testing Neo4j with Spring Boot and I have found the following problem when I try to insert an element using Rest API I get the error that a JSON can not be serialized to an Entity. If someone can help me or explain how to change my code or how to serialize that entity I would be very grateful. My classes are... User Entity

@NodeEntity
public class User extends AbstractEntity{

    @Id
    @GeneratedValue
    private Long id;

    private String firstname, lastname;


    @NotNull @NotBlank @Email
    @Index(unique = true)
    private String email;

    @Index(unique = true)
    private String phone;

    @Relationship(type = "ADDRESS")
    private Set<Address> addresses = new HashSet<>();

    public User() {
    }
    //get & set & const....
    }

Adrress Entity

@NodeEntity
public class Address extends AbstractEntity{
    /*Update the OMG [https://neo4j.com/developer/neo4j-ogm] and remove de id.
     Keep the id in AbstractEntity*/
    @Id
    @GeneratedValue
    private Long id;

    private String street, city;

    @Relationship(type = "COUNTRY")
    private Country country;

    public Address(String street, String city, Country country) {
        this.street = street;
        this.city = city;
        this.country = country;
    }

    public Address() {
    }
    //get & set & const...
    }

Country Entity

@NodeEntity
public class Country extends AbstractEntity {
    /*Update the OMG [https://neo4j.com/developer/neo4j-ogm] and remove de id.
     Keep the id in AbstractEntity*/
    @Id
    @GeneratedValue
    private Long id;

    @Index(unique=true)
    private String code;

    private String name;

    public Country(String code, String name) {
        this.code = code;
        this.name = name;
    }

    public Country() {
    } 
  }

Abstract Entity

@EnableNeo4jAuditing
public abstract class AbstractEntity {

    public abstract Long getId();

    @Transient
    private SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");

    private Date created = new Date();

    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    @Override
    public boolean equals(Object obj) {

        if (this == obj) {
            return true;
        }

        if (getId() == null || obj == null || !getClass().equals(obj.getClass())) {
            return false;
        }
        return getId().equals(((AbstractEntity) obj).getId());

    }

    @Override
    public int hashCode() {
        return getId() == null ? 0 : getId().hashCode();
    }
}

My simple repository class for store Users

public interface UserRepository extends Neo4jRepository<User, Long> {

    User findByFirstname(String name);

    @Override
    void delete(User deleted);
}

My Services Class

@Service
public class UserService {
     private UserRepository userRepository;
    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    public Iterable<User> contact() {
        return userRepository.findAll();
    }
    public User save(User user) {
            userRepository.save(user);
            return user;
    }
    public User show(Long id) {
        return userRepository.findById(id).get();
    }
    public User update(Long id, User user) {
        User c = userRepository.findById(id).get();
        if(user.getFirstname()!=null && !user.getFirstname().trim().isEmpty())
            c.setFirstname(user.getFirstname().trim());
        if(user.getLastname()!=null && !user.getLastname().trim().isEmpty())
            c.setLastname(user.getLastname().trim());
        if(user.getEmail()!=null && !user.getEmail().trim().isEmpty())
            c.setEmail(user.getEmail().trim());
        userRepository.save(c);
        return user;
    }
    public String delete(Long id) {
        User user = userRepository.findById(id).get();
        userRepository.delete(user);

        return "";
    }
}

My Controller Class

@RestController
public class UserController {
    @Autowired
    UserService userService;

    @RequestMapping(method = RequestMethod.GET, value = "/users")
    public Iterable<User> user() {
        return userService.contact();
    }

    @RequestMapping(method = RequestMethod.POST, value = "/users")
    public String save(@RequestBody User user) {
        try {
            userService.save(user);
        }catch (org.neo4j.driver.v1.exceptions.ClientException ex){
            System.err.println("******||||||||||||[{   The User exist with the same email   }]||||||||||||******");
        return "The User exist with the same email";
        }
        return user.toString();
    }
    @RequestMapping(method=RequestMethod.GET, value="/users/{id}")
    public User show(@PathVariable Long id) {
        try{
        return userService.show(id);}
        catch (java.util.NoSuchElementException ex){
            System.err.println("******||||||||||||[{   The User do not exist    }]||||||||||||******");
        }
        return null;
    }
    @RequestMapping(method=RequestMethod.PUT, value="/users/{id}")
    public User update(@PathVariable Long id, @RequestBody User user) {
        return userService.update(id, user);
    }
    @RequestMapping(method=RequestMethod.DELETE, value="/users/{id}")
    public String delete(@PathVariable Long id) {
        return userService.delete(id);
    }

}

This is my simple scheme that I am trying to model (Neo4j is a NoSQL database is free of outline, but I try to model a simple app) enter image description here

When it tried to test the methods of the api rest, it works for me but the Country entity is not serialized to a json.

I already have data inserted into the database, which I have done using a test method declaring the objects and using the methods to save it. The problem occurs when I use the json format. When I run the curl command to test the Rest API, it does not return the country

$ curl -i -H "Accept: application/json" localhost:8088/users/1
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 27 Sep 2018 20:38:31 GMT

{"created":"2018-09-27T19:55:21.578+0000","id":1,"firstname":"Yuniel","lastname":"Acosta Pérez","email":"[email protected]","phone":"+999999999999","addresses":[{"created":"2018-09-27T19:55:21.578+0000","id":0,"street":"Some Stree","city":"Some City","country":null}]}

As you can see, the country returns it as a null value and if it exists in the database.

When I tried to insert an element using the API it lamented an error that I can not serialize the country object to a JSON.

$ curl -i -X POST -H "Content-Type: application/json" -d '{"firstname":"Yuniel","lastname":"Acosta Pérez","email":"[email protected]","phone":"+999999999999","addresses":[{"street":"Some Stree","city":"Some City","country":[{"code":"OO","name":"ANYCOUNTRY"}]}]}' localhost:8088/users

The next mistake is the one that throws me

{"timestamp":"2018-09-27T21:07:56.365+0000","status":400,"error":"Bad Request","message":"JSON parse error: Cannot deserialize instance of com.syskayzen.hypercube.domain.Address out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of com.syskayzen.hypercube.domain.Address out of START_ARRAY token\n at [Source: (PushbackInputStream); line: 1, column: 122] (through reference chain: com.syskayzen.hypercube.domain.User[\"addresses\"])","path":"/users"}

If you can tell me how to solve the problem of how to serialize the Country Entity or if it is for another reason the error.

I am using Spring Boot 2.0.5 and Spring Data Neo4j

1

There are 1 best solutions below

0
On

I believe the reason Country is coming back null in your curl command is because the query is only going 1 hop deep in the database. So, it is pulling User, then going 1 hop to pull the Address node, but then it stops. To have it pull more hops along the path, you'll need to specify a depth.

You can do this by specifying a depth in the curl command and adding a parameter for 'int depth' to pass down the repository call. This will allow you to have a dynamic depth, in case you add more hops to your application that you want to retrieve.

Service method would look something like this....

public User show(Long id, int depth) {
        return userRepository.findById(id, depth).get();
}

Documentation for this is at the following link: https://docs.spring.io/spring-data/neo4j/docs/5.1.0.RELEASE/reference/html/#reference:session:persisting-entities:save-depth