Spring Data REST - обнаружено множество ассоциаций с одинаковым типом отношения

Я пытаюсь сделать простое приложение Spring. Он должен выставлять конечные точки REST и сохранять их в реляционной базе данных.

Я взял ваш образец проекта http://spring.io/guides/gs/accessing-data-rest/. Я могу выполнять все операции (POST, PATCH, PUT, GET), как указано в вашем руководстве.

Однако я попытался создать добавление отношений к классу Person Entity, и все начинает разваливаться.

@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'
}

Вызов:

$ 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"
}

Вопрос 1: Я могу делать сообщение, но мой GET не работает.

Вопрос 2: Почему я получаю эту ошибку, когда сообщение успешно завершено?

Вопрос 3: Есть ли хорошее руководство Spring, которое поможет с REST и JPA? Если вы все еще работаете над этими модулями, какие примеры я могу посмотреть?

Вопрос 4: Проблема @RepositoryRestResource? Он не распознается, если я не добавлю spring -data-rest-webmvc в качестве зависимости.

Это похоже на неотвеченный вопрос Spring Исключение неопределенности ассоциации данных Rest

Обновление

Он работает только с одним отображением OneToOne в классе Person. Если я добавлю оба класса, personDetails и personChildren в Person с отображением OneToOne. Он НЕ работает.

Я также попытался добавить @JointColumn(name="person_details") и @JointColumn(name="person_children") в personDetails и personChildren. Он также НЕ работал.

Ответ 1

Причина этого довольно проста: имена отношений для связанных объектов производятся из имен свойств содержащего класса. Таким образом, оба PersonDetails и PersonChildren хотят создать исходящую ссылку на Person с именем Person. Если бы мы это сделали, это выглядело бы примерно так.

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

Это, конечно, недействительно. Кроме того, выстраивание двух ссылок в массиве не позволит вам различать две ссылки больше (какая из них идет от PersonDetails и какая из них идет от PersonChildren.

Итак, здесь есть несколько вариантов:

  • Вручную назвать отношения этих типов. Вы можете аннотировать свойства Person с помощью @RestResource и настроить атрибут rel аннотации на нечто, отличное от Person.
  • Вы хотите, чтобы любой из двух не экспортировался вообще. Сама же аннотация может использоваться для предотвращения передачи ссылки вообще. Просто установите флаг exported в @RestResource на false, и ссылка не будет отображаться. Это может быть полезно, если указатель, например. от PersonDetails имеет смысл только в коде, но фактически не в представлении JSON.