Spring Boot @ResponseBody没有序列化实体ID


95

有一个奇怪的问题,无法弄清楚如何处理。有简单的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之外,还有其他所有字段,我需要在前端编辑/删除人员。如何配置Spring Boot来序列化ID?

这就是现在的响应:

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

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


您能否给我们更多有关弹簧设置的见解?您使用什么json编组器?默认的是杰克逊,...?
2014年

实际上没有特殊设置。想要尝试spring boot :)在pom中添加了spring-boot-starter-data-rest,并使用了@EnableAutoConfiguration。阅读一些教程,似乎所有的东西都必须开箱即用。就是这样,除了Id字段。更新了具有端点响应的帖子。
康斯坦丁2014年

1
在Spring 4中,您还应该@RestController在控制器类上使用并@ResponseBody从方法中删除。我也建议使用DTO类来处理json请求/响应,而不是域对象。
Vaelyr

Answers:


139

我最近遇到了同样的问题,这是因为spring-boot-starter-data-rest默认情况下是这样工作的。看到我的SO问题-> 在将应用程序迁移到Spring Boot后使用Spring Data Rest时,我发现带有@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
并且不要忘记现在为实体类中的ID支持getter和setter!..(我忘记了它,并且为此花了很多时间)
phil

3
coudnt发现RepositoryRestConfigurerAdapter org.springframework.data.rest.webmvc.config
戈文德·辛格

2
收益:The type RepositoryRestConfigurerAdapter is deprecated。也似乎不起作用
GreenAsJade

2
实际上RepositoryRestConfigurerAdapter在最新版本中已弃用,您必须RepositoryRestConfigurer直接实现(使用implements RepositoryRestConfigurer代替extends RepositoryRestConfigurerAdapter
Yann39,19年

48

如果您需要公开所有实体的标识符:

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 {}

如果要使用第一段代码,它将进入哪个目录和文件?
大卫·克里德

1
@DavidKrider:它应该在其自己的文件中,在Component Scan覆盖的任何目录中。主应用程序下方(带有@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)in MyRepositoryRestConfigurerAdapter,但是未调用该方法,并且ID仍未公开。我将Spring数据与spring数据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
确认与@ eric-peladan提出的解决方案不同,该解决方案在Spring Boot v1.3.3.RELEASE下可以正常工作。
Poliakoff

4

如果使用RepositoryRestMvcConfiguration,则必须使用Spring Boot扩展SpringBootRepositoryRestMvcConfiguration,
否则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

4

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


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

嗯,好吧,好像我找到了解决方案。从pom文件中删除spring-boot-starter-data-rest,并将@JsonManagedReference添加到phoneNumbers,将@JsonBackReference添加到person,将提供所需的输出。响应中的Json不再漂亮打印,但现在有ID。不知道在这种依赖性下,什么是魔法弹簧靴,但我不喜欢它:)


用于将您的Spring Data存储库公开为REST端点。如果您不想要该功能,可以将其忽略。id的事与Module我相信由SDR注册的Jackson 有关。
戴夫·塞尔

1
RESTful API不应公开代理密钥ID,因为它们对外部系统没有任何意义。在RESTful体系结构中,ID是该资源的规范URL。
克里斯·达默

4
我之前已经读过这篇文章,但老实说,我不明白如何在没有ID的情况下链接前端和后端。我必须将ID传递给orm才能进行删除/更新操作。也许您有一些链接,如何以真正的RESTful方式执行它:)
Konstantin 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.