You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Use it when the result is meant to be read-only, such as:
266
+
267
+
- service/query methods returning entity graphs for display or serialization
268
+
- query results you want the application to treat as immutable
269
+
- cached query results or other shared read models backed by entity graphs
270
+
- partial entity graphs where you want accidental lazy loading to fail fast
271
+
272
+
### When **not** to use it
273
+
274
+
Do **not** use `setUnmodifiable(true)` when the caller will:
275
+
276
+
- modify the beans and save them later
277
+
- rely on lazy loading of associations or unloaded scalar properties
278
+
- treat the result as a working persistence model rather than a read-only view
279
+
280
+
### Agent rule
281
+
282
+
If you are returning entity beans for read-only use, `setUnmodifiable(true)`
283
+
should be the default recommendation. If the caller needs a mutable model or a
284
+
serialized summary shape, choose mutable entities or DTO projection instead.
285
+
286
+
---
287
+
288
+
## Step 7 - Use `fetchQuery()` for to-many paths and `FetchGroup` for reusable query shapes
236
289
237
290
Ebean applies important SQL rules when translating ORM queries:
238
291
@@ -292,7 +345,7 @@ plain `fetch(...)` on those paths. `fetchQuery()` is often the safer default.
292
345
293
346
---
294
347
295
-
## Step 7 - Use DTO projection when the caller does not need entity beans
348
+
## Step 8 - Use DTO projection when the caller does not need entity beans
296
349
297
350
For list screens, API summaries, exports, or read-model views, the caller often
298
351
does **not** need managed entity beans. In those cases, project directly to a
@@ -323,7 +376,7 @@ List<CustomerSummary> summaries = new QCustomer()
323
376
324
377
---
325
378
326
-
## Step 8 - Only fall back to raw SQL when the ORM query is not a good fit
379
+
## Step 9 - Only fall back to raw SQL when the ORM query is not a good fit
327
380
328
381
Prefer the following order:
329
382
@@ -382,7 +435,12 @@ Customer customer = new QCustomer()
382
435
If the caller only needs summary fields, return a DTO instead of partially
383
436
loaded entities that might later trigger more loading or confuse serializers.
384
437
385
-
### Anti-pattern 4 - Fetching every relationship "just in case"
438
+
### Anti-pattern 4 - Returning mutable entity graphs for read-only use
439
+
440
+
If the caller is only meant to read the result, prefer `setUnmodifiable(true)`
441
+
so accidental setter calls, collection mutation, and lazy loading fail fast.
442
+
443
+
### Anti-pattern 5 - Fetching every relationship "just in case"
386
444
387
445
Do not eagerly fetch large object graphs unless the immediate caller will use
388
446
them. Query tuning is part of the job.
@@ -398,6 +456,8 @@ them. Query tuning is part of the job.
398
456
| Old `Q*` class still appears after entity rename | Stale generated source/class output | Run a clean rebuild |
399
457
|`findOne()` fails because multiple rows match | Predicate is not unique | Use `findList()` or tighten the predicate |
400
458
| Returned entities only have some fields loaded |`select()` or `FetchGroup` limited the query shape | Add the required fields or switch to DTO projection |
459
+
| Setter calls or collection mutation fail on query results |`setUnmodifiable(true)` returned a read-only graph | Remove `setUnmodifiable(true)` or treat the result as read-only |
460
+
| Accessing an unloaded property throws `LazyInitialisationException`|`setUnmodifiable(true)` disables lazy loading | Fetch the property up front or use DTO projection |
401
461
| Ebean executes secondary queries for a to-many path | ORM rules avoided cartesian product or honored `maxRows`| This is expected; use `fetchQuery()` explicitly when appropriate |
402
462
403
463
---
@@ -410,9 +470,10 @@ When asked to add or modify an Ebean query:
410
470
2. Choose the terminal method first (`exists`, `findOne`, `findList`, `findPagedList`, `asDto`)
411
471
3. Add predicates with query bean properties and association traversal
412
472
4. Add explicit ordering and pagination if relevant
413
-
5. Tune the fetch shape with `select()` / `fetch()` / `fetchQuery()` / `FetchGroup`
414
-
6. Prefer DTO projection for read models and serialized responses
415
-
7. Only use raw SQL if the ORM query is genuinely the wrong tool
473
+
5. If the result is read-only entity data, consider `setUnmodifiable(true)`
474
+
6. Tune the fetch shape with `select()` / `fetch()` / `fetchQuery()` / `FetchGroup`
475
+
7. Prefer DTO projection for read models and serialized responses
476
+
8. Only use raw SQL if the ORM query is genuinely the wrong tool
0 commit comments