将实体与OneToMany和HATEOAS RessourceAssembler一起使用会导致无限递归

问题描述 投票:0回答:1

我正在使用两个以@OneToMany(父级)注释的JPA实体 @ManyToOne(子级),并且我还编写了一个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\"])"

您可以看到无限循环来自仇恨资源,而不是实体本身。

我已经尝试在实体上添加@JsonManagedReference和@JsonBackReference或在子级上添加@JsonIgnore,但实际上没有任何帮助。嵌入子实体后,Hateeoas RessourceAssembler总是以无限循环结束。似乎缺少@Json ....批注有助于实体本身的JSON序列化,但它们不能解决RessourceAssembler的问题]

我有这两个实体(存储和库存)

@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:

@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个获取类以列出全部或仅一个带有其子级的存储库

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中的无限循环。我想要的是嵌入的子项只是不链接回父项(因此没有创建到父项的链接),或者它们包含一个级别的链接,但没有进行进一步的处理。

spring-boot spring-hateoas
1个回答
0
投票

要在模型属性定义为延迟加载时使用Jackson的API处理与模型序列化有关的问题,我们必须告诉序列化器忽略Hibernate添加到类的链或有用的垃圾,因此它可以管理延迟通过声明@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})批注来加载数据。

@Entity
@Table(name = "storage")
@Data
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Storage {...



@Entity
@Table(name = "stock")
@Data
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Stock {...

或者您可以仅声明单边映射,以注释存储实体声明并更改private Storage storage;以获取Stock类中的EAGER @ManyToOne(fetch = FetchType.EAGER)

@Entity
@Table(name = "storage")
@Data
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
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
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Stock {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "storage_id")
    private Storage storage;

    ... other fields ommitted
}
© www.soinside.com 2019 - 2024. All rights reserved.