我遇到了使用 Spring Boot 启动器的 Spring Data REST 应用程序的问题。我有许多用各种关系定义的实体。我可以通过键(通常)检索它们,但是我遇到了一些相同对象的集合的问题。我想知道这是否是 JsonIdentityInfo 问题或以其他方式“破坏”唯一标识符内容以防止循环 JSON 生成。
Company 表中有大约 500 个条目,页面大小默认为 20。转到默认端点 (/company),它返回以下错误。我可以毫无问题地提取各个公司 (/company/1),包括在创建 JSON 异常时尝试生成的同一家公司。
当我进入异常堆栈时,我看到它正在尝试为 supportEmailAddress 字段生成 JSON。这是可以被多个公司行引用的行。公司也有在此表中具有电子邮件地址的联系人,但这些通常不在公司或联系人之间共享。
注意:我用类似的堆栈跟踪查看了 SO 问题,但该问题似乎围绕自定义序列化程序展开。我没有使用自定义序列化程序。
我尝试过的几件事:
- 带和不带范围属性的 JsonIdentityInfo
- JsonManagedReference 和 JsonBackReference
- @Id 字段上的 @Access(AccessType.PROPERTY)
库版本:
ext['hibernate.version'] = '5.1.0.Final'
ext['hibernateVersion'] = '5.1.0.Final'
ext['springVersion'] = '2.5.1.RELEASE'
ext['springBootVersion'] = '1.3.5.RELEASE'
ext['springDataCommonsVersion'] = '1.12.1.RELEASE'
ext['springDataJpaVersion'] = '1.10.1.RELEASE'
ext['springIntegrationVersion'] = '4.2.6.RELEASE'
ext['querydslVersion'] = '4.1.0'
ext['jacksonVersion'] = '2.8.0'
ext['jacksonJsr310Version'] = '2.8.0'
我已经尝试通过序列化进行调试,根本问题是序列化程序在处理 company.supportEmailAddress.key 时会感到困惑。它正在尝试输出键值,但序列化程序希望键名是下一个。当第二次引用相同的 supportEmailAddress id 时会发生错误。
更新到杰克逊 2.8.0。没变。
我在最后添加了一个简化的手写示例 JSON 来显示我所期望的结构。如您所见,这两个公司引用了相同的 supportEmailAddress 对象(相同的 id)。如果我更改为不同的 ID,它会正确呈现。我相信第二个引用实际上只会输出 id 而不是对象的其余部分,因为它已经序列化了一次。我的理解是这是一个标准功能,我没有做任何与更改杰克逊默认功能相关的事情。
简化实体(省略访问器):
公司:
@Entity
@Table(name = "T_Company")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "key", scope = Company.class)
public class Company extends AbstractCustomEntity<Long> {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "COMPANY_ID")
private Long key;
@Size(max = 50)
@Column(name = "NAME", nullable = false, length = 50, unique = true)
private String name;
@OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, mappedBy = "company")
private Set<Alias> aliases;
@ManyToMany(mappedBy = "company")
private Set<Owner> owner;
@OneToMany(mappedBy = "agency", cascade = CascadeType.ALL)
private Set<Contact> contacts;
@ManyToOne
@JoinColumn(name = "SUPPORT_EMAIL_ADDRESS_ID")
private EmailAddress supportEmailAddress;
所有者:
@Entity
@Table(name = "T_OWNER")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "key", scope = Owner.class)
public class Owner extends AbstractCustomEntity<Long> {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "VENDOR_ID", nullable = false, updatable = false)
private Long key;
@NotNull
@Size( max = 50 )
@Column(name = "NAME",nullable = false, length = 50, unique = true)
private String name;
@ManyToMany( cascade = CascadeType.ALL)
@JoinTable(name = "T_OWNER_COMPANY"
, joinColumns = {@JoinColumn(name = "OWNER_ID")}
, inverseJoinColumns = {@JoinColumn(name = "COMPANY_ID")}
)
private Set<Company> companies;
@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL)
private Set<Contact> contacts;
电子邮件地址:
@Entity
@Table(name = "T_EMAIL_ADDR")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "key", scope = EmailAddress.class)
public class EmailAddress extends AbstractCustomEntity<Long> {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "EMAIL_ADDRESS_ID")
private Long key;
@Column(name = "EMAIL_ADDRESS_TYPE_NME")
@Enumerated(EnumType.STRING)
private EmailAddrType emailAddrType;
@Size(max = 200)
@Email
@Column(name = "EMAIL_ADDR", length = 200, nullable = false, updatable = false)
private String emailAddress;
接触:
@Entity
@Table(name = "T_CONTACT")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "CATG", length = 6)
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "key", scope = Contact.class)
public abstract class Contact extends AbstractCustomEntity<Long> {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "CONTACT_ID")
private Long key;
@Column(name = "CONTACT_NME", length = 100)
@Size(max = 100)
private String name;
@OneToOne
@JoinColumn(name = "EMAIL_ADDRESS_ID")
private EmailAddress emailAddress;
堆栈跟踪:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: Can not write a number, expecting field name; nested exception is com.fasterxml.jackson.core.JsonGenerationException: Can not write a number, expecting field name
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:276)
at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:100)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:222)
at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:183)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
at org.springframework.data.rest.webmvc.ResourceProcessorHandlerMethodReturnValueHandler.handleReturnValue(ResourceProcessorHandlerMethodReturnValueHandler.java:113)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:126)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:832)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:743)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:961)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.AbstractRequestLoggingFilter.doFilterInternal(AbstractRequestLoggingFilter.java:220)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:87)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:522)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1095)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:672)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1502)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1458)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: com.fasterxml.jackson.core.JsonGenerationException: Can not write a number, expecting field name
at com.fasterxml.jackson.core.JsonGenerator._reportError(JsonGenerator.java:1676)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator._verifyValueWrite(UTF8JsonGenerator.java:925)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator.writeNumber(UTF8JsonGenerator.java:787)
at com.fasterxml.jackson.databind.ser.std.NumberSerializers$LongSerializer.serialize(NumberSerializers.java:188)
at com.fasterxml.jackson.databind.ser.impl.WritableObjectId.writeAsId(WritableObjectId.java:35)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase._serializeWithObjectId(BeanSerializerBase.java:584)
at com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanSerializer.serialize(UnwrappingBeanSerializer.java:114)
at com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter.serializeAsField(UnwrappingBeanPropertyWriter.java:127)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:678)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157)
at com.fasterxml.jackson.databind.SerializerProvider.defaultSerializeValue(SerializerProvider.java:985)
at org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer.serialize(PersistentEntityJackson2Module.java:193)
at org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer.serialize(PersistentEntityJackson2Module.java:140)
at com.fasterxml.jackson.databind.SerializerProvider.defaultSerializeValue(SerializerProvider.java:985)
at org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$NestedEntitySerializer.serialize(PersistentEntityJackson2Module.java:356)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:672)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:678)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase._serializeWithObjectId(BeanSerializerBase.java:600)
at com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanSerializer.serialize(UnwrappingBeanSerializer.java:114)
at com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter.serializeAsField(UnwrappingBeanPropertyWriter.java:127)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:678)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157)
at com.fasterxml.jackson.databind.SerializerProvider.defaultSerializeValue(SerializerProvider.java:985)
at org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer.serialize(PersistentEntityJackson2Module.java:193)
at org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer.serialize(PersistentEntityJackson2Module.java:140)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:616)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:519)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:31)
at org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer.serialize(Jackson2HalModule.java:340)
at org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer.serialize(Jackson2HalModule.java:302)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:672)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:678)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:130)
at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1428)
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:930)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:269)
... 57 more
示例 JSON:
{
"_embedded" : {
"companies" : [ {
"key" : 1,
"name" : "company1",
"supportEmailAddress" : {
"key" : 1,
"emailAddrType" : "support",
"emailAddress" : "email@support.com"
}
"aliases" : [ ],
"contacts" : [ ],
"owner" : {
"key" : 1,
"name" : "owner 1",
"contacts" : [ ],
"_links" : {
"company" : {
"href" : "http://localhost:8080/company/1"
}
}
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/company/1"
},
"company" : {
"href" : "http://localhost:8080/company/1"
},
"aliases" : {
"href" : "http://localhost:8080/company/1/aliases"
},
"contacts" : {
"href" : "http://localhost:8080/company/1/contacts"
}
}
}, {
"key" : 2,
"name" : "company2",
"supportEmailAddress" : {
"key" : 1,
"emailAddrType" : "support",
"emailAddress" : "email@support.com"
}
"aliases" : [ ],
"contacts" : [ ],
"owner" : {
"key" : 2,
"name" : "owner 2",
"contacts" : [ ],
"_links" : {
"company" : {
"href" : "http://localhost:8080/company/2"
}
}
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/company/2"
},
"company" : {
"href" : "http://localhost:8080/company/2"
},
"aliases" : {
"href" : "http://localhost:8080/company/2/aliases"
},
"contacts" : {
"href" : "http://localhost:8080/company/2/contacts"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/company"
},
"profile" : {
"href" : "http://localhost:8080/profile/company"
},
"search" : {
"href" : "http://localhost:8080/company/search"
}
},
"page" : {
"size" : 20,
"totalElements" : 2,
"totalPages" : 1,
"number" : 0
}
}