Spring boot @ResponseBody không tuần tự hóa id tổ chức


95

Có một vấn đề lạ và không thể tìm ra cách để giải quyết nó. Có POJO đơn giản:

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

và điểm cuối còn lại:

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

Trong phản hồi json có tất cả các trường ngoại trừ Id, mà tôi cần ở phía front end để chỉnh sửa / xóa người. Làm cách nào để tôi có thể cấu hình khởi động mùa xuân để serialize Id?

Đó là cách phản hồi trông giống như bây giờ:

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

UPD Có bản đồ ngủ đông hai chiều, có thể bằng cách nào đó nó liên quan đến vấn đề.


Bạn có thể vui lòng cung cấp cho chúng tôi thêm thông tin chi tiết về thiết lập mùa xuân của bạn không? Bạn sử dụng json marshaller nào? Cái mặc định, jackson, ...?
Ota

Trên thực tế không có thiết lập đặc biệt. Muốn thử khởi động mùa xuân :) Đã thêm spring-boot-starter-data-rest vào pom và sử dụng @EnableAutoConfiguration là xong. Đọc vài hướng dẫn và có vẻ như tất cả đều phải làm việc hiệu quả. Và nó là, ngoại trừ trường Id đó. Đã cập nhật bài đăng với phản hồi điểm cuối.
Konstantin

1
Trong Spring 4, bạn cũng nên sử dụng @RestControllertrên lớp bộ điều khiển và loại bỏ @ResponseBodykhỏi các phương thức. Ngoài ra, tôi sẽ đề xuất có các lớp DTO để xử lý các yêu cầu / phản hồi json thay vì các đối tượng miền.
Vaelyr

Câu trả lời:


139

Gần đây tôi đã gặp vấn đề tương tự và đó là vì đó là cách spring-boot-starter-data-resthoạt động theo mặc định. Xem câu hỏi SO của tôi -> Trong khi sử dụng Spring Data Rest sau khi di chuyển một ứng dụng sang Spring Boot, tôi đã quan sát thấy rằng các thuộc tính thực thể với @Id không còn được sắp xếp thành JSON nữa

Để tùy chỉnh cách nó hoạt động, bạn có thể mở rộng RepositoryRestConfigurerAdapterđể hiển thị ID cho các lớp cụ thể.

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
! Và đừng quên để hỗ trợ tại getter và setter cho id trong lớp thực thể .. (tôi quên nó và đã tìm kiếm nhiều thời gian cho điều đó)
phil

3
coudnt find RepositoryRestConfigurerAdapter inorg.springframework.data.rest.webmvc.config
Govind Singh

2
Sản lượng: The type RepositoryRestConfigurerAdapter is deprecated. Cũng không xuất hiện để làm việc
GreenAsJade

2
Quả thực RepositoryRestConfigurerAdapterkhông được dùng trong các phiên bản mới nhất, bạn phải triển khai RepositoryRestConfigurertrực tiếp (sử dụng implements RepositoryRestConfigurerthay vì extends RepositoryRestConfigurerAdapter)
Yann39

48

Trong trường hợp bạn cần để lộ mã nhận dạng cho tất cả các thực thể :

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

Lưu ý rằng trong các phiên bản Spring Boot trước đó, 2.1.0.RELEASEbạn phải mở rộng (hiện không được dùng nữa) org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter thay vì triển khai RepositoryRestConfigurertrực tiếp.


Nếu bạn chỉ muốn hiển thị số nhận dạng của các thực thể mở rộng hoặc triển khai siêu lớp hoặc giao diện cụ thể :

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

Nếu bạn chỉ muốn hiển thị số nhận dạng của các thực thể với một chú thích cụ thể :

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

Chú thích mẫu:

import java.lang.annotation.*;

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

Nếu một người sử dụng khối mã đầu tiên, nó có thể đi vào thư mục và tệp nào?
David Krider

1
@DavidKrider: Nó phải nằm trong tệp riêng của nó, trong bất kỳ thư mục nào được quét thành phần . Bất kỳ gói phụ nào bên dưới ứng dụng chính của bạn (với @SpringBootApplicationchú thích) sẽ ổn.
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

Tôi đã cố gắng thêm @ConditionalOnBean(EntityManager.class)vào MyRepositoryRestConfigurerAdapter, nhưng phương thức không được gọi và id vẫn không được hiển thị. Tôi sử dụng dữ liệu mùa xuân với dữ liệu mùa xuân mybatis: github.com/hatunet/spring-data-mybatis
Dimitri Kopriwa

@DimitriKopriwa: Đây EntityManagerlà một phần của đặc tả JPA và MyBatis không triển khai JPA (hãy xem MyBatis có tuân theo JPA không? ). Vì vậy, tôi nghĩ bạn nên định cấu hình từng thực thể một, sử dụng config.exposeIdsFor(...)phương pháp như trong câu trả lời được chấp nhận .
lcnicolau

24

Câu trả lời từ @ eric-peladan không hoạt động tốt, nhưng khá gần gũi, có thể điều đó đã hoạt động với các phiên bản trước của Spring Boot. Bây giờ đây là cách nó được định cấu hình để thay thế, hãy sửa cho tôi nếu tôi sai:

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
Đã xác nhận rằng giải pháp này hoạt động tốt trong Spring Boot v1.3.3.RELEASE không giống như giải pháp do @ eric-peladan đề xuất.
Poliakoff

4

Với Spring Boot, bạn phải mở rộng SpringBootRepositoryRestMvcConfiguration
nếu bạn sử dụng RepositoryRestMvcConfiguration , cấu hình xác định trong ứng dụng. Properties có thể không hoạt động

@Configuration
public class MyConfiguration extends SpringBootRepositoryRestMvcConfiguration  {

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

Nhưng đối với nhu cầu tạm thời, bạn có thể sử dụng phép chiếu để bao gồm id trong tuần tự hóa như:

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

}


Trong khởi động mùa xuân 2, điều này không hoạt động. Lớp RepositoryRestMvcConfiguration không có configRepositoryRestConfiguration để ghi đè.
pitchblack408

4

Lớp RepositoryRestConfigurerAdapterđã không được chấp nhận kể từ 3.1, hiện thực RepositoryRestConfigurertrực tiếp.

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

Phông chữ: https://docs.spring.io/spring-data/rest/docs/current-SNAPSHOT/api/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurer.html


2

Cách dễ dàng: đổi tên biến của bạn private Long id;thànhprivate Long Id;

Làm việc cho tôi. Bạn có thể đọc thêm về nó ở đây


13
oh, con người ... đó là một mã như mùi ... thực sự, không làm điều đó
Igor Donin

@IgorDonin nói điều đó với cộng đồng Java, những người thích tìm hiểu và thu thập ngữ nghĩa từ các tên biến / lớp / hàm ... mọi lúc, mọi nơi.
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
Chào mừng đến với SO! Khi đăng mã trong câu trả lời của bạn, nó là hữu ích để giải thích cách mã của bạn giải quyết vấn đề của OP :)
Joel

1

Hm, ok có vẻ như tôi đã tìm thấy giải pháp. Xóa spring-boot-starter-data-rest khỏi tệp pom và thêm @JsonManagedReference vào phoneNumbers và @JsonBackReference cho người dùng sẽ cho kết quả mong muốn. Json đáp lại không được in đẹp nữa nhưng bây giờ nó có Id. Không biết chiếc giày lò xo kỳ diệu có tác dụng gì với sự phụ thuộc này nhưng tôi không thích nó :)


Nó để hiển thị kho dữ liệu Spring của bạn dưới dạng điểm cuối REST. Nếu bạn không muốn tính năng đó, bạn có thể bỏ nó đi. ModuleTôi tin rằng điều id là làm với một Jackson đã được đăng ký bởi SDR.
Dave Syer,

1
RESTful API không nên để lộ ID khóa thay thế vì chúng không có ý nghĩa gì đối với các hệ thống bên ngoài. Trong kiến ​​trúc RESTful, ID là URL chuẩn cho tài nguyên đó.
Chris DaMour

4
Tôi đã đọc điều này trước đây, nhưng thành thật mà nói, tôi không hiểu làm thế nào tôi có thể liên kết giao diện người dùng và giao diện người dùng mà không có Id. Tôi phải chuyển Id cho orm để thực hiện thao tác xóa / cập nhật. Có lẽ bạn có một số liên kết, làm thế nào nó có thể được thực hiện theo cách RESTful đúng :)
Konstantin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.