Spring boot @ResponseBody не сериализует идентификатор объекта


97

У вас странная проблема, и вы не можете понять, как с ней справиться. Имейте простой 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);
    }
}

и конечная точка отдыха:

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

В ответе json есть все поля, кроме Id, который мне нужен на стороне интерфейса для редактирования / удаления человека. Как я могу настроить весеннюю загрузку для сериализации Id?

Вот так сейчас выглядит ответ:

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

UPD Есть двунаправленное отображение гибернации, возможно, это как-то связано с проблемой.


Не могли бы вы рассказать нам больше о вашей весенней настройке? Какой json marshaller вы используете? Стандартный, Джексон, ...?
Ota

Собственно специальной настройки нет. Хотел попробовать весеннюю загрузку :) Добавил в pom spring-boot-starter-data-rest и с помощью @EnableAutoConfiguration все. Прочтите пару руководств, и кажется, что все они должны работать из коробки. И это так, кроме поля Id. Обновленный пост с ответом конечной точки.
Константин

1
В Spring 4 вы также должны использовать @RestControllerкласс контроллера и удалить @ResponseBodyиз методов. Также я бы посоветовал использовать классы DTO для обработки json-запросов / ответов вместо объектов домена.
Vaelyr

Ответы:


141

У меня недавно была такая же проблема, потому что так spring-boot-starter-data-restработает по умолчанию. См. Мой вопрос SO -> При использовании Spring Data Rest после переноса приложения на Spring Boot я заметил, что свойства объекта с @Id больше не упорядочиваются в JSON

Чтобы настроить его поведение, вы можете расширить доступ RepositoryRestConfigurerAdapterк идентификаторам для определенных классов.

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);
    }
}

1
И не забудьте теперь поддерживать геттер и сеттер для идентификатора в классе сущности! .. (Я забыл об этом и много времени искал для этого)
Фил

3
не могу найти RepositoryRestConfigurerAdapter вorg.springframework.data.rest.webmvc.config
Говинд Сингх

2
Урожайность: The type RepositoryRestConfigurerAdapter is deprecated. Также, похоже, не работает
GreenAsJade 05

2
RepositoryRestConfigurerAdapterВ последних версиях Indeed устарел, вам нужно реализовать RepositoryRestConfigurerнапрямую (использовать implements RepositoryRestConfigurerвместо extends RepositoryRestConfigurerAdapter)
Yann39

49

Если вам нужно предоставить идентификаторы для всех сущностей :

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));
    }
}

Обратите внимание, что в более ранних версиях Spring Boot 2.1.0.RELEASEвы должны расширять (теперь не рекомендуется), org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter а не реализовывать RepositoryRestConfigurerнапрямую.


Если вы хотите раскрыть только идентификаторы сущностей, которые расширяют или реализуют определенный суперкласс или интерфейс :

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

Если вы хотите предоставить только идентификаторы сущностей с определенной аннотацией :

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

Пример аннотации:

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExposeId {}

Если бы нужно было использовать первый блок кода, в какой каталог и файл он мог бы войти?
Дэвид Кридер

1
@DavidKrider: он должен быть в отдельном файле, в любом каталоге, охваченном сканированием компонентов . Любой дополнительный пакет ниже вашего основного приложения (с @SpringBootApplicationаннотацией) должен подойти.
lcnicolau

Field entityManager in com.myapp.api.config.context.security.RepositoryRestConfig required a bean of type 'javax.persistence.EntityManager' that could not be found.
Дмитрий Коприва,

Я попытался добавить @ConditionalOnBean(EntityManager.class)in MyRepositoryRestConfigurerAdapter, но метод не вызывается, а идентификатор все еще не отображается. Я использую данные Spring с данными Spring mybatis: github.com/hatunet/spring-data-mybatis
Dimitri Kopriwa

@DimitriKopriwa: EntityManagerявляется частью спецификации JPA, а MyBatis не реализует JPA (посмотрите, следует ли MyBatis за JPA? ). Итак, я думаю, вам следует настроить все сущности по очереди, используя config.exposeIdsFor(...)метод, указанный в принятом ответе .
lcnicolau,

24

Ответ от @ eric-peladan не сработал из коробки, но был довольно близок, возможно, это сработало для предыдущих версий Spring Boot. Теперь вот как он должен быть настроен, поправьте меня, если я ошибаюсь:

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);
    }
}

3
Подтверждено, что это решение отлично работает под Spring Boot v1.3.3.RELEASE в отличие от решения, предложенного @ eric-peladan.
Поляков

4

С помощью Spring Boot вам нужно расширить SpringBootRepositoryRestMvcConfiguration,
если вы используете RepositoryRestMvcConfiguration, конфигурация, определенная в application.properties, может не работать

@Configuration
public class MyConfiguration extends SpringBootRepositoryRestMvcConfiguration  {

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

Но для временной необходимости вы можете использовать проекцию для включения идентификатора в сериализацию, например:

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

}


В весенней загрузке 2 это не работает. У класса RepositoryRestMvcConfiguration нет configureRepositoryRestConfiguration для переопределения.
pitchblack408

4

Класс RepositoryRestConfigurerAdapterустарел, начиная с версии 3.1, реализуйте RepositoryRestConfigurerнапрямую.

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

Шрифт: https://docs.spring.io/spring-data/rest/docs/current-SNAPSHOT/api/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurer.html.


2

Простой способ: переименуйте вашу переменную private Long id;вprivate Long Id;

Работает для меня. Вы можете прочитать об этом здесь


13
ох, чувак ... вот такой запах кода ... правда, не делай этого
Игорь Донин

@IgorDonin сказал это сообществу Java, которое любит анализировать и сканировать семантику имен переменных / классов / функций ... всегда и везде.
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;
        }
    }
}

3
Добро пожаловать в SO! При размещении кода в ваших ответах полезно объяснить, как ваш код решает проблему OP :)
Джоэл

1

Хм, ладно, похоже, я нашел решение. Удаление spring-boot-starter-data-rest из файла pom и добавление @JsonManagedReference к phoneNumbers и @JsonBackReference человеку дает желаемый результат. Json в ответ больше не печатается, но теперь у него есть Id. Не знаю, что под капотом с этой зависимостью выполняет magic spring boot, но мне это не нравится :)


Это сделано для того, чтобы ваши репозитории Spring Data отображались как конечные точки REST. Если вам не нужна эта функция, вы можете не использовать ее. ModuleЯ полагаю, что идентификатор связан с Джексоном , зарегистрированным в SDR.
Dave Syer

2
API RESTful не должны раскрывать идентификаторы суррогатных ключей, потому что они ничего не значат для внешних систем. В архитектуре RESTful идентификатор - это канонический URL-адрес этого ресурса.
Крис Дамур 01

4
Я читал это раньше, но, честно говоря, я не понимаю, как связать интерфейс и сервер без идентификатора. Мне нужно передать идентификатор в orm для операции удаления / обновления. Может быть, у вас есть ссылка, как это сделать по-настоящему RESTful :)
Константин

0

Просто добавьте аннотацию @JsonProperty к идентификатору, и все заработает .

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonProperty
private long id;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.