Spring Boot @ResponseBody serialisiert die Entitäts-ID nicht

95

Habe ein seltsames Problem und kann nicht herausfinden, wie ich damit umgehen soll. Haben Sie einfache POJO:

@Entity
@Table(name = "persons")
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "middle_name")
    private String middleName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "comment")
    private String comment;

    @Column(name = "created")
    private Date created;

    @Column(name = "updated")
    private Date updated;

    @PrePersist
    protected void onCreate() {
        created = new Date();
    }

    @PreUpdate
    protected void onUpdate() {
        updated = new Date();
    }

    @Valid
    @OrderBy("id")
    @OneToMany(mappedBy = "person", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    private List<PhoneNumber> phoneNumbers = new ArrayList<>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getMiddleName() {
        return middleName;
    }

    public void setMiddleName(String middleName) {
        this.middleName = middleName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public Date getCreated() {
        return created;
    }

    public Date getUpdated() {
        return updated;
    }

    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }

    public void addPhoneNumber(PhoneNumber number) {
        number.setPerson(this);
        phoneNumbers.add(number);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }
}

@Entity
@Table(name = "phone_numbers")
public class PhoneNumber {

    public PhoneNumber() {}

    public PhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "phone_number")
    private String phoneNumber;

    @ManyToOne
    @JoinColumn(name = "person_id")
    private Person person;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }
}

und Ruheendpunkt:

@ResponseBody
@RequestMapping(method = RequestMethod.GET)
public List<Person> listPersons() {
    return personService.findAll();
}

In der json-Antwort gibt es alle Felder außer ID, die ich auf der Front-End-Seite benötige, um eine Person zu bearbeiten / löschen. Wie kann ich Spring Boot so konfigurieren, dass es auch die ID serialisiert?

So sieht die Antwort jetzt aus:

[{
  "firstName": "Just",
  "middleName": "Test",
  "lastName": "Name",
  "comment": "Just a comment",
  "created": 1405774380410,
  "updated": null,
  "phoneNumbers": [{
    "phoneNumber": "74575754757"
  }, {
    "phoneNumber": "575757547"
  }, {
    "phoneNumber": "57547547547"
  }]
}]

UPD Bidirektionales Hibernate-Mapping, möglicherweise hängt es irgendwie mit dem Problem zusammen.

Konstantin
quelle
Könnten Sie uns bitte weitere Einblicke in Ihr Frühjahrs-Setup geben? Welchen json marshaller benutzt du? Die Standardeinstellung, Jackson, ...?
Ota
Eigentlich gibt es kein spezielles Setup. Wollte Spring Boot ausprobieren :) Spring-Boot-Starter-Daten-Rest zu Pom hinzugefügt und @EnableAutoConfiguration verwendet, das ist alles. Lesen Sie ein paar Tutorials und es scheint, als müssten alle sofort funktionieren. Und es ist, außer diesem ID-Feld. Aktualisierter Beitrag mit Endpunktantwort.
Konstantin
1
In Spring 4 sollten Sie auch die @RestControllerController-Klasse verwenden und @ResponseBodyaus Methoden entfernen . Außerdem würde ich vorschlagen, DTO-Klassen zu haben, um JSON-Anforderungen / -Antworten anstelle von Domänenobjekten zu verarbeiten.
Vaelyr

Antworten:

139

Ich hatte kürzlich das gleiche Problem und das liegt daran, dass dies spring-boot-starter-data-reststandardmäßig so funktioniert. Siehe meine SO-Frage -> Bei der Verwendung von Spring Data Rest nach der Migration einer App zu Spring Boot habe ich festgestellt, dass Entitätseigenschaften mit @Id nicht mehr in JSON gemarshallt werden

Um das Verhalten anzupassen, können Sie RepositoryRestConfigurerAdapterIDs für bestimmte Klassen verfügbar machen.

import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;

@Configuration
public class RepositoryConfig extends RepositoryRestConfigurerAdapter {
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(Person.class);
    }
}
Patrick Grimard
quelle
1
Und vergessen Sie nicht, jetzt Getter und Setter für die ID in der Entity-Klasse zu unterstützen! .. (Ich habe es vergessen und viel Zeit dafür gesucht)
Phil
3
kann nicht finden RepositoryRestConfigurerAdapter inorg.springframework.data.rest.webmvc.config
Govind Singh
2
Ausbeuten : The type RepositoryRestConfigurerAdapter is deprecated.
Scheint
2
In der Tat RepositoryRestConfigurerAdapterist in den neuesten Versionen veraltet, müssen Sie RepositoryRestConfigurerdirekt implementieren ( implements RepositoryRestConfigureranstelle von verwenden extends RepositoryRestConfigurerAdapter)
Yann39
48

Falls Sie die Bezeichner für alle Entitäten verfügbar machen müssen :

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;

import javax.persistence.EntityManager;
import javax.persistence.metamodel.Type;

@Configuration
public class RestConfiguration implements RepositoryRestConfigurer {

    @Autowired
    private EntityManager entityManager;

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(
                entityManager.getMetamodel().getEntities().stream()
                .map(Type::getJavaType)
                .toArray(Class[]::new));
    }
}

Beachten Sie, dass Sie in früheren Versionen von Spring Boot 2.1.0.RELEASEdie (jetzt veraltete) Version erweitern müssen, org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapteranstatt sie RepositoryRestConfigurerdirekt zu implementieren .


Wenn Sie nur die Bezeichner von Entitäten verfügbar machen möchten, die bestimmte Superklassen oder Schnittstellen erweitern oder implementieren :

    ...
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(
                entityManager.getMetamodel().getEntities().stream()
                .map(Type::getJavaType)
                .filter(Identifiable.class::isAssignableFrom)
                .toArray(Class[]::new));
    }

Wenn Sie nur die Bezeichner von Entitäten mit einer bestimmten Anmerkung verfügbar machen möchten :

    ...
    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(
                entityManager.getMetamodel().getEntities().stream()
                .map(Type::getJavaType)
                .filter(c -> c.isAnnotationPresent(ExposeId.class))
                .toArray(Class[]::new));
    }

Beispielanmerkung:

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExposeId {}
lcnicolau
quelle
Wenn man den ersten Codeblock verwenden würde, in welches Verzeichnis und in welche Datei könnte er gehen?
David Krider
1
@DavidKrider: Es sollte sich in einer eigenen Datei in einem beliebigen Verzeichnis befinden, das vom Komponentenscan abgedeckt wird . Jedes Unterpaket unter Ihrer Haupt-App (mit der @SpringBootApplicationAnmerkung) sollte in Ordnung sein.
lcnicolau
Field entityManager in com.myapp.api.config.context.security.RepositoryRestConfig required a bean of type 'javax.persistence.EntityManager' that could not be found.
Dimitri Kopriwa
Ich habe versucht, ein @ConditionalOnBean(EntityManager.class)In hinzuzufügen MyRepositoryRestConfigurerAdapter, aber die Methode wird nicht aufgerufen und die ID wird immer noch nicht angezeigt. Ich verwende Frühlingsdaten mit Frühlingsdaten mybatis: github.com/hatunet/spring-data-mybatis
Dimitri Kopriwa
@DimitriKopriwa: Das EntityManagerist Teil der JPA-Spezifikation und MyBatis implementiert JPA nicht (siehe Verfolgt MyBatis JPA? ). Ich denke, Sie sollten alle Entitäten einzeln konfigurieren und dabei die config.exposeIdsFor(...)Methode wie in der akzeptierten Antwort verwenden .
lcnicolau
24

Die Antwort von @ eric-peladan hat nicht sofort funktioniert, war aber ziemlich nah dran, vielleicht hat das bei früheren Versionen von Spring Boot funktioniert. So soll es stattdessen konfiguriert werden. Korrigieren Sie mich, wenn ich falsch liege:

import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;

@Configuration
public class RepositoryConfiguration extends RepositoryRestConfigurerAdapter {

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(User.class);
        config.exposeIdsFor(Comment.class);
    }
}
Salvatorelab
quelle
3
Bestätigt, dass diese Lösung unter Spring Boot v1.3.3.RELEASE einwandfrei funktioniert, im Gegensatz zu einer von @ eric-peladan vorgeschlagenen.
Poliakoff
4

Mit Spring Boot müssen Sie SpringBootRepositoryRestMvcConfiguration erweitern,
wenn Sie RepositoryRestMvcConfiguration verwenden. Die in application.properties definierte Konfiguration funktioniert möglicherweise nicht

@Configuration
public class MyConfiguration extends SpringBootRepositoryRestMvcConfiguration  {

@Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
    config.exposeIdsFor(Project.class);
}
}

Aber für einen vorübergehenden Bedarf Sie können die Projektion verwenden, um ID in die Serialisierung einzubeziehen, wie:

@Projection(name = "allparam", types = { Person.class })
public interface ProjectionPerson {
Integer getIdPerson();
String getFirstName();
String getLastName();

}}

Eric Peladan
quelle
In Spring Boot 2 funktioniert dies nicht. Die Klasse RepositoryRestMvcConfiguration muss nicht configureRepositoryRestConfiguration überschreiben.
Pitchblack408
4

Die Klasse RepositoryRestConfigurerAdapterist seit 3.1 veraltet, RepositoryRestConfigurerdirekt implementieren .

@Configuration
public class RepositoryConfiguration implements RepositoryRestConfigurer  {
	@Override
	public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
		config.exposeIdsFor(YouClass.class);
		RepositoryRestConfigurer.super.configureRepositoryRestConfiguration(config);
	}
}

Schriftart: https://docs.spring.io/spring-data/rest/docs/current-SNAPSHOT/api/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurer.html

Gisomar Junior
quelle
2

Einfacher Weg: Benennen Sie Ihre Variable private Long id;in umprivate Long Id;

Funktioniert bei mir. Sie können mehr darüber lesen Sie hier

Андрей Миронов
quelle
13
Oh Mann ... das ist so ein Code-Geruch ... wirklich, tu das nicht
Igor Donin
@IgorDonin sagt das der Java-Community, die es liebt, Semantik von Variablen- / Klassen- / Funktionsnamen zu schnüffeln und zu crawlen ... die ganze Zeit, überall.
EralpB
2
@Component
public class EntityExposingIdConfiguration extends RepositoryRestConfigurerAdapter {

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        try {
            Field exposeIdsFor = RepositoryRestConfiguration.class.getDeclaredField("exposeIdsFor");
            exposeIdsFor.setAccessible(true);
            ReflectionUtils.setField(exposeIdsFor, config, new ListAlwaysContains());
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    class ListAlwaysContains extends ArrayList {

        @Override
        public boolean contains(Object o) {
            return true;
        }
    }
}
David B.
quelle
3
Willkommen bei SO! Wenn Sie Code in Ihren Antworten veröffentlichen, ist es hilfreich zu erklären, wie Ihr Code das Problem des OP löst :)
Joel
1

Hm, ok, anscheinend habe ich die Lösung gefunden. Wenn Sie Spring-Boot-Starter-Data-Rest aus der POM-Datei entfernen und @JsonManagedReference zu phoneNumbers und @JsonBackReference zu person hinzufügen, erhalten Sie die gewünschte Ausgabe. Json als Antwort ist nicht mehr schön gedruckt, aber jetzt hat es Id. Ich weiß nicht, was Magic Spring Boot unter der Haube mit dieser Abhängigkeit macht, aber ich mag es nicht :)

Konstantin
quelle
Hiermit können Sie Ihre Spring Data-Repositorys als REST-Endpunkte verfügbar machen. Wenn Sie diese Funktion nicht möchten, können Sie sie weglassen. Die ID-Sache hat mit einem Jackson zu tun Module, der von SDR registriert ist, glaube ich.
Dave Syer
1
RESTful-APIs sollten keine Ersatzschlüssel-IDs verfügbar machen, da sie für externe Systeme nichts bedeuten. In der RESTful-Architektur ist die ID die kanonische URL für diese Ressource.
Chris DaMour
4
Ich habe das schon einmal gelesen, aber ehrlich gesagt verstehe ich nicht, wie ich Frontend und Backend ohne ID verknüpfen kann. Ich muss die ID zum Löschen / Aktualisieren an orm übergeben. Vielleicht haben Sie einen Link, wie kann es auf echte RESTful Weise durchgeführt werden :)
Konstantin