You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/08/23 14:25:29 UTC

(juneau) branch docs updated: Add a DTO-first JPA/Hibernate cookbook (topic 3.20.1) and note the 10.0 marshalling proxy unwrap.

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/docs by this push:
     new 2207eb97f7 Add a DTO-first JPA/Hibernate cookbook (topic 3.20.1) and note the 10.0 marshalling proxy unwrap.
2207eb97f7 is described below

commit 2207eb97f76a0e01fa249cca973a53daf1a09dd3
Author: James Bognar <ja...@apache.org>
AuthorDate: Sun Aug 23 10:25:04 2026 -0400

    Add a DTO-first JPA/Hibernate cookbook (topic 3.20.1) and note the 10.0 marshalling proxy unwrap.
---
 pages/release-notes/10.0.0.md            | 11 ++++
 pages/topics/03.20.01.JpaAndHibernate.md | 88 ++++++++++++++++++++++++++++++++
 pages/topics/03.20.JacksonComparison.md  |  5 ++
 sidebars.ts                              |  5 ++
 4 files changed, 109 insertions(+)

diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index 5706c81163..f54cea646c 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -78,6 +78,17 @@ Key behaviours:
 See the [View-Based Projection](/docs/topics/ViewProjection) topic page for full documentation and a
 Jackson migration table.
 
+### Hibernate lazy-proxy unwrap + JPA cookbook
+
+Marshalling now deproxifies objects whose runtime class `ClassUtils.getProxyFor` recognizes,
+including Hibernate 6/7 lazy association proxies (`$HibernateProxy$` in the class name).
+Serializers use the unwrapped class (immediate superclass for subclass proxies; first
+interface for JDK proxies) so handler fields and proxy `_type` names are not emitted.
+This is a core `getClassMetaForObject` change across all formats — not a
+`juneau-marshall-hibernate` module, and not a parse-into-`@Entity` feature.
+
+See [JPA and Hibernate](/docs/topics/JpaAndHibernate).
+
 ### juneau-config / juneau-commons
 
 ### Config profiles + relaxed binding (cloud-native environment config)
diff --git a/pages/topics/03.20.01.JpaAndHibernate.md b/pages/topics/03.20.01.JpaAndHibernate.md
new file mode 100644
index 0000000000..8b471fc4aa
--- /dev/null
+++ b/pages/topics/03.20.01.JpaAndHibernate.md
@@ -0,0 +1,88 @@
+---
+title: "JPA and Hibernate"
+slug: JpaAndHibernate
+---
+
+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.
+
+```java
+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](/docs/topics/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.
diff --git a/pages/topics/03.20.JacksonComparison.md b/pages/topics/03.20.JacksonComparison.md
index 5e6a267c27..d7047c48b6 100644
--- a/pages/topics/03.20.JacksonComparison.md
+++ b/pages/topics/03.20.JacksonComparison.md
@@ -25,3 +25,8 @@ The following charts describe equivalent features between the two libraries:
 | `@JsonPropertyOrder` | <a href="/site/apidocs/org/apache/juneau/commons/bean/BeanType.html#properties()" target="_blank">@BeanType(properties="...")</a> |
 | `@JsonValue`<br/>`@JsonRawValue` | Can be replicated using swaps with `Reader` swapped values. |
 | `@JsonView(MyView.class)` | <a href="/site/apidocs/org/apache/juneau/marshall/MarshalledProp.html#view()" target="_blank">@MarshalledProp(view="MyView")</a> — see [View-Based Projection](/docs/topics/ViewProjection) |
+
+## Hibernate / JPA
+
+Jackson ships `jackson-datatype-hibernate` for ORM entity graphs. Juneau has no equivalent
+module. Prefer DTOs; see [JPA and Hibernate](/docs/topics/JpaAndHibernate).
diff --git a/sidebars.ts b/sidebars.ts
index 8f813ef85c..5366f5e211 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -404,6 +404,11 @@ const sidebars: SidebarsConfig = {
 						id: 'topics/03.20.JacksonComparison',
 						label: '3.20. Comparison with Jackson',
 					},
+					{
+						type: 'doc',
+						id: 'topics/03.20.01.JpaAndHibernate',
+						label: '3.20.1. JPA and Hibernate',
+					},
 					{
 						type: 'doc',
 						id: 'topics/03.21.PojoCategories',