Весняне завантаження @ResponseBody не серіалізує ідентифікатор об'єкта


95

У вас є дивна проблема і я не можу зрозуміти, як з цим боротися. Простий 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?

Ось як виглядає зараз відповідь:

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

UPD Майте двонаправлене відображення в сплячому режимі, можливо, це якось пов’язано з проблемою.


Чи можете ви, будь-ласка, дати нам більше інформації про ваше весняне налаштування? Який json marshaller ви використовуєте? Типовий, Джексон, ...?
Ота,

Насправді спеціальних налаштувань немає. Хотів спробувати spring boot :) Додав spring до boot-starter-data-rest до pom і використовуючи @EnableAutoConfiguration, і все. Прочитайте кілька підручників і, здається, всі повинні працювати нестандартно. І воно є, за винятком того поля Id. Оновлений пост із відповіддю на кінцеву точку.
Костянтин

1
Навесні 4 слід також скористатись @RestControllerкласом контролера та видалити його @ResponseBodyз методів. Також я б запропонував мати класи DTO для обробки запитів / відповідей json замість об'єктів домену.
Vaelyr

Відповіді:


139

Нещодавно у мене була та сама проблема, і це тому, що це 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
І не забудьте підтримати зараз getter і setter для ідентифікатора в класі сутності! .. (я забув це і шукав багато часу для цього)
phil

3
coudnt find RepositoryRestConfigurerAdapter inorg.springframework.data.rest.webmvc.config
Говінд Сінгх

2
Урожайність: The type RepositoryRestConfigurerAdapter is deprecated. Також не здається, що працює
GreenAsJade

2
Справді RepositoryRestConfigurerAdapter, застаріле в останніх версіях, вам доведеться реалізовувати його RepositoryRestConfigurerбезпосередньо (використовувати implements RepositoryRestConfigurerзамість extends RepositoryRestConfigurerAdapter)
Yann39

48

Якщо вам потрібно виставити ідентифікатори для всіх сутностей :

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)в MyRepositoryRestConfigurerAdapter, але метод не викликається, ідентифікатор все ще не піддається впливу. Я використовую дані Spring з даними spring mybatis: github.com/hatunet/spring-data-mybatis
Dimitri

@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! Розміщуючи код у своїх відповідях, корисно пояснити, як ваш код вирішує проблему ОП :)
Джоель

1

Гм, добре, схоже, я знайшов рішення. Видалення spring-boot-starter-data-rest з файлу pom та додавання @JsonManagedReference до phoneNumbers та @JsonBackReference до особи дає бажаний результат. Json у відповідь вже не надрукований, але тепер він має Id. Не знаю, що чарівне весняне завантаження виконує під капотом з цією залежністю, але мені це не подобається :)


Це для виставлення ваших сховищ Spring Data як кінцевих точок REST. Якщо ви не хочете цієї функції, можете залишити її. Ідея полягає у тому, що стосується Джексона, Moduleякий, на мою думку, зареєстрований SDR.
Dave Syer

1
API RESTful не повинні виставляти ідентифікатори сурогатних ключів, оскільки вони нічого не означають для зовнішніх систем. У архітектурі RESTful ідентифікатор є канонічною URL-адресою для цього ресурсу.
Кріс ДаМур

4
Я вже читав це раніше, але чесно кажучи, я не розумію, як я можу зв'язати передній та задній кінці без ідентифікатора. Я повинен передати Id до orm для операції видалення / оновлення. Можливо, у вас є якесь посилання, як це можна виконати по-справжньому ВИПУСКУЄМО :)
Костянтин
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.