[英]Could not write JSON: failed to lazily initialize a collection of role
I tried to implement a REST service with Java, Hibernate, and Spring, which returns JSON.我尝试使用 Java、Hibernate 和 Spring 实现 REST 服务,返回 JSON。
I have map a many to many relation.我有 map 多对多关系。 I have a supplier that has a list of ingredients, and each ingredient has a list of suppliers.我有一个有成分列表的供应商,每个成分都有一个供应商列表。
I created the table:我创建了表:
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);
Then I have an Ingredient model:然后我有一个成分model:
.....
.....
@ManyToMany(mappedBy = "ingredients")
@OrderBy("created DESC")
@BatchSize(size = 1000)
private List<Supplier> suppliers = new ArrayList<>();
....
....
Then I have a Supplier model:然后我有一个供应商model:
....
@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<>();
....
Endpoint :端点:
@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;
}
Service服务
....
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();
}
}
And IdAbstractObject和IdAbstractObject
public abstract class IdAbstractObject{
public Long id;
}
My problem is, when I call the endpoint:我的问题是,当我调用端点时:
http://localhost:8080/supplier/1
I got an error:我收到一个错误:
"Could not write JSON: failed to lazily initialize a collection of role: myPackage.ingredient.Ingredient.suppliers, could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: myPackage.ingredient.Ingredient.suppliers, could not initialize proxy “无法写入 JSON:无法延迟初始化角色集合:myPackage.ingredient.Ingredient.suppliers,无法初始化代理 - 否 Session;嵌套异常是 com.fasterxml.jackson.databind.JsonMappingException:无法延迟初始化集合角色:myPackage.ingredient.Ingredient.suppliers,无法初始化代理
- no Session (through reference chain: myPackage.supplier.SupplierObject["ingredients"]->org.hibernate.collection.internal.PersistentBag[0]->myPackage.ingredient.Ingredient["suppliers"])"没有 Session(通过参考链:myPackage.supplier.SupplierObject["ingredients"]->org.hibernate.collection.internal.PersistentBag[0]->myPackage.ingredient.Ingredient["suppliers"])"
I followed this:我跟着这个:
Avoid Jackson serialization on non fetched lazy objects 避免对非获取的惰性对象进行 Jackson 序列化
Now I haven't the error but in json returned, the ingredients field is null:现在我没有错误,但在返回 json 时,成分字段为 null:
{
"id": 1,
"email": "mail@gmail.com",
"phoneNumber": null,
"address": null,
"responsible": null,
"companyName": "Company name",
"vat": "vat number",
"ingredients": null
}
but in debug I can see ingredients....但在调试中我可以看到成分....
This is the normal behaviour of Hibernate and Jackson Marshaller Basically you want to have the following: a JSON with all Supplier object details... included the Ingredients.这是 Hibernate 和 Jackson Marshaller 的正常行为 基本上,您希望拥有以下内容:包含所有供应商对象详细信息的 JSON……包括成分。
Please note that in this case you must be very carefull because you can have a cyclic reference when you try to create the JSON itself so you should use also JsonIgnore
annotation请注意,在这种情况下,您必须非常小心,因为当您尝试创建 JSON 本身时可能会有循环引用,因此您还应该使用JsonIgnore
注释
The first thing you must do is to load the Supplier and all its details (ingredients included).您必须做的第一件事是加载供应商及其所有详细信息(包括成分)。
How can you do it?你怎么能做到呢? By using several strategies... let's use the Hibernate.initialize
.通过使用几种策略......让我们使用Hibernate.initialize
。 This must be used before the closing of hibernate session that is in the DAO (or repository) implementation (basically where you use the hibernate session).这必须在关闭DAO(或存储库)实现中的休眠会话之前使用(基本上是您使用休眠会话的地方)。
So in this case (I assume to use Hibernate) in my repository class I should write something like this:因此,在这种情况下(我假设使用 Hibernate)在我的存储库类中,我应该编写如下内容:
public Supplier findByKey(Long id)
{
Supplier result = (Supplier) getSession().find(Supplier.class, id);
Hibernate.initialize(result.getIngredients());
return result;
}
Now you have the Supplier
object with all its own details ( Ingredients
too) Now in your service you can do what you did that is:现在您拥有了包含所有详细信息的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;
}
In this way Jackson is able in writing the JSON but
let's give a look to the Ingredient
object.. it has the following property:通过这种方式,Jackson 能够编写 JSON, but
让我们看看Ingredient
对象。它具有以下属性:
@ManyToMany(mappedBy = "ingredients")
@OrderBy("created DESC")
@BatchSize(size = 1000)
private List<Supplier> suppliers = new ArrayList<>();
What will happen when Jackson tries to create the JSON?当 Jackson 尝试创建 JSON 时会发生什么? It will access to the each element inside the List<Ingredient>
and it will try to create a JSON for this one too.... also for the suppliers list and this is a cyclic reference... so you must avoid it and you can avoid it by using the JsonIgnore annotation.它将访问List<Ingredient>
的每个元素,它也会尝试为这个元素创建一个 JSON ......也用于供应商列表,这是一个循环引用......所以你必须避免它,你可以通过使用 JsonIgnore 注释来避免它。 For example you may write your Ingredient
entity class in this way:例如,您可以以这种方式编写Ingredient
实体类:
@JsonIgnoreProperties(value= {"suppliers"})
public class Ingredient implements Serializable
{
......
}
In this way you:通过这种方式,您:
In any case I would suggest to you to create specific DTO (or VO) object to use for marshalling and unmarshalling JSONs在任何情况下,我都会建议您创建特定的 DTO(或 VO)对象以用于编组和解组 JSON
I hope this is usefull我希望这是有用的
Angelo安杰洛
You have some solutions to resolve this issue:您有一些解决方案可以解决此问题:
@ManyToMany(fetch = FetchType.LAZY)
您可以使用@ManyToMany(fetch = FetchType.LAZY)
But EAGER fetching is very bad from a performance perspective.但是从性能的角度来看,EAGER 获取非常糟糕。 Moreover, once you have an EAGER association, there is no way you can make it LAZY.此外,一旦你有了一个 EAGER 协会,你就无法让它变得懒惰。
@ManyToMany @Fetch(FetchMode.JOIN)
您可以使用@ManyToMany @Fetch(FetchMode.JOIN)
More information: https://docs.jboss.org/hibernate/orm/3.2/api/org/hibernate/FetchMode.html更多信息: https : //docs.jboss.org/hibernate/orm/3.2/api/org/hibernate/FetchMode.html
Edit: It can occur when you have the following line in yout application.properties
file:编辑:当您的application.properties
文件中有以下行时,可能会发生这种情况:
spring.jpa.open-in-view = false
In my project I came across the same problem as yours.在我的项目中,我遇到了和你一样的问题。 The problem is that by the time of reading the data "one to many" the session has already been closed.问题是,在“一对多”读取数据时,会话已经关闭。 To get all the data, you need to explicitly initialize or use the transaction.要获取所有数据,您需要显式初始化或使用事务。 I used an explicit initialization.我使用了显式初始化。 You need to add a line in the DAO:您需要在 DAO 中添加一行:
Hibernate.initialize(supplier.getIngredients());
After that, Hibernate will load all the data from the database.之后,Hibernate 将从数据库中加载所有数据。 To avoid generating an exception when serializing to JSON, I add the @JsonIgnore
annotation in the one-to-many model field.为了避免在序列化为 JSON 时产生异常,我在一对多模型字段中添加了@JsonIgnore
注解。
Here is an example of my code:这是我的代码示例:
1.Model 1.型号
@OneToMany(mappedBy = "commandByEv", fetch = FetchType.LAZY)
@JsonIgnore
private Set<Evaluation> evaluations;
2. DAO 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();
}
只需在模型类中的@JsonIgnore
之后添加@JsonIgnore
@oneToMany
。
This is due to the hibernate session closed before the lazy initialization kicked in.这是由于在延迟初始化开始之前休眠会话已关闭。
The solution is explained well at this answer below.解决方案在下面的这个答案中得到了很好的解释。 Avoid Jackson serialization on non fetched lazy objects 避免对未获取的惰性对象进行 Jackson 序列化
You should use a jackson-datatype-hibernate.您应该使用 jackson-datatype-hibernate。
https://github.com/FasterXML/jackson-datatype-hibernate https://github.com/FasterXML/jackson-datatype-hibernate
And add this on your Application.java并将其添加到您的 Application.java
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Hibernate5Module());
return objectMapper;
}
In my application.properties file open-in-view property of spring jpa was false.在我的 application.properties 文件中,spring jpa 的 open-in-view 属性是错误的。 I had to comment out to get rid of this.我不得不发表评论以摆脱这一点。
#spring.jpa.open-in-view=false
Hope this help somebody.希望这对某人有所帮助。
I had to add spring.jpa.open-in-view = true
to application.properties我必须将spring.jpa.open-in-view = true
添加到 application.properties
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.