无法编写JSON:无法懒惰地初始化角色

时间:2018-01-05 15:56:47

标签: java spring hibernate jackson

我正在实现一个带有java的服务器REST - hibernate - spring,它返回一个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;
}
....

SupplierObject

    @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

我收到了一个错误:

  

&#34;无法编写JSON:懒得初始化一个集合   角色:myPackage.ingredient.Ingredient.suppliers,无法初始化   代理 - 没有会话;嵌套异常是   com.fasterxml.jackson.databind.JsonMappingException:懒得失败   初始化角色集合:   myPackage.ingredient.Ingredient.suppliers,无法初始化代理    - 没有会话(通过参考链:myPackage.supplier.SupplierObject [\&#34; ingredients \&#34;] - &gt; org.hibernate.collection.internal.PersistentBag [0] - &gt; myPackage.ingredient.Ingredient [ \&#34;供应商\&#34;])&#34;

我错了什么?

@Nikhil Yekhe

我跟着这个:

Avoid Jackson serialization on non fetched lazy objects

现在我没有错误,但在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

4 个答案:

答案 0 :(得分:5)

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

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

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

你怎么能这样做?通过使用多种策略......让我们使用Hibernate.initialize。必须在DAO(或存储库)实现中的hibernate会话关闭之前使用此(基本上在使用hibernate会话的地方)。

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

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

现在您拥有Supplier对象及其所有详细信息(Ingredients) 现在,在您的服务中,您可以做您所做的事情:

@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;
}

通过这种方式,杰克逊能够编写JSON but让我们看看Ingredient对象..它具有以下属性:

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

杰克逊试图创建JSON时会发生什么?它将访问List<Ingredient>内的每个元素,它也将尝试为此创建一个JSON ....也适用于供应商列表,这是一个循环引用...所以你必须避免它和你可以通过使用JsonIgnore注释来避免它。例如,您可以用这种方式编写Ingredient实体类:

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

通过这种方式你:

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

无论如何,我建议您创建特定的DTO(或VO)对象,用于编组和解组JSON

我希望这是有用的

安吉洛

答案 1 :(得分:2)

在我的项目中,我遇到了和你一样的问题。问题在于,在阅读数据时,一对多的数据是&#34;会议已经结束。要获取所有数据,您需要显式初始化或使用事务。我使用了显式初始化。 您需要在DAO中添加一行:

Hibernate.initialize(supplier.getIngredients());

之后,Hibernate将加载数据库中的所有数据。为避免在序列化为JSON时生成异常,我在一对多模型字段中添加@JsonIgnore注释。

以下是我的代码示例:

1.型号

@OneToMany(mappedBy = "commandByEv", fetch = FetchType.LAZY)
@JsonIgnore
private Set<Evaluation> evaluations;

2。 DAO

public Command getCommand(long id) {
Session session = sessionFactory.getCurrentSession();
Evaluation evaluation = session.get(Evaluation.class, id);
Hibernate.initialize(evaluation.getCommand());
return evaluation.getCommand();
}

答案 2 :(得分:0)

这是由于在延迟初始化启动之前关闭了休眠会话。

下面的答案很好地解释了解决方案。 Avoid Jackson serialization on non fetched lazy objects

答案 3 :(得分:0)

您有一些解决此问题的方法:

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

但是从性能角度来看,EAGER的获取非常糟糕。此外,一旦有了EAGER关联,就无法将其设为LAZY。

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

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

编辑:当您的application.properties文件中包含以下行时,就会发生这种情况:

spring.jpa.open-in-view = false