Я использую две сущности JPA, аннотированные @OneToMany (parent) <-> @ManyToOne (child), и я также написал RessourceAssembler, чтобы превратить сущности в ресурсы в контроллере приложения Springboot (примеры кода см. Ниже)).
Без связи @OneToMany в родительском объекте сборка и сериализация Ressource работает просто отлично.
Как только я добавляю отношение OneToMany к родительскому объекту, сериализация разрывается с этим:
"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\"])"
Как вы можете видеть, бесконечный цикл исходит из ресурса hateoas, а не из самих сущностей.
Я уже пытался добавить @JsonManagedReference и @JsonBackReference для сущностей или @JsonIgnore для дочернего элемента, но на самом деле ничего не помогает. Hateoas RessourceAssembler всегда заканчивается бесконечным циклом, как только дочерняя сущность внедряется. Кажется, что shose @Json .... аннотации помогают в сериализации JSON самой сущности, но они не решают проблемы с RessourceAssembler
У меня есть эти 2 сущности (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
}
и я использую RessourceAssemlber, как показано ниже для родительской сущности '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"));
}
}
, и в контроллере у меня есть 2 класса get для вывода списка всех или только одного хранилища с его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
}
Может кто-нибудь просветить меня, как я могу решить этот бесконечный цикл в HateOAS. Я хочу, чтобы внедренные дочерние записи просто не ссылались на родительский элемент (поэтому ссылки на родительский элемент не создаются) или содержали ссылку для одного уровня, но дальнейшая обработка не выполнялась.