无法写入 JSON:无法延迟初始化角色集合

2022-09-04 00:42:16

我尝试用java - hibernate - spring实现服务器REST,它返回一个json。

我有映射多对多的关系。

我解释得更好,我有一个供应商,有一个成分清单,每个成分都有一个供应商清单。

我创建了表格:

CREATE TABLE supplier_ingredient (
  supplier_id BIGINT,
  ingredient_id BIGINT
)


ALTER TABLE supplier_ingredient ADD CONSTRAINT supplier_ingredient_pkey 
PRIMARY KEY(supplier_id, ingredient_id);

ALTER TABLE supplier_ingredient ADD CONSTRAINT 
fk_supplier_ingredient_ingredient_id FOREIGN KEY (ingredient_id) 
REFERENCES ingredient(id);

ALTER TABLE supplier_ingredient ADD CONSTRAINT 
fk_supplier_ingredient_supplier_id FOREIGN KEY (supplier_id) REFERENCES 
supplier(id);

然后我有成分模型:

.....
.....
@ManyToMany(mappedBy = "ingredients")
@OrderBy("created DESC")
@BatchSize(size = 1000)
private List<Supplier> suppliers = new ArrayList<>();
....
....

然后我有供应商模型:

....
@ManyToMany
@JoinTable( name = "supplier_ingredient ", 
        joinColumns = @JoinColumn(name = "supplier_id", referencedColumnName = "id"), 
        inverseJoinColumns = @JoinColumn(name = "ingredient_id", referencedColumnName = "id"), 
        foreignKey = @ForeignKey(name = "fk_supplier_ingredient_supplier_id"))
@OrderBy("created DESC")
@Cascade(CascadeType.SAVE_UPDATE)
@BatchSize(size = 1000)
private List<Ingredient> ingredients = new ArrayList<>();
....

端点

@RequestMapping(value = "/{supplierId:[0-9]+}", method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
public SupplierObject get(@PathVariable Long supplierId) {

    Supplier supplier = supplierService.get(supplierId);

    SupplierObject supplierObject = new SupplierObject (supplier);

    return SupplierObject;

}

服务

....
public Supplier get(Long supplierId) {

    Supplier supplier = supplierDao.getById(supplierId); (it does entityManager.find(entityClass, id))

    if (supplier == null) throw new ResourceNotFound("supplier", supplierId);

    return supplier;
}
....

供应商对象

    @JsonIgnoreProperties(ignoreUnknown = true)
    public class SupplierObject extends IdAbstractObject {

    public String email;

    public String phoneNumber;

    public String address;

    public String responsible;

    public String companyName;

    public String vat;

    public List<Ingredient> ingredients = new ArrayList<>();

    public SupplierObject () {

    }

    public SupplierObject (Supplier supplier) {

        id = supplier.getId();
        email = supplier.getEmail();
        responsible = supplier.getResponsible();
        companyName = supplier.getCompanyName();
        phoneNumber = supplier.getPhone_number();
        ingredients = supplier.getIngredients();
        vat = supplier.getVat();
        address = supplier.getAddress();


    }
}

IdAbstractObject

public abstract class IdAbstractObject{

    public Long id;

}

我的问题是,当我调用端点时:

http://localhost:8080/supplier/1

我收到一个错误:

“无法编写 JSON:无法懒惰地初始化角色集合:myPackage.ingredient.Ingredient.suppliers,无法初始化代理 - 无会话;嵌套的异常是 com.fasterxml.jackson.databind.JsonMappingException: 未能懒惰地初始化角色集合: myPackage.ingredient.Ingredient.suppliers, 无法初始化代理 - 无会话 (通过引用链: myPackage.supplier.SupplierObject[\“ingredients\”]->org.hibernate.collection.internal.PersistentBag[0]->myPackage.ingredient.Ingredient[\“supplier\”])”

我遵循了这个:

避免对未获取的惰性对象进行 Jackson 序列化

现在我没有错误,但在返回的json中,成分字段为空:

{
  "id": 1,
  "email": "mail@gmail.com",
  "phoneNumber": null,
  "address": null,
  "responsible": null,
  "companyName": "Company name",
  "vat": "vat number",
  "ingredients": null
}

但在调试中,我可以看到成分....

enter image description here


答案 1

这是Hibernate和Jackson Marshaller的正常行为 基本上,您希望拥有以下内容:包含所有供应商对象详细信息的JSON...包括成分。

请注意,在这种情况下,您必须非常小心,因为当您尝试创建JSON本身时,您可以有一个循环引用,因此您还应该使用JsonIgnore注释

您必须做的第一件事是加载供应商及其所有详细信息(包括成分)。

你怎么能做到呢?通过使用几种策略...让我们使用Hibernate.initialize。这必须在 DAO(或存储库)实现中的休眠会话关闭之前使用(基本上是使用休眠会话的位置)。

因此,在这种情况下(我假设使用Hibernate),在我的存储库类中,我应该写这样的东西:

public Supplier findByKey(Long id)
{
    Supplier result = (Supplier) getSession().find(Supplier.class, id);
    Hibernate.initialize(result.getIngredients());
    return result;
}

现在你有对象的所有自己的细节(太)现在在你的服务中,你可以做你所做的是:SupplierIngredients

@RequestMapping(value = "/{supplierId:[0-9]+}", method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
public SupplierObject get(@PathVariable Long supplierId) 
{
    Supplier supplier = supplierService.get(supplierId);
    SupplierObject supplierObject = new SupplierObject (supplier);
    return SupplierObject;
}

通过这种方式,Jackson能够编写JSON,让我们看一下对象。它具有以下属性:butIngredient

@ManyToMany(mappedBy = "ingredients")
@OrderBy("created DESC")
@BatchSize(size = 1000)
private List<Supplier> suppliers = new ArrayList<>();

当 Jackson 尝试创建 JSON 时会发生什么?它将访问内部的每个元素,并且还将尝试为此元素创建一个JSON。也适用于供应商列表,这是一个循环参考...因此,您必须避免它,并且可以通过使用JsonIgnore注释来避免它。例如,您可以通过以下方式编写实体类:List<Ingredient>Ingredient

@JsonIgnoreProperties(value= {"suppliers"})
public class Ingredient implements Serializable
{
......
}

通过这种方式,您可以:

  • 在供应商对象中加载所有相关成分
  • 在尝试创建 JSON 本身时避免循环引用

在任何情况下,我都会建议您创建特定的DTO(或VO)对象,以用于编组和取消编组JSON

我希望这是有用的

安杰洛


答案 2

您有一些解决方案来解决此问题:

  1. 您可以使用@ManyToMany(fetch = FetchType.LAZY)

但是从性能的角度来看,EAGER FETCH是非常糟糕的。此外,一旦你有一个渴望的联想,你就没有办法让它变得懒惰。

  1. 您可以使用@ManyToMany @Fetch(FetchMode.JOIN)

更多信息: https://docs.jboss.org/hibernate/orm/3.2/api/org/hibernate/FetchMode.html

编辑:当您在 yout 文件中有以下行时,可能会发生这种情况:application.properties

spring.jpa.open-in-view = false

推荐