Boot musim semi @ResponseBody tidak membuat serial id entitas

96

Memiliki masalah yang aneh dan tidak tahu cara mengatasinya. Miliki POJO sederhana:

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

dan titik akhir istirahat:

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

Dalam respon json ada semua field kecuali Id, yang saya butuhkan di front end untuk mengedit / menghapus orang. Bagaimana saya bisa mengkonfigurasi boot musim semi untuk membuat ID serial juga?

Seperti itulah tanggapannya sekarang:

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

UPD Memiliki pemetaan hibernasi dua arah, mungkin entah bagaimana terkait dengan masalah.

Konstantin
sumber
Bisakah Anda memberi kami lebih banyak wawasan tentang penyiapan musim semi Anda? Json marshaller apa yang Anda gunakan? Yang default, jackson, ...?
Ota
Sebenarnya tidak ada pengaturan khusus. Ingin mencoba boot musim semi :) Menambahkan spring-boot-starter-data-rest ke pom dan menggunakan @EnableAutoConfiguration itu saja. Baca beberapa tutorial dan sepertinya semua harus bekerja di luar kotak. Dan memang benar, kecuali bidang Id itu. Posting yang diperbarui dengan respons titik akhir.
Konstantin
1
Di Spring 4 Anda juga harus menggunakan @RestControllerpada kelas pengontrol dan menghapus @ResponseBodydari metode. Juga saya akan menyarankan memiliki kelas DTO untuk menangani permintaan / tanggapan json daripada objek domain.
Vaelyr

Jawaban:

140

Saya baru-baru ini mengalami masalah yang sama dan itu karena cara spring-boot-starter-data-restkerjanya secara default. Lihat pertanyaan SO saya -> Saat menggunakan Spring Data Rest setelah memigrasi aplikasi ke Spring Boot, saya telah mengamati bahwa properti entitas dengan @Id tidak lagi diatur ke JSON

Untuk menyesuaikan perilakunya, Anda dapat memperluas RepositoryRestConfigurerAdapteruntuk mengekspos ID untuk kelas tertentu.

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);
    }
}
Patrick Grimard
sumber
1
Dan jangan lupa untuk mendukung pengambil dan penyetel sekarang untuk id di kelas entitas! .. (Saya lupa dan mencari banyak waktu untuk itu)
phil
3
tidak dapat menemukan RepositoryRestConfigurerAdapter diorg.springframework.data.rest.webmvc.config
Govind Singh
2
Hasil: The type RepositoryRestConfigurerAdapter is deprecated. Juga tampaknya tidak bekerja
GreenAsJade
2
Memang RepositoryRestConfigurerAdaptersudah usang di versi terbaru, Anda harus menerapkan RepositoryRestConfigurersecara langsung (gunakan implements RepositoryRestConfigurersebagai pengganti extends RepositoryRestConfigurerAdapter)
Yann39
49

Jika Anda perlu mengekspos pengenal untuk semua entitas :

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

Perhatikan bahwa dalam versi Spring Boot sebelum 2.1.0.RELEASEAnda harus memperpanjang (sekarang sudah usang) org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter alih-alih mengimplementasikan RepositoryRestConfigurersecara langsung.


Jika Anda hanya ingin mengekspos pengenal entitas yang memperluas atau menerapkan kelas super atau antarmuka tertentu :

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

Jika Anda hanya ingin mengekspos pengenal entitas dengan anotasi tertentu :

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

Contoh anotasi:

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExposeId {}
lcnicolau.dll
sumber
Jika seseorang menggunakan blok kode pertama, direktori dan file apa yang akan dimasuki?
David Krider
1
@DavidKrider: Ini harus ada di filenya sendiri, di direktori manapun yang dicakup oleh Pemindaian Komponen . Sub paket apa pun di bawah aplikasi utama Anda (dengan @SpringBootApplicationanotasi) seharusnya baik-baik saja.
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
Saya mencoba untuk menambahkan @ConditionalOnBean(EntityManager.class)dalam MyRepositoryRestConfigurerAdapter, namun metode ini tidak disebut dan id masih tidak terkena. Saya menggunakan data Musim Semi dengan data musim semi mybatis: github.com/hatunet/spring-data-mybatis
Dimitri Kopriwa
@DimitriKopriwa: Ini EntityManageradalah bagian dari spesifikasi JPA dan MyBatis tidak mengimplementasikan JPA (lihat Apakah MyBatis mengikuti JPA? ). Jadi, saya pikir Anda harus mengonfigurasi semua entitas satu per satu, menggunakan config.exposeIdsFor(...)metode seperti pada jawaban yang diterima .
lcnicolau
24

Jawaban dari @ eric-peladan tidak langsung berhasil, tetapi cukup mendekati, mungkin berfungsi untuk Spring Boot versi sebelumnya. Sekarang beginilah seharusnya dikonfigurasi, perbaiki saya jika saya salah:

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);
    }
}
Salvatorelab
sumber
3
Dikonfirmasi bahwa solusi ini berfungsi dengan baik di bawah Spring Boot v1.3.3. RELEASE tidak seperti yang diusulkan oleh @ eric-peladan.
Poliakoff
4

Dengan Spring Boot Anda harus memperluas SpringBootRepositoryRestMvcConfiguration
jika Anda menggunakan RepositoryRestMvcConfiguration , konfigurasi yang ditentukan dalam application.properties mungkin tidak berfungsi

@Configuration
public class MyConfiguration extends SpringBootRepositoryRestMvcConfiguration  {

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

Tetapi untuk kebutuhan sementara Anda dapat menggunakan proyeksi untuk memasukkan id dalam serialisasi seperti:

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

}

eric peladan
sumber
Di boot musim semi 2, ini tidak berhasil. RepositoryRestMvcConfiguration kelas tidak memiliki configureRepositoryRestConfiguration untuk diganti.
pitchblack408
4

Kelas RepositoryRestConfigurerAdaptersudah tidak digunakan lagi sejak 3.1, terapkan RepositoryRestConfigurersecara langsung.

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

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

Gisomar Junior
sumber
2

Cara mudah: ganti nama variabel Anda private Long id;menjadiprivate Long Id;

Bekerja untuk saya. Anda dapat membaca lebih lanjut di sini

Андрей Миронов
sumber
13
oh, man ... itu bau kode ... sungguh, jangan lakukan itu
Igor Donin
@IgorDonin mengatakan bahwa untuk komunitas Java yang suka mengendus dan merayapi semantik dari nama variabel / kelas / fungsi ... setiap saat, di mana saja.
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;
        }
    }
}
David B.
sumber
3
Selamat datang di SO! Saat memposting kode dalam jawaban Anda, akan sangat membantu untuk menjelaskan bagaimana kode Anda menyelesaikan masalah OP :)
Joel
1

Hm, oke sepertinya saya menemukan solusinya. Menghapus spring-boot-starter-data-rest dari file pom dan menambahkan @JsonManagedReference ke phoneNumbers dan @JsonBackReference ke person memberikan keluaran yang diinginkan. Json sebagai tanggapan tidak cukup dicetak lagi tetapi sekarang memiliki Id. Tidak tahu apa yang dilakukan sepatu bot musim semi ajaib dengan ketergantungan ini, tetapi saya tidak menyukainya :)

Konstantin
sumber
Ini untuk mengekspos repositori Spring Data Anda sebagai titik akhir REST. Jika Anda tidak menginginkan fitur itu, Anda dapat meninggalkannya. Hal id ada hubungannya dengan Jackson Moduleyang didaftarkan oleh SDR saya percaya.
Dave Syer
2
RESTful API tidak boleh mengekspos ID kunci pengganti karena tidak berarti apa-apa bagi sistem eksternal. Dalam arsitektur RESTful, ID adalah URL kanonis untuk sumber daya itu.
Chris DaMour
4
Saya telah membaca ini sebelumnya, tapi sejujurnya saya tidak mengerti bagaimana saya bisa menghubungkan front end dan back end tanpa Id. Saya harus memberikan Id ke orm untuk operasi hapus / update. Mungkin Anda memiliki beberapa tautan, bagaimana itu dapat dilakukan dengan cara yang benar-benar tenang :)
Konstantin