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

Posted

技术标签:

【中文标题】无法写入 JSON:无法延迟初始化角色集合【英文标题】:Could not write JSON: failed to lazily initialize a collection of role 【发布时间】:2018-06-15 11:16:49 【问题描述】:

我尝试使用 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);

然后我有Ingredient模型:

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

我遇到了一个错误:

"无法写入 JSON:无法懒惰地初始化一个集合 角色:myPackage.ingredient.Ingredient.suppliers,无法初始化 代理 - 没有会话;嵌套异常是 com.fasterxml.jackson.databind.JsonMappingException: 懒惰失败 初始化角色集合: myPackage.ingredient.Ingredient.suppliers,无法初始化代理 - 无 Session(通过引用链:myPackage.supplier.SupplierObject[\"ingredients\"]->org.hibernate.collection.internal.PersistentBag[0]->myPackage.ingredient.Ingredient[\"suppliers\"])"

我跟着这个:

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

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

【问题讨论】:

【参考方案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;

现在您有了 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;

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

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

当 Jackson 尝试创建 JSON 时会发生什么?它将访问List&lt;Ingredient&gt; 中的每个元素,并且它也会尝试为这个元素创建一个 JSON ......同样适用于供应商列表,这是一个循环引用......所以你必须避免它,你可以通过使用 JsonIgnore 注释来避免它。例如你可以这样写你的Ingredient实体类:

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

......

这样你:

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

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

希望对你有用

安杰洛

【讨论】:

【参考方案2】:

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

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

但是从性能的角度来看,EAGER fetching 非常糟糕。而且,一旦你有了一个 EAGER 关联,你就无法让它变得 LAZY。

    您可以使用@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

【讨论】:

改变:spring.jpa.open-in-view = true 对我有用...【参考方案3】:

在我的项目中,我遇到了与您相同的问题。问题是,在“一对多”读取数据时,会话已经关闭。要获取所有数据,您需要显式初始化或使用事务。我使用了显式初始化。 你需要在DAO中添加一行:

Hibernate.initialize(supplier.getIngredients());

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

这是我的代码示例:

1.型号

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

2。道

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

【讨论】:

我的关系是多对多,反正我不明白你的建议。首先你说你需要在DAO中添加一行: Hibernate.initialize(supplier.getIngredients());然后你写一个 DAO ......我很困惑【参考方案4】:

只需在模型类中的@oneToMany 之后添加@JsonIgnore

【讨论】:

【参考方案5】:

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

解决方案在下面的这个答案中得到了很好的解释。 Avoid Jackson serialization on non fetched lazy objects

【讨论】:

所以,我尝试了您链接的示例,但现在返回的 json 中的成分为空: "id": 1, "email": "123@gmail.com", "phoneNumber ": null, "address": null, "responsible": null, "companyName": "123123", "vat": "ddstere", "ingredients": null 我正在使用 Hibernate 5,为此,我正在使用Hibernate5Module 而不是 Hibernate4Module,就像在您的链接中一样,我确信,我在调试中看到,成分存在【参考方案6】:

您应该使用 jackson-datatype-hibernate。

https://github.com/FasterXML/jackson-datatype-hibernate

并将其添加到您的 Application.java

@Bean
public ObjectMapper objectMapper() 
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new Hibernate5Module());
    return objectMapper;

【讨论】:

【参考方案7】:

在我的 application.properties 文件中,spring jpa 的 open-in-view 属性是错误的。我必须注释掉才能摆脱这个。

 #spring.jpa.open-in-view=false

希望这对某人有所帮助。

【讨论】:

以上是关于无法写入 JSON:无法延迟初始化角色集合的主要内容,如果未能解决你的问题,请参考以下文章

休眠延迟加载不适用于 Spring Boot => 无法延迟初始化角色集合无法初始化代理 - 无会话

org.hibernate.LazyInitializationException:无法延迟初始化角色集合:FQPropretyName,无法初始化代理 - 无会话

LazyInitializationException: 延迟初始化角色集合失败 无法初始化代理 - 没有会话

无法延迟初始化角色集合:myapp.myapp.models.Contact.messages,无法初始化代理 - 没有会话

未能延迟初始化角色集合:无法初始化代理 - Hibernate 中的 @ManyToMany 映射注释中没有会话错误? [复制]

延迟初始化:未能延迟初始化集合