用spring反序列化JSON:未解析的前向引用Jackson Exception

我在Spring上使用API​​ Rest项目。 我有一个服务“CreateMateriel”,它作为参数数据JSON:

装备对象的JSON

{ "agence": 1, "code": "001", "type": "MyType" } 

“物质”与“Agence”有很多关系。 我把@JsonIdentityInfo标签用来使用Agence的Id而不是Agence的对象(看完这个话题后 )

 @JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "idAgence") @JsonIdentityReference(alwaysAsId = true) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "agence") private Agence agence; 

但是当我在POST / materiels上发送JSON时,我有这个例外:

 2017-05-16 18:00:53.021 WARN 8080 --- [nio-8080-exec-8] .wsmsDefaultHandlerExceptionResolver : Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Unresolved forward references for: at [Source: java.io.PushbackInputStream@767dfd7a; line: 5, column: 1]Object id [1] (for fr.app.domain.Agence) at [Source: java.io.PushbackInputStream@767dfd7a; line: 2, column: 14].; nested exception is com.fasterxml.jackson.databind.deser.UnresolvedForwardReference: Unresolved forward references for: at [Source: java.io.PushbackInputStream@767dfd7a; line: 5, column: 1]Object id [1] (for fr.app.domain.Agence) at [Source: java.io.PushbackInputStream@767dfd7a; line: 2, column: 14]. 

经过多次研究后,我看到在JsonIdentityInfo中使用ObjectIdResolver ……但我认为这不是最好的解决方案。 这就是为什么我请求您帮助检测问题的根源。 谢谢

MaterielController.java

 package fr.app.controllers; import fr.app.domain.Materiel; import fr.app.services.MaterielService; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.Collection; @RestController @RequestMapping(value = "/materiels") public class MaterielController { @Resource private MaterielService materielService; @RequestMapping(method = RequestMethod.POST) public Materiel createMateriel(@RequestBody Materiel materiel) { return this.materielService.createMateriel(materiel); } @RequestMapping(method = RequestMethod.GET) public Collection getAllMateriels() { return this.materielService.getAllMateriels(); } @RequestMapping(value = "/{code}", method = RequestMethod.GET) public Materiel getMaterielByCode(@PathVariable(value = "code") String code) { //find materiel by code return this.materielService.getMaterielByCode(code); } @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) public void deleteMateriel(@PathVariable(value = "id") Long id) { this.materielService.deleteMateriel(id); } @RequestMapping(value = "/{id}", method = RequestMethod.PUT) public Materiel updateMateriel(@PathVariable(value = "id") Long id, @RequestBody Materiel materiel) { materiel.setIdMateriel(id); return this.materielService.updateMateriel(materiel); } } 

Materiel.java

 package fr.app.domain; import com.fasterxml.jackson.annotation.*; import javax.persistence.*; @Entity public class Materiel { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long idMateriel; @Column(name = "type_materiel", nullable = false) private String type; @Column(name = "code_materiel", unique = true, nullable = false) private String code; @JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "idAgence") @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "agence") private Agence agence; public Materiel() { } public Materiel(String type, String code, String dateScan) { this.type = type; this.code = code; this.dateScan = dateScan; } public Long getIdMateriel() { return idMateriel; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public Agence getAgence() { return agence; } public void setAgence(Agence agence) { if(this.agence != null) this.agence.deleteMateriel(this); this.agence = agence; this.agence.addMateriel(this); } } 

MaterielService.java

 package fr.app.services; import fr.app.domain.Materiel; import fr.app.repositories.MaterielRepository; import org.apache.commons.collections.IteratorUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Collection; @Service(value = "materielService") public class MaterielServiceImpl implements MaterielService { @Resource private MaterielRepository materielRepository; ... @Override public Materiel createMateriel(Materiel materiel) { return this.materielRepository.save(materiel); } ... } 

Agence.java

 package fr.app.domain; import com.fasterxml.jackson.annotation.*; import javax.persistence.*; import java.util.HashSet; import java.util.Set; @Entity public class Agence { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long idAgence; @Column(name = "nom",unique = true, nullable = false) private String nom; @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "agence") private Set materiels = new HashSet(); public Agence() { } public Agence(String nom) { this.nom = nom; } public Long getIdAgence() { return idAgence; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } public Set getMateriels() { return materiels; } public void setMateriels(Set materiels) { this.materiels = materiels; } public void addMateriel(Materiel materiel) { this.materiels.add(materiel); } public void deleteMateriel(Materiel materiel) { this.materiels.remove(materiel); } } 

的pom.xml

   org.springframework.boot spring-boot-starter-data-jpa   mysql mysql-connector-java 5.1.6   org.springframework.boot spring-boot-starter-test test   org.springframework.boot spring-boot-starter-web   commons-collections commons-collections 3.0   org.hibernate hibernate-core 5.2.9.Final   org.hibernate hibernate-entitymanager 5.2.9.Final   io.springfox springfox-swagger2 2.6.1   io.springfox springfox-swagger-ui 2.2.2 compile    com.fasterxml.jackson.core jackson-databind 2.8.6   

好的,我发现了问题。

最后我需要添加解析器。 所以我在这里找到一个实现ObjectIDResolver的例子:

 import com.fasterxml.jackson.annotation.ObjectIdGenerator; import com.fasterxml.jackson.annotation.ObjectIdResolver; import javax.persistence.EntityManager; /** * @author fta on 20.12.15. */ public class EntityIdResolver implements ObjectIdResolver { private EntityManager entityManager; public EntityIdResolver( final EntityManager entityManager) { this.entityManager = entityManager; } @Override public void bindItem( final ObjectIdGenerator.IdKey id, final Object pojo) { } @Override public Object resolveId(final ObjectIdGenerator.IdKey id) { return this.entityManager.find(id.scope, id.key); } @Override public ObjectIdResolver newForDeserialization(final Object context) { return this; } @Override public boolean canUseFor(final ObjectIdResolver resolverType) { return false; } } 

然后我添加@JsonIdentityInfo

 @Entity @JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "idAgence", resolver = EntityIdResolver.class, scope=Agence.class) public class Agence { // ... } 

您有两个域:Materiel和Agence。 当jackson试图反序列化Materiel时,它调用方法getAgence 。 此方法返回一个Agence对象,该对象也将由Jackson反序列化。 当Jackson调用getMeteriels (Agence)方法时,它会返回一套也将反序列化的装备。 问题是jackson将尝试再次反序列化Set中的每个装备。 这里也有同样的问题。

对于您的上一个例外(utf-8),您可以更改控制器的签名,如下所示:

 @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity create(HttpServletRequest request) throws IOException { // getting the posted value String body = CharStreams.toString(request.getReader()); Bean bean = new ObjectMapper().readValue(body, service.getBeanClass()); bean.setId(null); Bean saved = service.save(bean); URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(saved.getId()).toUri(); return ResponseEntity.created(location).body(saved); } 

使用此解决方案,您可以完全控制映射。

请享用