Skip to main content

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.