Issue
I come from a Payara/EclipseLink background where this just works out of the box. My contract requires me to use EAP which does not support EclipseLink.persistance and I always prefer the "provided" over adding libraries.
I am creating a very simple REST microservice with very simple relationships for my objects. However, when I try to access an object, I am getting a serialization error (specifics below).
ENTITY ONE
@Entity
@Table(name = "training_centers")
@NamedQuery(name = TrainingCenterEntity.findAll, query = "SELECT c FROM TrainingCenterEntity c")
@JsonbVisibility(PrivateVisibilityStrategy.class)
public class TrainingCenterEntity implements Serializable, ValidEntity {
private static final long serialVersionUID = 9197911968578298904L;
public static final String PREFIX = "centers.entity.TrainingCenter.";
public static final String findAll = PREFIX + "findAll";
@Id
@Column(name = "program_code")
private String program_code;
@Column(name = "program_name", length = 255, nullable = false)
private String program_name;
public TrainingCenterEntity(String code, String name) {
this.program_code = code;
this.program_name = name;
this.classifications = new ArrayList<ClassificationEntity>();
}
public TrainingCenterEntity() {
this.classifications = new ArrayList<ClassificationEntity>();
}
public String getProgramCode() {
return program_code;
}
public void setProgramCode(String program_code) {
this.program_code = program_code;
}
@OneToMany(
fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
orphanRemoval = true,
mappedBy = "center")
private List<ClassificationEntity> classifications;
ENTITY TWO
@Entity
@Table(name = "classifications")
@NamedQueries({
@NamedQuery(name = ClassificationEntity.findByProgramCode, query = "SELECT c FROM ClassificationEntity c WHERE c.center = :programCode"),
@NamedQuery(name = ClassificationEntity.findAll, query = "SELECT c FROM ClassificationEntity c")
})
@JsonbVisibility(PrivateVisibilityStrategy.class)
public class ClassificationEntity implements Serializable, ValidEntity{
private static final long serialVersionUID = 5138672261884252346L;
public static final String PREFIX = "centers.entity.ClassificationEntity.";
public static final String findByProgramCode = PREFIX + "findByProgramCode";
public static final String findAll = PREFIX + "findAll";
@Id
@Column(name = "classification_id", columnDefinition = "serial")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "classification_type", unique = true, nullable = false)
private ClassificationType classificationType;
@Column(name = "classification_name", nullable = true, length = 255)
private String classificationName;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "program_code", referencedColumnName = "program_code")
private TrainingCenterEntity center;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public ClassificationEntity() {
this.center = new TrainingCenterEntity();
}
REST END POINT FOR ENTITY ONE
@GET
@Produces({MediaType.APPLICATION_JSON})
public List<TrainingCenterEntity> findAll() {
return this.manager.findAll();
}
StackTrace for REST Endpoint
11:44:03,129 SEVERE [org.eclipse.yasson.internal.Marshaller] (default task-1) Unable to serialize property 'classifications' from ptsi.service.centers.entity.TrainingCenterEntity
11:44:03,129 SEVERE [org.eclipse.yasson.internal.Marshaller] (default task-1) Generating incomplete JSON
11:44:03,129 INFO [io.jaegertracing.internal.reporters.LoggingReporter] (default task-1) Span reported: 33fb13e2e5f679d8:33fb13e2e5f679d8:0:1 - GET:ptsi.service.centers.boundary.TrainingCenterResource.findAll
11:44:03,129 ERROR [io.undertow.request] (default task-1) UT005023: Exception handling request to /learning-hibernate/api/centers: org.jboss.resteasy.spi.UnhandledException: javax.ws.rs.ProcessingException: RESTEASY008205: JSON Binding serialization error javax.json.bind.JsonbException: Unable to serialize property 'classifications' from ptsi.service.centers.entity.TrainingCenterEntity
at [email protected]//org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:356)
at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:193)
at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:539)
at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:461)
at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:229)
at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:135)
at [email protected]//org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:356)
at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:138)
at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:215)
at [email protected]//org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:227)
at [email protected]//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
at [email protected]//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)
at [email protected]//javax.servlet.http.HttpServlet.service(HttpServlet.java:590)
at [email protected]//io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
at [email protected]//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
at io.opentracing.contrib.opentracing-jaxrs2//io.opentracing.contrib.jaxrs2.server.SpanFinishingFilter.doFilter(SpanFinishingFilter.java:52)
at [email protected]//io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
at [email protected]//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
at [email protected]//io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
at [email protected]//io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
at [email protected]//io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
at [email protected]//io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at [email protected]//org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at [email protected]//io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
at [email protected]//io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
at [email protected]//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at [email protected]//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
at [email protected]//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
at [email protected]//io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
at [email protected]//io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
at [email protected]//io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
at [email protected]//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at [email protected]//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at [email protected]//org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130)
at [email protected]//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
at [email protected]//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
at [email protected]//org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1541)
at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1541)
at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1541)
at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1541)
at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1541)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78)
at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99)
at [email protected]//io.undertow.server.Connectors.executeRootHandler(Connectors.java:376)
at [email protected]//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
at [email protected]//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
at [email protected]//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1982)
at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: javax.ws.rs.ProcessingException: RESTEASY008205: JSON Binding serialization error javax.json.bind.JsonbException: Unable to serialize property 'classifications' from ptsi.service.centers.entity.TrainingCenterEntity
at org.jboss.resteasy.resteasy-json-binding-provider@3.11.2.Final-redhat-00002//org.jboss.resteasy.plugins.providers.jsonb.JsonBindingProvider.writeTo(JsonBindingProvider.java:160)
at [email protected]//org.jboss.resteasy.core.interception.AbstractWriterInterceptorContext.writeTo(AbstractWriterInterceptorContext.java:137)
at [email protected]//org.jboss.resteasy.core.interception.ServerWriterInterceptorContext.writeTo(ServerWriterInterceptorContext.java:61)
at [email protected]//org.jboss.resteasy.core.interception.AbstractWriterInterceptorContext.proceed(AbstractWriterInterceptorContext.java:124)
at [email protected]//org.jboss.resteasy.security.doseta.DigitalSigningInterceptor.aroundWriteTo(DigitalSigningInterceptor.java:147)
at [email protected]//org.jboss.resteasy.core.interception.AbstractWriterInterceptorContext.proceed(AbstractWriterInterceptorContext.java:129)
at [email protected]//org.jboss.resteasy.core.ServerResponseWriter.lambda$writeNomapResponse$2(ServerResponseWriter.java:151)
at [email protected]//org.jboss.resteasy.core.interception.ContainerResponseContextImpl.filter(ContainerResponseContextImpl.java:398)
at [email protected]//org.jboss.resteasy.core.ServerResponseWriter.executeFilters(ServerResponseWriter.java:219)
at [email protected]//org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:95)
at [email protected]//org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:69)
at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:530)
... 58 more
Caused by: javax.json.bind.JsonbException: Unable to serialize property 'classifications' from ptsi.service.centers.entity.TrainingCenterEntity
at [email protected]//org.eclipse.yasson.internal.serializer.ObjectSerializer.serializeInternal(ObjectSerializer.java:67)
at [email protected]//org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:64)
at [email protected]//org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serializerCaptor(AbstractContainerSerializer.java:96)
at [email protected]//org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serializeItem(AbstractContainerSerializer.java:157)
at [email protected]//org.eclipse.yasson.internal.serializer.CollectionSerializer.serializeInternal(CollectionSerializer.java:39)
at [email protected]//org.eclipse.yasson.internal.serializer.CollectionSerializer.serializeInternal(CollectionSerializer.java:27)
at [email protected]//org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:64)
at [email protected]//org.eclipse.yasson.internal.Marshaller.serializeRoot(Marshaller.java:148)
at [email protected]//org.eclipse.yasson.internal.Marshaller.marshall(Marshaller.java:76)
at [email protected]//org.eclipse.yasson.internal.Marshaller.marshall(Marshaller.java:102)
at [email protected]//org.eclipse.yasson.internal.JsonBinding.toJson(JsonBinding.java:118)
at org.jboss.resteasy.resteasy-json-binding-provider@3.11.2.Final-redhat-00002//org.jboss.resteasy.plugins.providers.jsonb.JsonBindingProvider.writeTo(JsonBindingProvider.java:156)
... 69 more
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: ptsi.service.centers.entity.TrainingCenterEntity.classifications, could not initialize proxy - no Session
at [email protected]//org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:602)
at [email protected]//org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:217)
at [email protected]//org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:581)
at [email protected]//org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:148)
at [email protected]//org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:390)
at [email protected]//org.eclipse.yasson.internal.serializer.CollectionSerializer.serializeInternal(CollectionSerializer.java:38)
at [email protected]//org.eclipse.yasson.internal.serializer.CollectionSerializer.serializeInternal(CollectionSerializer.java:27)
at [email protected]//org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:64)
at [email protected]//org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serializerCaptor(AbstractContainerSerializer.java:96)
at [email protected]//org.eclipse.yasson.internal.serializer.ObjectSerializer.marshallProperty(ObjectSerializer.java:110)
at [email protected]//org.eclipse.yasson.internal.serializer.ObjectSerializer.serializeInternal(ObjectSerializer.java:65)
... 80 more
The same error appears for the other side. When using EclipseLink, this just works out of the box, and I have tried a couple fo days but every example I have seen does not seem to address my specific issue.
For Clarity, here is the boundary code for ENTITY ONE
RESOURCE for ENTITY ONE
@Stateless
@Path("centers")
public class TrainingCenterResource {
@Inject
TrainingCenterManager manager;
// POST should return 201 with Location URI in header
@POST
@Consumes({MediaType.APPLICATION_JSON})
public Response save(TrainingCenterEntity center, @Context UriInfo info) {
TrainingCenterEntity saved = this.manager.save(center);
String id = saved.getProgramCode();
URI uri = info.getAbsolutePathBuilder().path("/" + id).build();
return Response.created(uri).build();
}
@GET
@Path("{id}")
@Produces({MediaType.APPLICATION_JSON})
public Response find(@PathParam("id") String id) {
TrainingCenterEntity center = this.manager.findById(id);
if (center != null) {
return Response.ok(center).build();
}
else {
return Response.status(Response.Status.NOT_FOUND).build();
}
}
@GET
@Produces({MediaType.APPLICATION_JSON})
public List<TrainingCenterEntity> findAll() {
return this.manager.findAll();
}
@PUT
@Path("{id}")
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
public TrainingCenterEntity update(@PathParam("id") String id, TrainingCenterEntity center) {
center.setProgramCode(id);
return this.manager.save(center);
}
@DELETE
@Path("{id}")
public void delete(@PathParam("id") String id) {
this.manager.delete(id);
}
}
PERSISTANCE MANAGER FOR ENTITY ONE
@Stateless
public class TrainingCenterManager {
@Inject
private Logger LOGGER;
@Inject
private EntityManager em;
public TrainingCenterEntity save(TrainingCenterEntity center) {
try {
return this.em.merge(center);
} catch (PersistenceException pex) {
LOGGER.log(Level.FINE, pex.toString(), pex);
return null;
}
}
public TrainingCenterEntity findById(String code) {
try {
return this.em.find(TrainingCenterEntity.class, code);
} catch (Exception e) {
LOGGER.log(Level.FINE, e.toString(), e);
return null;
}
}
public List<TrainingCenterEntity> findAll() {
try {
return this.em.createNamedQuery(TrainingCenterEntity.findAll, TrainingCenterEntity.class).getResultList();
} catch (Exception e) {
LOGGER.log(Level.FINE, e.toString(), e);
return null;
}
}
public void delete(String code) {
try {
TrainingCenterEntity reference = this.em.getReference(TrainingCenterEntity.class, code);
this.em.remove(this.em.merge(reference));
} catch (PersistenceException pex) {
LOGGER.log(Level.FINE, pex.toString(), pex);
}
}
}
Solution
Per spec, your manager EJB finds all entities and loads then into the returned List, but JPA defaults lists - such as classifications in your entity - as lazy loaded fields.
When your findAll method returns, there is no more transaction and the entities inside the lists became detached from the EntityManager. As such, the entity manager can't to load the contents of each classifications field to allow serialization.
You may change the behavior of your entity manager using some internal properties or even define your classifications field as eager fetched, but this may induce performance problems when you use the TrainingCenterEntity without accessing the classifications field (the classifications will be retrieved from database every time you get a center).
Your best approach is changing your named query to use JOIN FETCH:
@NamedQuery(name = TrainingCenterEntity.findAll, query = "SELECT c FROM TrainingCenterEntity c JOIN FETCH c.classifications")
JOIN FETCH allows the JPA to eager fetch the contents of otherwise lazy loaded fields.
Please note that:
- any findAll method tends to expose to much data; and
- some implementations of JSON serialization libraries are not intelligent enough to detect circular relations, so your classification -> trainingcenter may cause a stack overflow if not properly annotated as ignored on serialization (@JsonIgnore in Jackson case).
Your code works without issues using EclipseLink probably because your entities weren't static weaved prior execution. Some defaults configurations of EclipseLink also allows to load lazy fields in some different scenarios even with static weaved lazy loaded definitions. In hibernate, the weaving is called bytecode enhancement and it's made automatically.
Answered By - Paulo Araújo