JPA and Hibernate
Juneau has no ORM module (no analog of Jackson's jackson-datatype-hibernate).
The supported path is DTO-first: map persistence entities to marshallable beans inside
the service layer, with the persistence session still open, and return the DTO from REST.
This page covers Hibernate 6/7 with jakarta.persistence only. Hibernate 5 / javax.persistence
is not documented here.
Use DTOs
Keep @Entity types and EntityManager / Session out of serializers.
public class UserDto {
public String id;
public String name;
}
public UserDto toDto(User entity) {
var dto = new UserDto();
dto.id = entity.getId();
dto.name = entity.getName();
return dto; // session still open if you read lazy associations here
}
The REST method returns UserDto, not User.
Do not parse JSON into @Entity
Juneau will still bind a JSON object onto a matching bean via the no-arg constructor and setters if you pass an entity class to a parser. That instance is detached and unmanaged. It is not in a persistence context.
The service must find the managed entity, copy fields, and merge. Never persist the
object the parser just constructed. This is a documentation warning, not a parser feature
Juneau will add.
If you serialize an entity anyway
Initialize (or project) the graph you intend to emit so the serializer never walks an uninitialized lazy association.
Bidirectional associations: put @MarshalledIgnore / @BeanIgnore on one side, or enable
detectRecursions() — see Recursion.
jakarta.persistence.Transient is not java.beans.Transient. Juneau ignores
java.beans.Transient and Java transient fields by default. JPA @Transient is ignored
only if you also mark @MarshalledIgnore / @BeanIgnore (or exclude the property).
Bytecode-enhanced entities keep the entity class name. The $HibernateProxy$ unwrap below
does not apply. Uninitialized enhanced attributes still throw
LazyInitializationException if the serializer reads them.
PersistentBag / PersistentSet serialize as collections once initialized. An
uninitialized lazy collection is a LazyInitializationException, not null.
What marshalling unwraps
ClassUtils.getProxyFor recognizes Hibernate lazy association proxies whose class name
contains $HibernateProxy$ and returns the immediate superclass. getClassMetaForObject
uses that class for every marshalling format (JSON, XML, HTML, CSV, YAML, …), not JSON
only. Other types getProxyFor already knew (JDK dynamic proxies, $$EnhancerBySpringCGLIB$$,
Javassist, $ByteBuddy$) unwrap the same way.
JDK proxies — including annotation instances — resolve to the first interface.
Subclass proxies resolve to the immediate superclass, which is the entity for a typical
Hibernate association proxy but may be a mapped superclass under @Inheritance.
Not unwrapped: Spring 6 names that contain only $$SpringCGLIB$$ (that infix is
logger-name-only today). Bytecode-enhanced entities with no $HibernateProxy$ in the
class name.
Juneau beans as @Entity
If a type is both a Juneau bean and a JPA entity: no-arg constructor, mutable properties.
Do not persist @MarshalledIgnore fields you still need in the database, and do not
marshall fields that must stay persistence-only without an ignore annotation.
What this release does not ship
No juneau-marshall-hibernate module, no force-load / id-only lazy knobs, and no
EntityManager inside the marshaller. If LazyInitializationException or N+1 still
hurts after mapping to DTOs, that is a follow-up — not hidden settings on the serializer.