I'm using two JPA entities annotated with @OneToMany (parent) <-> @ManyToOne (child) and I also wrote a RessourceAssembler to turn the entities into resources in the controller of a Springboot application (see below for code samples).
Without the relationship @OneToMany in the parent entity, Ressource assembling and serialisation works just fine.
As soon as I add the OneToMany relation on the parent the serialisation breaks with this:
"Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: org.springframework.hateoas.Resource[\"content\"]->com.marcelser.app.entities.Storage[\"storageStock\"])"
As you can see the infinite loop comes from the hateoas Resource, not the entities themselves.
I already tried to add @JsonManagedReference and @JsonBackReference on the entities or @JsonIgnore on the child but nothing really helps. The Hateoas RessourceAssembler always ends up in a infinite loop as soon as the child entity is embedded. It seems that shose @Json.... annotations help with the JSON serialisation of the entity itself but they don't solve problems with the RessourceAssembler
I have these 2 entities (Storage & Stock)
@Entity
@Table(name = "storage")
@Data
public class Storage {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
@OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "storage")
private Set<Stock> storageStock = new HashSet<>();;
}
@Entity
@Table(name = "stock")
@Data
public class Stock {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "storage_id")
private Storage storage;
... other fields ommitted
}
and I'm using a RessourceAssemlber like follows for the parent entity 'Storage':
@Component
public class StorageResourceAssembler implements ResourceAssembler<Storage, Resource<Storage>> {
@Override
public Resource<Storage> toResource(Storage storage) {
return new Resource<>(storage,
linkTo(methodOn(StorageController.class).one(storage.getId())).withSelfRel(),
linkTo(methodOn(StorageController.class).all()).withRel("storages"));
}
}
and in the controller I have 2 get classes to list all or just a single Storage with its childs
public class StorageController {
private final StorageRepository repository;
private final StorageResourceAssembler assembler;
@GetMapping
ResponseEntity<?> all() {
List<Resource<Storage>> storages = repository.findAll().stream()
.map(assembler::toResource)
.collect(Collectors.toList());
Resources<Resource<Storage>> resources = new Resources<>(storages,
linkTo(methodOn(StorageController.class).all()).withSelfRel());
return ResponseEntity.ok(resources);
}
private static final Logger log = LoggerFactory.getLogger(StorageController.class);
StorageController(StorageRepository repository, StorageResourceAssembler assembler) {
this.repository = repository;
this.assembler = assembler;
}
@GetMapping("/{id}")
ResponseEntity<?> one(@PathVariable Long id) {
try {
Storage storage = repository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(id));
Resource<Storage> resource = assembler.toResource(storage);
return ResponseEntity.ok(resource);
}
catch (EntityNotFoundException e) {
log.info(e.getLocalizedMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body (new VndErrors.VndError("Storage not found", "could not find storage with id " + id ));
}
}
... omitted Put/Post/Delete
}
Can anyone enlighten me how I can solve this infinite loop in HateOAS. What I want is that the embedded child entries just either don't link back to the parent (so no links to parent are created) or they contain the link for the one level but no further processing is done.