it-roy-ru.com

Не удалось написать JSON: не удалось лениво инициализировать коллекцию ролей

Я реализую сервер REST с Java - 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);

Тогда у меня есть 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: не удалось лениво инициализировать коллекцию Role: myPackage.ingredient.Ingredient.suppliers, не удалось инициализировать Proxy - нет Session; вложенное исключение Com.fasterxml.jackson.databind .JsonMappingException: не удалось лениво Инициализировать коллекцию ролей: MyPackage.ingredient.Ingredient.suppliers, не удалось инициализировать прокси .__ - нет сеанса (через цепочку ссылок: myPackage.supplier.SupplierObject [\ "ингридиенты\"] -> org.hibernate.collection.internal.PersistentBag [0] -> myPackage.ingredient.Ingredient [\" поставщиков\"])"

В чем я не прав?

@Nikhil Yekhe

Я следил за этим:

Избегайте сериализации Джексона на невнятых ленивых объектах

Теперь у меня нет ошибки, но в json возвращается, поле ингредиентов равно нулю:

{
"id": 1,
"email": "[email protected]",
"phoneNumber": null,
"address": null,
"responsible": null,
"companyName": "Company name",
"vat": "vat number",
"ingredients": null
}

но в отладке я вижу ингредиенты ....

 enter image description here

3
Gjord83

Это нормальное поведение Hibernate и Jackson Marshaller В основном вы хотите иметь следующее: JSON со всеми деталями объекта поставщика ... включая ингредиенты. 

Обратите внимание, что в этом случае вы должны быть очень осторожны, потому что вы можете иметь циклическую ссылку, когда пытаетесь создать сам JSON, поэтому вы должны также использовать JsonIgnore annotation

Первое, что вы должны сделать, это загрузить поставщика и все его детали (включая ингредиенты). 

Как ты можешь это сделать? Используя несколько стратегий ... давайте использовать Hibernate.initialize . Этот должен использоваться перед закрытием сеанса hibernate, который находится в реализации DAO (или репозитория) (в основном там, где вы используете сеанс 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.

Я надеюсь, что это полезно

Angelo

4
Angelo Immediata

В моем проекте я столкнулся с той же проблемой, что и ваш. Проблема в том, что к моменту чтения данных «один ко многим» сессия уже была закрыта. Чтобы получить все данные, вам нужно явно инициализировать или использовать транзакцию. Я использовал явную инициализацию. Вам нужно добавить строку в DAO:

Hibernate.initialize(supplier.getIngredients());

После этого Hibernate загрузит все данные из базы данных. Чтобы избежать генерации исключения при сериализации в JSON, я добавляю аннотацию @JsonIgnore в поле модели «один ко многим».

Вот пример моего кода:

1.Model

@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();
}
2
conacry

Просто добавьте @JsonIgnore после @oneToMany в класс вашей модели.

0
Majid

Это происходит из-за того, что сеанс гибернации был закрыт до начала отложенной инициализации. 

Решение хорошо объяснено в этом ответе ниже . Избегайте сериализации Джексона на не извлеченных ленивых объектах

0
Nikhil Yekhe