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 {}
lcnicolau
источник
Если бы нужно было использовать первый блок кода, в какой каталог и файл он мог бы войти?
Дэвид Кридер
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;
Бикрам
источник