Spring boot @ResponseBody ไม่ซีเรียลไลซ์รหัสเอนทิตี


98

มีปัญหาแปลก ๆ และคิดไม่ออกว่าจะจัดการอย่างไร มี 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 marshaller อะไร ค่าเริ่มต้นแจ็คสัน ... ?
โอตะ

จริงๆแล้วไม่มีการตั้งค่าพิเศษ อยากลองสปริงบูต :) เพิ่ม spring-boot-starter-data-rest ให้กับ pom และใช้ @EnableAutoConfiguration นั่นคือทั้งหมด อ่านบทแนะนำสองสามบทและดูเหมือนว่าทุกคนต้องทำงานนอกกรอบ และเป็นยกเว้นฟิลด์รหัสนั้น อัปเดตโพสต์พร้อมการตอบสนองปลายทาง
Konstantin

1
ใน Spring 4 คุณควรใช้@RestControllerในคลาสคอนโทรลเลอร์และลบออก@ResponseBodyจากเมธอด นอกจากนี้ฉันขอแนะนำให้มีคลาส DTO เพื่อจัดการคำขอ / การตอบกลับ json แทนอ็อบเจ็กต์โดเมน
Vaelyr

คำตอบ:


141

เมื่อเร็ว ๆ นี้ฉันมีปัญหาเดียวกันและเป็นเพราะนั่นเป็นวิธีการspring-boot-starter-data-restทำงานตามค่าเริ่มต้น ดูคำถาม SO ของฉัน -> ในขณะที่ใช้ Spring Data Rest หลังจากย้ายแอปไปยัง Spring Boot ฉันสังเกตเห็นว่าคุณสมบัติของเอนทิตีที่มี @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
และอย่าลืมสนับสนุนตอนนี้ getter และ setter สำหรับ id ในคลาสเอนทิตี! .. (ฉันลืมไปแล้วและกำลังค้นหาเวลานานมาก)
phil

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

2
ผลตอบแทน: The type RepositoryRestConfigurerAdapter is deprecated. ดูเหมือนจะไม่ทำงาน
GreenAsJade

2
แน่นอนRepositoryRestConfigurerAdapterเลิกใช้งานแล้วในเวอร์ชันล่าสุดคุณต้องใช้งานRepositoryRestConfigurerโดยตรง (ใช้implements RepositoryRestConfigurerแทนextends RepositoryRestConfigurerAdapter)
Yann39

50

ในกรณีที่คุณต้องการเปิดเผยตัวระบุสำหรับเอนทิตีทั้งหมด :

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.RepositoryRestConfigurerAdapterแทนที่จะใช้งานRepositoryRestConfigurerโดยตรง


หากคุณต้องการเพียงแค่เปิดเผยตัวระบุของเอนทิตีที่ขยายหรือใช้งานซูเปอร์คลาสหรืออินเทอร์เฟซเฉพาะ :

    ...
    @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 ยังไม่เปิดเผย ฉันใช้ข้อมูล Spring กับข้อมูลสปริง 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
ยืนยันว่าโซลูชันนี้ใช้งานได้ดีภายใต้ Spring Boot v1.3.3 ปล่อยออกมาไม่เหมือนกับที่เสนอโดย @ eric-peladan
Poliakoff

5

คลาส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


4

ด้วย Spring Boot คุณต้องขยายSpringBootRepositoryRestMvcConfiguration
ถ้าคุณใช้RepositoryRestMvcConfigurationการกำหนดค่ากำหนดใน 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

2

วิธีง่ายๆ: เปลี่ยนชื่อตัวแปรของคุณprivate Long id;เป็นprivate Long Id;

เหมาะสำหรับฉัน คุณสามารถอ่านเพิ่มเติมได้ที่นี่


13
โอ้มนุษย์ ... นั่นเป็นกลิ่นรหัส ... จริงๆอย่าทำอย่างนั้น
Igor Donin

@IgorDonin กล่าวถึงชุมชน Java ที่ชอบดมกลิ่นและรวบรวมข้อมูลความหมายจากชื่อตัวแปร / คลาส / ฟังก์ชัน ... ตลอดเวลาทุกที่
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! เมื่อโพสต์รหัสในคำตอบของคุณการอธิบายว่าโค้ดของคุณแก้ปัญหา OP ได้อย่างไร :)
Joel

1

อืมโอเคดูเหมือนว่าฉันจะพบทางออกแล้ว การลบ spring-boot-starter-data-rest ออกจากไฟล์ pom และเพิ่ม @JsonManagedReference ไปยัง phoneNumbers และ @JsonBackReference ถึงบุคคลจะให้ผลลัพธ์ที่ต้องการ Json ในการตอบกลับไม่ได้พิมพ์ออกมาสวย ๆ อีกต่อไป แต่ตอนนี้มี Id ไม่รู้ว่าเมจิกสปริงบูตทำงานภายใต้ประทุนด้วยการพึ่งพานี้ แต่ฉันไม่ชอบ :)


ใช้สำหรับเปิดเผยที่เก็บข้อมูล Spring ของคุณเป็นปลายทาง REST หากคุณไม่ต้องการคุณสมบัตินั้นคุณสามารถละทิ้งได้ สิ่งที่ต้องทำกับแจ็คสันModuleที่ลงทะเบียนโดย SDR ฉันเชื่อว่า
Dave Syer

2
RESTful API ไม่ควรเปิดเผยรหัสคีย์ตัวแทนเนื่องจากไม่มีความหมายอะไรกับระบบภายนอก ในสถาปัตยกรรม RESTful ID คือ URL ตามรูปแบบบัญญัติสำหรับทรัพยากรนั้น
Chris DaMour

4
ฉันเคยอ่านสิ่งนี้มาก่อน แต่จริงๆแล้วฉันไม่เข้าใจว่าฉันจะเชื่อมโยงส่วนหน้าและส่วนหลังโดยไม่มี Id ได้อย่างไร ฉันต้องส่ง Id ไปยัง orm เพื่อดำเนินการลบ / อัปเดต บางทีคุณอาจมีลิงค์บางส่วนจะดำเนินการอย่างไรในทางที่ดีจริง :)
Konstantin

0

เพียงแค่เพิ่ม คำอธิบายประกอบ@JsonPropertyลงใน Id ก็ใช้งานได้

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonProperty
private long id;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.