Spring boot @ResponseBody varlık kimliğini seri hale getirmiyor


96

Garip bir probleminiz var ve bununla nasıl başa çıkılacağını çözemiyorsunuz. Basit bir POJO'ya sahip olun:

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

ve dinlenme uç noktası:

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

Json yanıtında, kişiyi düzenlemek / silmek için ön uç tarafında ihtiyacım olan Id dışında tüm alanlar var. Spring boot'u Id'yi seri hale getirmek için nasıl yapılandırabilirim?

Yanıt şu anda böyle görünüyor:

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

UPD İki yönlü hazırda bekletme eşleştirmesi var, belki bir şekilde sorunla ilgilidir.


Bize yay kurulumunuz hakkında daha fazla bilgi verebilir misiniz? Hangi json marshaller kullanıyorsunuz? Varsayılan olan, Jackson, ...?
Ota

Aslında özel bir kurulum yok. Spring boot'u denemek istedim :) Pom'a spring-boot-starter-data-rest eklendi ve @EnableAutoConfiguration kullanarak hepsi bu. Birkaç öğretici okuyun ve her şeyin kutudan çıkarılması gerekiyor gibi görünüyor. Ve bu, Id alanı dışında. Uç nokta yanıtıyla güncellenmiş gönderi.
Konstantin

1
Spring 4'te ayrıca @RestControllerdenetleyici sınıfında kullanmalı ve @ResponseBodyyöntemlerden kaldırmalısınız . Ayrıca, etki alanı nesneleri yerine json isteklerini / yanıtlarını işlemek için DTO sınıflarına sahip olmayı öneririm.
Vaelyr

Yanıtlar:


141

Son zamanlarda aynı sorunu yaşadım ve bunun nedeni spring-boot-starter-data-restvarsayılan olarak böyle çalışıyor. SO soruma bakın -> Bir uygulamayı Spring Boot'a taşıdıktan sonra Spring Data Rest'i kullanırken, @Id'ye sahip varlık özelliklerinin artık JSON'a sıralanmadığını gözlemledim

Nasıl davrandığını özelleştirmek RepositoryRestConfigurerAdapteriçin, belirli sınıfların kimliklerini ortaya çıkaracak şekilde genişletebilirsiniz .

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
Ve şimdi varlık sınıfında alıcı ve ayarlayıcıyı desteklemeyi unutmayın! .. (unuttum ve bunun için çok zaman arıyordum)
phil

3
bulmak coudnt RepositoryRestConfigurerAdapter içindeorg.springframework.data.rest.webmvc.config
Govind Singh

2
Verim: The type RepositoryRestConfigurerAdapter is deprecated. Ayrıca işe yaramıyor
GreenAsJade

2
Indeed RepositoryRestConfigurerAdapterson sürümlerde kullanımdan kaldırılmıştır, RepositoryRestConfigurerdoğrudan uygulamanız gerekir ( implements RepositoryRestConfigureryerine kullanın extends RepositoryRestConfigurerAdapter)
Yann39

49

Tüm varlıklar için tanımlayıcıları ifşa etmeniz gerektiğinde :

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'un 2.1.0.RELEASEsizden önceki sürümlerinde doğrudan uygulama yerine (artık kullanımdan kaldırılmıştır) genişletmeniz gerektiğini unutmayın .org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapterRepositoryRestConfigurer


Yalnızca belirli süper sınıfı veya arayüzü genişleten veya uygulayan varlıkların tanımlayıcılarını ortaya çıkarmak istiyorsanız :

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

Yalnızca belirli bir ek açıklama ile varlıkların tanımlayıcılarını ortaya çıkarmak istiyorsanız :

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

Örnek açıklama:

import java.lang.annotation.*;

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

İlk kod bloğu kullanılacak olsaydı, hangi dizine ve dosyaya girebilirdi?
David Krider

1
@DavidKrider: Bileşen Taraması kapsamındaki herhangi bir dizinde kendi dosyasında olmalıdır . Ana uygulamanızın altındaki ( @SpringBootApplicationek açıklamalı) herhangi bir alt paket iyi olmalıdır.
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

Bir @ConditionalOnBean(EntityManager.class)giriş eklemeye çalıştım MyRepositoryRestConfigurerAdapter, ancak yöntem çağrılmadı ve kimlik hala açığa çıkmadı. Bahar verilerini mybatis yay verileriyle kullanıyorum: github.com/hatunet/spring-data-mybatis
Dimitri Kopriwa

@DimitriKopriwa: EntityManagerJPA şartname parçasıdır ve MyBatis JPA uygulamıyor (bakmak mu MyBatis JPA izler? ). Bu yüzden, kabul edilen cevaptakiconfig.exposeIdsFor(...) yöntemi kullanarak tüm varlıkları tek tek yapılandırmanız gerektiğini düşünüyorum .
lcnicolau

24

@ Eric-peladan'ın cevabı kutudan çıktığı gibi işe yaramadı, ancak oldukça yakındı, belki bu Spring Boot'un önceki sürümlerinde işe yaradı. Şimdi bunun yerine nasıl yapılandırılması gerekiyor, yanılıyorsam düzeltin:

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
Bu çözümün Spring Boot v1.3.3.RELEASE altında, @ eric-peladan tarafından önerilenden farklı olarak iyi çalıştığı doğrulandı.
Poliakoff

4

Spring Boot ile RepositoryRestMvcConfiguration kullanıyorsanız , SpringBootRepositoryRestMvcConfiguration'ı genişletmeniz gerekir . Application.properties dosyasında tanımlanan yapılandırma çalışmayabilir

@Configuration
public class MyConfiguration extends SpringBootRepositoryRestMvcConfiguration  {

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

Ancak geçici bir ihtiyaç için Serileştirmeye kimliği eklemek için aşağıdaki gibi projeksiyon kullanabilirsiniz :

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

}


Spring boot 2'de bu çalışmıyor. RepositoryRestMvcConfiguration sınıfında, geçersiz kılmak için configureRepositoryRestConfiguration yoktur.
pitchblack408

4

Sınıf RepositoryRestConfigurerAdapter3.1'den beri kullanımdan kaldırıldı, RepositoryRestConfigurerdoğrudan uygula.

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

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


2

Kolay yol: değişkeninizi private Long id;şu şekilde yeniden adlandırın:private Long Id;

Benim için çalışıyor. Bu konuda daha fazla bilgi bulabilirsiniz burada


13
ah, adamım ... bu böyle bir kod kokusu ... gerçekten, bunu yapma
Igor Donin

@IgorDonin bunu değişken / sınıf / işlev adlarından anlambilim koklamayı ve taramayı seven Java topluluğuna söylüyor ... her zaman, her yerde.
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'ya hoş geldiniz! Cevaplarınıza kod gönderirken, kodunuzun OP'nin problemini nasıl çözdüğünü açıklamak faydalı olacaktır :)
Joel

1

Hm, tamam çözümü buldum galiba. Spring-boot-starter-data-rest'in pom dosyasından kaldırılması ve phoneNumbers'e @JsonManagedReference ve kişiye @JsonBackReference eklenmesi istenen çıktıyı verir. Cevap olarak Json artık oldukça basılmıyor ama şimdi Id var. Bu bağımlılıkla kaputun altında sihirli yaylı botun nasıl bir performans gösterdiğini bilmiyorum ama hoşuma gitmiyor :)


Spring Data depolarınızı REST uç noktaları olarak göstermek içindir. Bu özelliği istemiyorsanız, onu dışarıda bırakabilirsiniz. İddia edilen şeyin ModuleSDR tarafından kayıtlı bir Jackson ile ilgili olduğuna inanıyorum.
Dave Syer

2
RESTful API'ler, harici sistemler için hiçbir şey ifade etmediklerinden, yedek anahtar kimliklerini ifşa etmemelidir. RESTful mimarisinde kimlik, bu kaynağın kanonik URL'sidir.
Chris DaMour

4
Bunu daha önce okudum, ancak dürüst olmak gerekirse, ön ucu ve arka ucu Id olmadan nasıl bağlayabileceğimi anlamıyorum. Silme / güncelleme işlemi için Id'yi orm'a iletmem gerekiyor. Belki bir bağlantınız vardır, gerçek RESTful bir şekilde nasıl yapılabilir :)
Konstantin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.