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.