Skip to content

Commit 7a80ebe

Browse files
committed
Add CTE documentation to Hibernate tutorial
Signed-off-by: Marvin Froeder <velo.br@gmail.com>
1 parent f3ff952 commit 7a80ebe

2 files changed

Lines changed: 102 additions & 3 deletions

File tree

docs/tutorials/hibernate.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,102 @@ List results = hibernateQuery.list();
154154
This returns a Hibernate `org.hibernate.query.Query` rather than a JPA
155155
`jakarta.persistence.Query`.
156156

157+
## Common Table Expressions
158+
159+
`HibernateQuery` supports Common Table Expressions (CTEs) via the `with()`
160+
method family. CTEs define named temporary result sets that can be referenced
161+
in the main query. This feature requires Hibernate 6.5 or later and is not
162+
available on `JPAQuery`.
163+
164+
| Method | Description |
165+
|---|---|
166+
| `with(alias, subquery)` | Define a CTE |
167+
| `withMaterializedHint(alias, subquery)` | Define a CTE with `MATERIALIZED` hint |
168+
| `withNotMaterializedHint(alias, subquery)` | Define a CTE with `NOT MATERIALIZED` hint |
169+
170+
### Simple CTE with Join
171+
172+
Define a CTE that selects a specific cat's weight, then join against it to
173+
find lighter cats:
174+
175+
```java
176+
QCat cat = QCat.cat;
177+
QCat felix = new QCat("felix");
178+
179+
Cat result = new HibernateQuery<Cat>(session)
180+
.withNotMaterializedHint(felix,
181+
JPAExpressions.select(cat.bodyWeight.as(felix.bodyWeight))
182+
.from(cat)
183+
.where(cat.name.eq("Felix")))
184+
.select(cat)
185+
.from(felix)
186+
.join(cat).on(cat.bodyWeight.lt(felix.bodyWeight))
187+
.orderBy(cat.bodyWeight.desc())
188+
.limit(1)
189+
.fetchOne();
190+
```
191+
192+
Generated HQL:
193+
194+
```
195+
with
196+
felix as not materialized (select cat.bodyWeight as bodyWeight
197+
from Cat cat
198+
where cat.name = ?1)
199+
select cat
200+
from Cat cat, felix felix
201+
where cat.bodyWeight > felix.bodyWeight
202+
```
203+
204+
### CTE with Custom Column
205+
206+
Use `Expressions.numberPath()` to create custom column references within a CTE:
207+
208+
```java
209+
QCat cat = QCat.cat;
210+
QCat avgCat = new QCat("avgcat");
211+
NumberPath<Double> avgWeight = Expressions.numberPath(Double.class, avgCat, "avgweight");
212+
213+
List<Cat> results = new HibernateQuery<Cat>(session)
214+
.with(avgCat,
215+
JPAExpressions.select(cat.bodyWeight.avg().as(avgWeight))
216+
.from(cat))
217+
.select(cat)
218+
.from(cat, avgCat)
219+
.orderBy(cat.bodyWeight.subtract(avgWeight).abs().asc(), cat.id.asc())
220+
.fetch();
221+
```
222+
223+
### Multiple CTEs
224+
225+
Chain `.with()` calls to define multiple CTEs. Later CTEs can reference earlier
226+
ones:
227+
228+
```java
229+
QCat cat = QCat.cat;
230+
QCat felix = new QCat("felix");
231+
QCat felixMates = new QCat("felixMates");
232+
233+
List<Integer> results = new HibernateQuery<Integer>(session)
234+
.with(felix,
235+
JPAExpressions.select(cat.id.as(felix.id))
236+
.from(cat)
237+
.where(cat.name.eq("Felix")))
238+
.with(felixMates,
239+
JPAExpressions.select(cat.id.as(cat.id))
240+
.from(cat)
241+
.innerJoin(felix).on(cat.mate.id.eq(felix.id)))
242+
.select(felixMates.id)
243+
.from(felixMates)
244+
.fetch();
245+
```
246+
247+
{: .note }
248+
CTE aliases reuse existing Q-types (e.g., `new QCat("felix")`) to define CTE
249+
columns. Use `as()` in projections to map columns from the subquery to the CTE
250+
alias fields. The `JPAExpressions` factory is used for CTE subqueries, same as
251+
for standard JPQL subqueries.
252+
157253
## Native SQL with Hibernate
158254

159255
Use `HibernateSQLQuery` to run native SQL through a Hibernate `Session`:
@@ -173,6 +269,7 @@ for more details on native SQL query patterns.
173269
|---|---|---|
174270
| Underlying API | JPA EntityManager | Hibernate Session |
175271
| Query factory | `JPAQueryFactory` | `HibernateQueryFactory` |
272+
| Common Table Expressions | Not available | `with()`, `withMaterializedHint()`, `withNotMaterializedHint()` |
176273
| Query caching | Not available | `setCacheable()`, `setCacheRegion()` |
177274
| Read-only mode | Not available | `setReadOnly()` |
178275
| SQL comments | Not available | `setComment()` |

docs/tutorials/jpa.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ The `JPAAnnotationProcessor` finds domain types annotated with the
5858
If you use Hibernate annotations in your domain types, use the processor
5959
`com.querydsl.apt.hibernate.HibernateAnnotationProcessor` instead. See the
6060
[Hibernate tutorial]({{ site.baseurl }}/tutorials/hibernate) for
61-
Hibernate-specific features such as query caching and read-only mode.
61+
Hibernate-specific features such as Common Table Expressions, query caching,
62+
and read-only mode.
6263

6364
Run `mvn clean install` and your query types will be generated into
6465
`target/generated-sources/java`.
@@ -155,8 +156,9 @@ Both `JPAQuery` and `HibernateQuery` implement the `JPQLQuery` interface.
155156

156157
For the examples in this chapter, queries are created via a `JPAQueryFactory`
157158
instance. `JPAQueryFactory` should be the preferred option for obtaining
158-
`JPAQuery` instances. For the Hibernate API, `HibernateQueryFactory` can be used. For
159-
Hibernate-specific features, see the
159+
`JPAQuery` instances. For the Hibernate API, `HibernateQueryFactory` can be
160+
used. For Hibernate-specific features such as CTEs, query caching, and
161+
read-only mode, see the
160162
[Hibernate tutorial]({{ site.baseurl }}/tutorials/hibernate).
161163

162164
To retrieve the customer with the first name Bob:

0 commit comments

Comments
 (0)