使用 Jackson 序列化 Collection 时出现 LazyInitializationException

LazyInitializationException when serializing Collection with Jackson

我正在使用 AngularJs 和 Rest 服务 (RestEasy) 构建一个应用程序,我需要在其中显示产品列表。当用户单击其中一种产品时加载列表后,将加载该产品的详细信息和图像。

我不想在显示所有产品的列表时加载与产品相关联的集合(即图片),但仅当我需要其中一个产品的详细信息时,出于性能原因,所以我使用延迟加载。

Hibernate 运行良好,我可以看到最初我只加载了一些属性而不是集合,但是在其余服务中,当 Jackson 尝试访问集合以对其进行序列化时,我得到了这个著名的异常:

Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.domainmodel.Product.Images

当然那一刻不再有Session打开,数据库事务已经提交。我不想让会话保持打开状态。所以在网上搜索我看到我们可以告诉 Jackson 不要使用注释 @JsonIgnore 序列化 属性。 这个注释必须放在实体(POJO)的 属性 或 getter 中,所以基本上,如果我告诉 Jackson 在序列化到 JSON 时不要序列化这个集合,它就会解决这个问题是暂时的,但是一旦我需要用集合加载产品,因为用户点击了它,它就不会序列化集合,所以我稍后会遇到这个问题。所以我不认为这对我来说是一个解决方案。关于如何解决这种情况的任何想法和建议?我post实体的代码和其他服务在这里。提前致谢!!

@Entity
@Table(name = "PRODUCTS")
public class Product implements Serializable {

/**
 * 
 */
private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;

@Column(name = "PATH_TO_IMAGE", nullable = false, insertable = true, updatable = true)
private String pathToImage;

@Column(name="DESCRIPTION",nullable=true, insertable = true, updatable = true)
private String description;

@ManyToOne(optional = false)
@JoinColumn(name = "XID_PRODUCT_TYPE", nullable = false, insertable = false, updatable = false)
private ProductType productType;


@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
        name = "PRODUCT_IMAGE",
        joinColumns = {@JoinColumn(name = "XID_PRODUCT", referencedColumnName = "ID")},
        inverseJoinColumns = {@JoinColumn(name = "XID_IMAGE", referencedColumnName = "ID")})
private List<Image> images;

... getters and setters 
}

然后是其余服务的代码:

@Path("/products")
public class ProductsService extends CommonService{

private static final Logger log =  Logger.getLogger(ProductsService.class);


@EJB
private ProductsServiceHandler handler;

@GET
@Produces(MediaType.APPLICATION_JSON)
public Response getAllProducts(@Context Request request, @Context HttpHeaders httpHeaders)
{
    try
    {
        List<Product> products=handler.findAllProducts();
        return Response.ok(products).header(ALLOW_ORIGIN_HEADER, "*").build();
    }
    catch(Exception e){return Response.status(Status.INTERNAL_SERVER_ERROR).build();}
}

}

请求的完整堆栈跟踪:

 2015-02-03 00:35:41,085 ERROR [io.undertow.request] (default task-9) UT005023: Exception handling request to /arenaclub/rest/products: org.jboss.resteasy.spi.UnhandledException: Response is committed, can't handle exception
at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:148) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:432) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:376) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:179) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:220) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51) [resteasy-jaxrs-3.0.8.Final.jar:]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) [jboss-servlet-api_3.1_spec-1.0.0.Final.jar:1.0.0.Final]
at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85) [undertow-servlet-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:61) [undertow-servlet-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) [undertow-servlet-1.0.15.Final.jar:1.0.15.Final]
at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25) [undertow-core-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:113) [undertow-servlet-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:56) [undertow-servlet-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25) [undertow-core-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:45) [undertow-core-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:61) [undertow-servlet-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:58) [undertow-core-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:70) [undertow-servlet-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.security.handlers.SecurityInitialHandler.handleRequest(SecurityInitialHandler.java:76) [undertow-core-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25) [undertow-core-1.0.15.Final.jar:1.0.15.Final]
at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25) [undertow-core-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25) [undertow-core-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:240) [undertow-servlet-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:227) [undertow-servlet-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.access[=13=]0(ServletInitialHandler.java:73) [undertow-servlet-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:146) [undertow-servlet-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:177) [undertow-core-1.0.15.Final.jar:1.0.15.Final]
at io.undertow.server.HttpServerExchange.run(HttpServerExchange.java:727) [undertow-core-1.0.15.Final.jar:1.0.15.Final]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [rt.jar:1.8.0_25]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [rt.jar:1.8.0_25]
at java.lang.Thread.run(Thread.java:745) [rt.jar:1.8.0_25]
Caused by: com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.fgonzalez.domainmodel.Product.images, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.fgonzalez.domainmodel.Product["images"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:232) [jackson-databind-2.3.2.jar:2.3.2]
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:197) [jackson-databind-2.3.2.jar:2.3.2]
at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:187) [jackson-databind-2.3.2.jar:2.3.2]
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:652) [jackson-databind-2.3.2.jar:2.3.2]
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:152) [jackson-databind-2.3.2.jar:2.3.2]
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:100) [jackson-databind-2.3.2.jar:2.3.2]
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:21) [jackson-databind-2.3.2.jar:2.3.2]
at com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase.serialize(AsArraySerializerBase.java:183) [jackson-databind-2.3.2.jar:2.3.2]
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:114) [jackson-databind-2.3.2.jar:2.3.2]
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:610) [jackson-databind-2.3.2.jar:2.3.2]
at org.jboss.resteasy.plugins.providers.jackson.ResteasyJackson2Provider.writeTo(ResteasyJackson2Provider.java:186) [resteasy-jackson2-provider-3.0.8.Final.jar:]
at org.jboss.resteasy.core.interception.AbstractWriterInterceptorContext.writeTo(AbstractWriterInterceptorContext.java:129) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.core.interception.ServerWriterInterceptorContext.writeTo(ServerWriterInterceptorContext.java:62) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.core.interception.AbstractWriterInterceptorContext.proceed(AbstractWriterInterceptorContext.java:118) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.security.doseta.DigitalSigningInterceptor.aroundWriteTo(DigitalSigningInterceptor.java:143) [resteasy-crypto-3.0.8.Final.jar:]
at org.jboss.resteasy.core.interception.AbstractWriterInterceptorContext.proceed(AbstractWriterInterceptorContext.java:122) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.plugins.interceptors.encoding.GZIPEncodingInterceptor.aroundWriteTo(GZIPEncodingInterceptor.java:100) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.core.interception.AbstractWriterInterceptorContext.proceed(AbstractWriterInterceptorContext.java:122) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:99) [resteasy-jaxrs-3.0.8.Final.jar:]
at org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:427) [resteasy-jaxrs-3.0.8.Final.jar:]
... 32 more

您可以 运行 Jackson 在 JPA 会话中,这样延迟加载就可以了。

您可以做几件事。

  1. 最快的方法 - 遍历 products 并在 return 计算结果
  2. 之前对每个产品调用 setImages(null)
  3. Product 注册两个自定义 JSON 序列化器,一个会序列化图像,一个不会,然后根据调用的服务选择序列化器。问题是我不确定这可以用 RestEasy 完成,但请查看 this thread 以获得一些提示
  4. 不要 return 来自 REST 服务的实体 bean,而是使用 DTO,这样您就可以轻松地控制 return 的内容和格式。另一大优点是您将 REST 层与实体 bean 分离,这使您可以自由更改实体,而不必担心更改 REST 用户期望的格式。这将是首选。

您可以考虑两种可能的选择:

  1. 为您的服务层使用单独的数据传输对象 (DTO),因此您将在实体-> DTO期间管理获取所需信息转换。这种方法的缺点 - 您需要编写和维护这些转换器。另一方面,您可以完全灵活地将任何实体组合到您的服务响应中。首选。
  2. 创建单独的轻量级实体(例如"ProductInfo")用于"products"调用。将多个实体映射到一个数据库应该没有任何问题 table。

Jackson 项目的人们开发了一个很好的模块来解决这个问题!

在 github 上查看: https://github.com/FasterXML/jackson-datatype-hibernate