스프링 부트 @ResponseBody는 엔티티 ID를 직렬화하지 않습니다.


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를 제외한 모든 필드가 있습니다. Id도 직렬화하도록 스프링 부트를 구성하려면 어떻게해야합니까?

이제 응답이 이렇게 생겼습니다.

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

UPD 양방향 최대 절전 매핑이 있으며 문제와 관련이있을 수 있습니다.


스프링 설정에 대한 더 많은 통찰력을 제공해 주시겠습니까? 어떤 json 마샬 러를 사용합니까? 기본값은 jackson, ...?
Ota

실제로 특별한 설정은 없습니다. spring-boot-starter-data-rest를 pom에 추가하고 @EnableAutoConfiguration을 사용하는 것이 전부입니다. 몇 가지 자습서를 읽고 모두가 상자에서 벗어나야하는 것처럼 보입니다. 그리고 이드 필드를 제외하고는. 엔드 포인트 응답으로 업데이트 된 게시물.
Konstantin

1
Spring 4에서는 @RestController컨트롤러 클래스 에서도 사용 하고 @ResponseBody메서드에서 제거 해야 합니다. 또한 도메인 개체 대신 json 요청 / 응답을 처리하는 DTO 클래스를 사용하는 것이 좋습니다.
Vaelyr

답변:


139

나는 최근에 같은 문제가 있었고 그것이 spring-boot-starter-data-rest기본적으로 작동 하는 방식 이기 때문입니다 . 내 SO 질문을 참조하십시오-> 앱을 Spring Boot로 마이그레이션 한 후 Spring Data Rest를 사용하는 동안 @Id가있는 엔티티 속성이 더 이상 JSON으로 마샬링되지 않음을 관찰했습니다.

작동 방식을 사용자 지정하려면 RepositoryRestConfigurerAdapter특정 클래스에 대한 ID를 노출 하도록 확장 할 수 있습니다.

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
! 그리고 엔티티 클래스의 ID에 대한 getter와 setter 지금 지원하는 것을 잊지 마세요 .. (나는 그것을 잊고 그것을위한 많은 시간을 검색했다)

3
찾을 coudnt RepositoryRestConfigurerAdapter org.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.RepositoryRestConfigurerAdapterRepositoryRestConfigurer


특정 수퍼 클래스 또는 인터페이스 를 확장하거나 구현하는 엔티티의 식별자 만 노출하려는 경우 :

    ...
    @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 {}

첫 번째 코드 블록을 사용한다면 어떤 디렉토리와 파일로 들어갈 수 있습니까?
David Krider

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.
Dimitri Kopriwa

나는 추가 시도 @ConditionalOnBean(EntityManager.class)에서을 MyRepositoryRestConfigurerAdapter하지만, 메소드가 호출되지 않고 ID가 아직 노출되지 않습니다. 나는 스프링 데이터 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
이 솔루션이 @ eric-peladan이 제안한 것과 달리 Spring Boot v1.3.3.RELEASE에서 제대로 작동하는지 확인했습니다.
Poliakoff

4

Spring Boot를 사용하면 RepositoryRestMvcConfiguration 을 사용하는 경우 SpringBootRepositoryRestMvcConfiguration 을 확장
해야합니다 . application.properties에 정의 된 구성이 작동하지 않을 수 있습니다.

@Configuration
public class MyConfiguration extends SpringBootRepositoryRestMvcConfiguration  {

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

그러나 일시적인 필요를 위해 프로젝션을 사용하여 다음 과 같이 직렬화에 id 를 포함 할 수 있습니다 .

@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
이러한 코드 냄새의 오, 남자는 ... 정말, 그렇게하지 않습니다
이고르 Donin

@IgorDonin은 변수 / 클래스 / 함수 이름에서 의미론을 스니핑하고 크롤링하는 것을 좋아하는 Java 커뮤니티에게 항상, 어디서나 말합니다.
EralpB 2017

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 문제를 어떻게 해결하는지 설명하는 것이 도움이됩니다. :)
Joel

1

흠, 그래 내가 해결책을 찾은 것 같습니다. pom 파일에서 spring-boot-starter-data-rest를 제거하고 @JsonManagedReference를 phoneNumbers에 추가하고 @JsonBackReference를 사람에게 추가하면 원하는 출력이 제공됩니다. 응답의 Json은 더 이상 인쇄되지 않지만 이제는 이드가 있습니다. 이 종속성으로 인해 매직 스프링 부츠가 어떤 작업을 수행하는지 모르지만 마음에 들지 않습니다. :)


스프링 데이터 저장소를 REST 엔드 포인트로 노출하기위한 것입니다. 해당 기능을 원하지 않는 경우 생략 할 수 있습니다. id는 ModuleSDR에 등록 된 Jackson 과 관련이 있다고 믿습니다.
데이브 Syer

1
RESTful API는 외부 시스템에 아무런 의미가 없으므로 대리 키 ID를 노출해서는 안됩니다. RESTful 아키텍처에서 ID는 해당 리소스의 표준 URL입니다.
Chris DaMour

4
전에 읽어 봤지만 솔직히 이드없이 프론트 엔드와 백엔드를 연결하는 방법을 모르겠습니다. 삭제 / 업데이트 작업을 위해 ID를 orm에 전달해야합니다. 링크가있을 수 있습니다. 어떻게 진정한 RESTful 방식으로 수행 할 수 있습니까? :)
Konstantin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.