Spring Data REST - Detected multiple association links with same relation type
Asked Answered
C

1

12

I am trying to doing a simple Spring app. It needs to expose REST endpoints and save it to a relational database.

I took your sample project, http://spring.io/guides/gs/accessing-data-rest/. I am able to do all the operations( POST, PATCH, PUT, GET) as mentioned in your guide.

However I tried creating adding relationships to Person Entity class and things start to fall apart.

@Entity
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String firstName;
    private String lastName;

    @OneToOne(cascade = {CascadeType.ALL})
    private PersonDetails personDetails;

    @OneToOne(cascade = {CascadeType.ALL})
    private PersonChildren personChildren;

     ///Getter and setters for everything except id.
}


@Entity
public class PersonChildren {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String childFirstName;
    private String childLastName;

    @OneToOne(mappedBy="personChildren", optional=false)
    private Person person;

///Getter and setters for everything except id.
}


@Entity
public class PersonDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String email;
    private String phoneNumber;

    @OneToOne(mappedBy="personDetails",optional=false)
    private Person person;

///Getter and setters for everything except id.
}


@RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {

    List<Person> findByLastName(@Param("name") String name);

}

build.gradle

buildscript {
    repositories {
        maven { url "http://repo.spring.io/libs-release" }
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.1.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar {
    baseName = 'gs-accessing-data-rest'
    version = '0.1.0'
}

repositories {
    mavenLocal()
    mavenCentral()
    maven { url "http://repo.spring.io/libs-release" }
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("com.h2database:h2")
    compile("org.springframework.data:spring-data-rest-webmvc")
}

task wrapper(type: Wrapper) {
    gradleVersion = '1.11'
}

Call:

$ curl -i -X POST -H "Content-Type:application/json" -d '{ "firstName":"John", "lastName": "Doe", "personDetails": { "email": "[email protected]", "phoneNumber": "001-002-0003" }, "personChildren": {"childFirstName": "Mary", "childLastName": "Martin" } }' <code> http://localhost:8080/people </code>

Response: 
HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
<code>
Location: http://localhost:8080/people/1
</code>
Content-Length: 0
Date: Thu, 26 Jun 2014 05:42:45 GMT

$ curl  http://localhost:8080/people

{
  "timestamp" : 1403761371011,
  "status" : 500,
  "error" : "Internal Server Error",
  "exception" : "org.springframework.http.converter.HttpMessageNotWritableException",
  "message" : "Could not write JSON: Detected multiple association links with same relation type! Disambiguate association @javax.persistence.OneToOne(optional=false, targetEntity=void, cascade=[], fetch=EAGER, orphanRemoval=false, mappedBy=personChildren) private com.ds.dao.rest.Person com.ds.dao.rest.PersonChildren.person using @RestResource! (through reference chain: org.springframework.hateoas.PagedResources[\"_embedded\"]->java.util.UnmodifiableMap[\"people\"]->java.util.ArrayList[0]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Detected multiple association links with same relation type! Disambiguate association @javax.persistence.OneToOne(optional=false, targetEntity=void, cascade=[], fetch=EAGER, orphanRemoval=false, mappedBy=personChildren) private com.ds.dao.rest.Person com.ds.dao.rest.PersonChildren.person using @RestResource! (through reference chain: org.springframework.hateoas.PagedResources[\"_embedded\"]->java.util.UnmodifiableMap[\"people\"]->java.util.ArrayList[0])",
  "path" : "/people"
}

Question 1: I am able to do a post but my GET keeps failing.

Question 2: Why am I getting this error when Post succeeds?

Question 3: Is there a good Spring Guide that will help with REST and JPA? If you are still working on these modules what examples can I look at?

Question 4: Is @RepositoryRestResource the problem? It is not recognized unless I add spring-data-rest-webmvc as dependency.

This is similar to the unanswered question Spring Data Rest Ambiguous Association Exception

Update:

It is working with only one OneToOne mapping in Person class. If I add both classes, personDetails and personChildren in Person with OneToOne mapping. It is NOT working.

I also tried adding @JointColumn(name="person_details") and @JointColumn(name="person_children") to personDetails and personChildren. It did NOT work either.

Crowe answered 26/6, 2014 at 9:13 Comment(0)
R
15

The reason for that is pretty simple: the relation names for associated entities are derived from the property names of the containing class. So both PersonDetails and PersonChildren want to create an outbound link to a Person named person. If we rendered that, it would look something like this

{ _links : { 
   person : { href : … }, <- the one from PersonDetails
   person : { href : … }  <- the one from PersonChildren
}

This is of course invalid. Also, lining up the two links in an array would not allow you to distinguish between the two links anymore (which one is coming from PersonDetails and which one is coming from PersonChildren.

So there are a few options here:

  1. Manually name the relations of those types. You can annotate the Person properties with @RestResource and configure the rel attribute of the annotation to something different than person.
  2. You want any of the two not exported at all. The very same annotation can be used to prevent the link from being rendered at all. Simply set the exported flag in @RestResource to false and the link will not be rendered. This might be useful if the pointer e.g. from PersonDetails is just relevant within the code, but actually not in a JSON representation.
Riki answered 27/6, 2014 at 8:1 Comment(2)
Oliver, this worked like a charm. I used option 1. Thanks for all your help.Crowe
I tried both the options, none of them are working from me.Fingertip

© 2022 - 2024 — McMap. All rights reserved.