Использование Jackson2 с Spring 4.0 (MVC + REST+ Hibernate)

Я делаю спокойное приложение и пытаюсь преобразовать список объектов в json для определенного URL-адреса (@RequestMapping / @ResponseBody)

У меня есть jackson-hibernate4 и jackson-core, databind и т. д. в моем пути к классам.

Вот мой объект, который я хочу преобразовать в json.

@Entity
@Table(name="Product")
public class Product {
@Id
@Column(name="productId")
@GeneratedValue
protected int productId;
@Column(name="Product_Name")
protected String name;

@Column(name="price")
protected BigDecimal baseprice;


@OneToMany(cascade = javax.persistence.CascadeType.ALL,mappedBy="product",fetch=FetchType.EAGER)
protected List<ProductOption> productoption = new ArrayList<ProductOption>();

@OneToMany(cascade = javax.persistence.CascadeType.ALL,mappedBy="product",fetch=FetchType.EAGER)
protected List<ProductSubOption> productSubOption = new ArrayList<ProductSubOption>();


@ManyToOne
@JoinColumn(name="ofVendor")
protected Vendor vendor;

Два объекта внутри Product также являются POJO'S.

Вот мой метод, который извлекает список продуктов

@Override
public List<Product> getMenuForVendor(int vendorId) {
    List<Product> result = em.createQuery("from "+Product.class.getName()+" where ofVendor = :vendorId").setParameter("vendorId", vendorId).getResultList();
    System.out.println(result.size());
    return result;
}

Когда я пытаюсь вернуть этот список в свой контроллер, я получаю сообщение «Не удается лениво загрузить для json», поэтому я настраиваю свои объекты на жадную загрузку. Вот мой контроллер

@Autowired
private MenuDaoImpl ms;

@RequestMapping(value = "/{vendorId}", method = RequestMethod.GET)
public @ResponseBody List<Product> getMenu(@PathVariable int vendorId){

    List<Product> Menu = Collections.unmodifiableList(ms.getMenuForVendor(vendorId));
    return Menu;
}

Теперь, когда я нажимаю на свой URL-адрес localhost:8080/getMenu/1, я должен отображать строку json, но я получаю большой список ошибок

WARN : org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver -       Handling of [org.springframework.http.converter.HttpMessageNotWritableException] resulted in Exception
  java.lang.IllegalStateException: Cannot call sendError() after the response has been     committed
at org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:467)
 Could not write JSON: Infinite recursion (StackOverflowError) (through reference chain:

Я не уверен, что я что-то упускаю. Пожалуйста, направляйте.


person Nikhil    schedule 04.03.2014    source источник
comment
Похоже, у вас есть циклическая ссылка в ваших сущностях.   -  person Sotirios Delimanolis    schedule 05.03.2014
comment
Я просто каким-то образом смог получить json в своем браузере, но теперь он печатает его бесконечно, пока на моей консоли не появится переполнение стека. Где у него есть этот цикл?   -  person Nikhil    schedule 05.03.2014
comment
Есть ли у Vendor обратная ссылка на Product?   -  person Sotirios Delimanolis    schedule 05.03.2014
comment
У продавца есть список продуктов... и у каждого товара есть один продавец...   -  person Nikhil    schedule 05.03.2014
comment
Итак, Джексон пытается сериализовать Produce и должен сериализовать его Vendor. Сериализация его Vendor означает сериализацию списка Product этого Vendor. И так далее и так далее, пока не закончится память.   -  person Sotirios Delimanolis    schedule 05.03.2014
comment
Для этого есть аннотация Джексона. Я думаю, что это называется @JsonBackReference или что-то подобное.   -  person Sotirios Delimanolis    schedule 05.03.2014
comment
Неправильно ли связывать мои сущности с обеих сторон в отношениях «один ко многим»?   -  person Nikhil    schedule 05.03.2014
comment
Хорошо.. Это должно быть передано продавцу?   -  person Nikhil    schedule 05.03.2014
comment
Возможно обе стороны отношений.   -  person Sotirios Delimanolis    schedule 05.03.2014


Ответы (3)


Я решил это, используя @JsonBackReference для привязки @ManyToOne и @JsonManagedReference для привязки @OneToMany.

Спасибо "Сотириос Делиманолис"

person Nikhil    schedule 04.03.2014
comment
что делать, если отношение @OneToOne - person Ulug'bek Ro'zimboyev; 07.10.2016
comment
но для дочернего элемента manyToOne отсутствует родительское свойство, чтобы получить Parent - person haidarvm; 24.09.2019

На вопрос уже был дан ответ. Я просто ставлю ссылку на хороший пример, который ясно объясняет как проблему, так и решение. http://geekabyte.blogspot.in/2013/09/fixing-converterhttpmessagenotwritablee.html

person Shalvika    schedule 04.11.2014

Я понимаю, что это может быть не на 100% то, что вам нужно, но, тем не менее, мне захотелось поделиться этим, так как я потратил много времени на борьбу с этой проблемой в те дни.

Кроме того, вместо использования аннотаций Json вы можете рассмотреть собственный анализатор Json. Убедитесь, что вы используете правильный пакет для банок Джексона, так как они недавно изменили свою структуру пакета (когда вы используете любой из их классов с номером 2, как показано ниже).

Начните с создания HttpMessageConverter:

   @Bean
    public HttpMessageConverter jacksonMessageConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setPrefixJson(false);
        converter.setPrettyPrint(true);
        converter.setObjectMapper(objectMapper());
        return converter;
    }

Добавьте ObjectMapper, к которому вы подключаете модуль сопоставления, и присоединяйте сериализаторы, которые вы будете использовать.

public ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);

    SimpleModule module = new SimpleModule("jacksonJsonMapper", Version.unknownVersion());
    module.addSerializer(Product.class, new Product());

    objectMapper.registerModule(module);

    return objectMapper;
}

Теперь создайте сериализатор. Этот класс предоставит вывод, который вы видите, когда получаете объекты, а Джексон сделает все остальное. Вы просто предоставляете скелет того, как это должно выглядеть.

public class Product erializer extends JsonSerializer<Product> {

@Override
public void serialize(Product product, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
    if(product == null) { 
        //Handle it, if you want 
    }

    if(product != null) {

        jsonGenerator.writeStartObject();

        jsonGenerator.writeStringField("id", productId.getId().toString());
        jsonGenerator.writeStringField("title",  product.getName());
        jsonGenerator.writeStringField("basePrice", product.getBasePrice());


        //Add items to the json array representation
        jsonGenerator.writeArrayFieldStart("productoptions");
        for(ProductOption productOption: product.getProductoption()) {
            jsonGenerator.writeStartObject("field", productOption.getFoo());

            jsonGenerator.writeEndObject();
        }
        jsonGenerator.writeEndArray();

        jsonGenerator.writeEndObject();
    }
}

}

На заметку, но я все еще надеюсь, что она будет полезна: вам нужно убедиться, что у вас есть транзакция, доступная при ленивом извлечении сущностей. Вы также должны иметь в виду, что ленивый способ загрузки сущностей является предпочтительным, если только вы не хотите полностью разрушать свой сервер каждый раз, когда вы получаете какую-либо ссылку.

Попробуйте изменить метод, который извлекает данные, добавив @Transactional поверх него, чтобы убедиться, что транзакция открыта для метода во время его работы, в противном случае он, вероятно, будет закрыт к тому времени, когда вы попытаетесь получить дочерние объекты.

person scav    schedule 05.03.2014