【问题标题】:Could not write JSON: failed to lazily initialize a collection of role无法写入 JSON:无法延迟初始化角色集合
【发布时间】: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
}

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

【问题讨论】:

    标签: java spring hibernate spring-boot jackson


    【解决方案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】:

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

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

      但是从性能的角度来看,EAGER fetching 非常糟糕。而且,一旦你有了一个 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
      

      【讨论】:

      • 改变: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
          

          希望这对某人有所帮助。

          【讨论】:

            猜你喜欢
            • 2022-01-05
            • 1970-01-01
            • 2018-10-05
            • 2022-01-11
            • 1970-01-01
            • 1970-01-01
            • 2016-07-06
            • 2015-07-02
            • 2018-02-25
            相关资源
            最近更新 更多