Spring boot@ResponseBody 不序列化实体 ID

有一个奇怪的问题,不知道如何处理。 做一个简单的 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 之外,还有其他所有字段,我需要在前端编辑/删除 person。我如何配置弹簧启动序列化 ID 以及?

这就是现在的反应:

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

UPD 具有双向休眠映射,可能与某种问题有关。

45467 次浏览

Hm, ok seems like I found the solution. Removing spring-boot-starter-data-rest from pom file and adding @JsonManagedReference to phoneNumbers and @JsonBackReference to person gives desired output. Json in response isn't pretty printed any more but now it has Id. Don't know what magic spring boot performs under hood with this dependency but I don't like it :)

I recently had the same problem and it's because that's how spring-boot-starter-data-rest works by default. See my SO question -> While using Spring Data Rest after migrating an app to Spring Boot, I have observed that entity properties with @Id are no longer marshalled to JSON

To customize how it behaves, you can extend RepositoryRestConfigurerAdapter to expose IDs for specific classes.

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

With Spring Boot you have to extends SpringBootRepositoryRestMvcConfiguration
if you use RepositoryRestMvcConfiguration the configuration define in application.properties may not worked

@Configuration
public class MyConfiguration extends SpringBootRepositoryRestMvcConfiguration  {


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

But for a temporary need You can use projection to include id in the serialization like :

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

}

Answer from @eric-peladan didn't work out of the box, but was pretty close, maybe that worked for previous versions of Spring Boot. Now this is how it is supposed to be configured instead, correct me if I'm wrong:

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

Easy way: rename your variable private Long id; to private Long Id;

Works for me. You can read more about it here

In case you need to expose the identifiers for all entities:

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

Note that in versions of Spring Boot prior to 2.1.0.RELEASE you must extend the (now deprecated) org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter instead of implement RepositoryRestConfigurer directly.


If you only want to expose the identifiers of entities that extends or implements specific super class or interface:

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

If you only want to expose the identifiers of entities with a specific annotation:

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

Sample annotation:

import java.lang.annotation.*;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExposeId {}
@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;
}
}
}

The class RepositoryRestConfigurerAdapter has been deprecated since 3.1, implement RepositoryRestConfigurer directly.

@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

Just add @JsonProperty annotation to the Id and it works.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonProperty
private long id;

another approach is to implement RepositoryRestConfigurerAdapter in configuration. (This approach will be usefull when you have to do marshalling in many places)

Implement the RepositoryRestConfigurer and use @Configuration annotation on the class.

Here's the snippet

@Configuration
public class BasicConfig implements RepositoryRestConfigurer{


@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
// TODO Auto-generated method stub
config.exposeIdsFor(Person.class);
}
    

}

You can also use the static configuration method to easily enable exposing ids in a few lines.

From the Spring Data Rest RepsositoryRestConfigurer docs:

static RepositoryRestConfigurer withConfig(Consumer<RepositoryRestConfiguration> consumer)

Convenience method to easily create simple RepositoryRestConfigurer instances that solely want to tweak the RepositoryRestConfiguration.

Parameters: consumer - must not be null.

Since: 3.1

So this works for me in an existing @Configuration-annotated class:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;


@Configuration
public class ApplicationConfiguration {


@Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return RepositoryRestConfigurer.withConfig(repositoryRestConfiguration ->
repositoryRestConfiguration.exposeIdsFor(Person.class)
);
}
}