0
# Utility Operations
1
2
Utility classes for transaction management, EntityManager access, parameter building, and sorting configuration. These utilities provide essential supporting functionality for Panache operations and query building.
3
4
## Capabilities
5
6
### Panache Utility Class
7
8
The Panache utility class provides global operations for EntityManager access, transaction management, and database operations.
9
10
```java { .api }
11
public class Panache {
12
// EntityManager access
13
public static EntityManager getEntityManager();
14
public static Session getSession();
15
public static EntityManager getEntityManager(Class<?> clazz);
16
public static Session getSession(Class<?> clazz);
17
public static EntityManager getEntityManager(String persistenceUnit);
18
public static Session getSession(String persistenceUnit);
19
20
// Transaction management
21
public static TransactionManager getTransactionManager();
22
public static void setRollbackOnly();
23
24
// Database operations
25
public static int executeUpdate(String query, Object... params);
26
public static int executeUpdate(String query, Map<String, Object> params);
27
public static int executeUpdate(String query, Parameters params);
28
29
// Flushing
30
public static void flush();
31
public static void flush(Class<?> clazz);
32
public static void flush(String persistenceUnit);
33
}
34
```
35
36
**Usage:**
37
```java
38
import io.quarkus.hibernate.orm.panache.Panache;
39
40
// Access default EntityManager/Session
41
EntityManager em = Panache.getEntityManager();
42
Session session = Panache.getSession();
43
44
// Access EntityManager for specific entity class
45
EntityManager personEm = Panache.getEntityManager(Person.class);
46
Session personSession = Panache.getSession(Person.class);
47
48
// Access EntityManager for specific persistence unit
49
EntityManager customEm = Panache.getEntityManager("custom-pu");
50
51
// Transaction management
52
TransactionManager tm = Panache.getTransactionManager();
53
Panache.setRollbackOnly(); // Mark transaction for rollback
54
55
// Execute update queries
56
int updated = Panache.executeUpdate("UPDATE Person SET status = ?1 WHERE age > ?2", PersonStatus.Adult, 18);
57
58
// Execute with named parameters
59
int deleted = Panache.executeUpdate("DELETE FROM Person WHERE status = :status",
60
Parameters.with("status", PersonStatus.Inactive));
61
62
// Manual flush operations
63
Panache.flush(); // Flush default EntityManager
64
Panache.flush(Person.class); // Flush EntityManager for Person
65
Panache.flush("custom-pu"); // Flush specific persistence unit
66
```
67
68
### Parameters Builder
69
70
Utility class for building named parameter maps in a fluent manner.
71
72
```java { .api }
73
public class Parameters {
74
// Static factory method
75
public static Parameters with(String name, Object value);
76
77
// Builder methods
78
public Parameters and(String name, Object value);
79
80
// Convert to Map
81
public Map<String, Object> map();
82
}
83
```
84
85
**Usage:**
86
```java
87
import io.quarkus.panache.common.Parameters;
88
89
// Simple parameter building
90
Parameters params = Parameters.with("status", PersonStatus.Active)
91
.and("city", "NYC")
92
.and("minAge", 18);
93
94
// Use in queries
95
List<Person> persons = Person.list("status = :status AND city = :city AND age >= :minAge", params);
96
97
// Convert to Map if needed
98
Map<String, Object> paramMap = params.map();
99
100
// Complex parameter building
101
Parameters complexParams = Parameters.with("namePattern", "%John%")
102
.and("statuses", Arrays.asList(PersonStatus.Active, PersonStatus.Pending))
103
.and("startDate", LocalDate.now().minusYears(1))
104
.and("endDate", LocalDate.now());
105
106
List<Person> results = Person.list(
107
"name ILIKE :namePattern AND status IN :statuses AND createdDate BETWEEN :startDate AND :endDate",
108
complexParams
109
);
110
```
111
112
### Page Class
113
114
Immutable utility class for representing pagination information.
115
116
```java { .api }
117
public class Page {
118
// Fields
119
public final int index; // 0-based page index
120
public final int size; // Page size
121
122
// Constructors
123
public Page(int size);
124
public Page(int index, int size);
125
126
// Static factory methods
127
public static Page of(int index, int size);
128
public static Page ofSize(int size);
129
130
// Navigation methods
131
public Page next();
132
public Page previous();
133
public Page first();
134
public Page index(int newIndex);
135
}
136
```
137
138
**Usage:**
139
```java
140
import io.quarkus.panache.common.Page;
141
142
// Create page with default index 0
143
Page firstPage = new Page(20); // 20 items per page, page 0
144
Page firstPageAlt = Page.ofSize(20); // Same as above
145
146
// Create specific page
147
Page secondPage = new Page(1, 20); // Page 1 (second page), 20 items
148
Page secondPageAlt = Page.of(1, 20); // Same as above
149
150
// Navigation
151
Page page = Page.of(0, 10);
152
Page nextPage = page.next(); // Page(1, 10)
153
Page prevPage = nextPage.previous(); // Page(0, 10)
154
Page firstPage2 = page.first(); // Page(0, 10)
155
Page specificPage = page.index(5); // Page(5, 10)
156
157
// Use with queries
158
List<Person> persons = Person.findAll()
159
.page(Page.of(0, 25))
160
.list();
161
162
// Pagination loop
163
Page currentPage = Page.ofSize(100);
164
PanacheQuery<Person> query = Person.findAll();
165
166
while (true) {
167
List<Person> batch = query.page(currentPage).list();
168
if (batch.isEmpty()) break;
169
170
processBatch(batch);
171
currentPage = currentPage.next();
172
}
173
```
174
175
### Sort Class
176
177
Utility class for building SQL sorting specifications with direction and null precedence control.
178
179
```java { .api }
180
public class Sort {
181
// Static factory methods
182
public static Sort by(String column);
183
public static Sort by(String column, Direction direction);
184
public static Sort by(String column, NullPrecedence nullPrecedence);
185
public static Sort by(String column, Direction direction, NullPrecedence nullPrecedence);
186
public static Sort by(String... columns);
187
public static Sort ascending(String... columns);
188
public static Sort descending(String... columns);
189
public static Sort empty();
190
191
// Builder methods
192
public Sort and(String name);
193
public Sort and(String name, Direction direction);
194
public Sort and(String name, NullPrecedence nullPrecedence);
195
public Sort and(String name, Direction direction, NullPrecedence nullPrecedence);
196
197
// Direction control
198
public Sort descending();
199
public Sort ascending();
200
public Sort direction(Direction direction);
201
202
// Configuration
203
public Sort disableEscaping();
204
205
// Access methods
206
public List<Column> getColumns();
207
public boolean isEscapingEnabled();
208
209
// Nested types
210
public enum Direction { Ascending, Descending }
211
public enum NullPrecedence { NULLS_FIRST, NULLS_LAST }
212
213
public static class Column {
214
public String getName();
215
public Direction getDirection();
216
public NullPrecedence getNullPrecedence();
217
}
218
}
219
```
220
221
**Usage:**
222
```java
223
import io.quarkus.panache.common.Sort;
224
225
// Simple sorting
226
Sort nameSort = Sort.by("name");
227
Sort nameDescSort = Sort.by("name", Sort.Direction.Descending);
228
229
// Multiple columns
230
Sort multiSort = Sort.by("lastName", "firstName");
231
Sort mixedSort = Sort.by("lastName").and("firstName", Sort.Direction.Descending);
232
233
// Convenience methods
234
Sort ascSort = Sort.ascending("name", "age");
235
Sort descSort = Sort.descending("createdDate", "id");
236
237
// Null precedence
238
Sort nullsFirstSort = Sort.by("name", Sort.NullPrecedence.NULLS_FIRST);
239
Sort complexSort = Sort.by("status", Sort.Direction.Ascending, Sort.NullPrecedence.NULLS_LAST)
240
.and("name");
241
242
// Direction changes
243
Sort sort = Sort.by("name", "age").descending(); // Makes both columns descending
244
Sort mixed = Sort.by("name").and("age").direction(Sort.Direction.Descending);
245
246
// Use with queries
247
List<Person> sorted = Person.listAll(Sort.by("lastName", "firstName"));
248
249
List<Person> complexSorted = Person.list("status = :status",
250
Parameters.with("status", PersonStatus.Active),
251
Sort.by("priority", Sort.Direction.Descending)
252
.and("createdDate", Sort.Direction.Ascending)
253
);
254
255
// Disable column escaping if needed
256
Sort unescaped = Sort.by("custom_column").disableEscaping();
257
```
258
259
### Advanced Utility Usage Examples
260
261
Combining utilities for complex operations:
262
263
```java
264
public class PersonService {
265
266
@Transactional
267
public PagedResult<Person> searchPersonsPaginated(
268
String namePattern,
269
PersonStatus status,
270
String sortBy,
271
String sortDirection,
272
int page,
273
int size) {
274
275
// Build parameters
276
Parameters params = Parameters.with("namePattern", "%" + namePattern + "%")
277
.and("status", status);
278
279
// Build sort
280
Sort.Direction direction = "desc".equalsIgnoreCase(sortDirection)
281
? Sort.Direction.Descending
282
: Sort.Direction.Ascending;
283
Sort sort = Sort.by(sortBy, direction);
284
285
// Build page
286
Page pageObj = Page.of(page, size);
287
288
// Execute query
289
PanacheQuery<Person> query = Person.find("name ILIKE :namePattern AND status = :status", sort, params);
290
long totalCount = query.count();
291
List<Person> results = query.page(pageObj).list();
292
293
return new PagedResult<>(results, page, size, totalCount);
294
}
295
296
@Transactional
297
public void bulkUpdatePersonStatus(PersonStatus oldStatus, PersonStatus newStatus) {
298
// Use Panache utility for bulk update
299
int updated = Panache.executeUpdate(
300
"UPDATE Person SET status = :newStatus, updatedDate = :now WHERE status = :oldStatus",
301
Parameters.with("newStatus", newStatus)
302
.and("oldStatus", oldStatus)
303
.and("now", LocalDateTime.now())
304
);
305
306
// Force flush to ensure immediate update
307
Panache.flush();
308
309
log.info("Updated {} persons from {} to {}", updated, oldStatus, newStatus);
310
}
311
312
@Transactional
313
public void cleanupInactivePersons(int daysInactive) {
314
LocalDateTime cutoffDate = LocalDateTime.now().minusDays(daysInactive);
315
316
try {
317
// Get transaction manager for manual control
318
TransactionManager tm = Panache.getTransactionManager();
319
320
// Execute cleanup
321
int deleted = Panache.executeUpdate(
322
"DELETE FROM Person WHERE status = :status AND lastLoginDate < :cutoff",
323
Parameters.with("status", PersonStatus.Inactive)
324
.and("cutoff", cutoffDate)
325
);
326
327
if (deleted > 1000) {
328
// Too many deletions, rollback for safety
329
Panache.setRollbackOnly();
330
throw new IllegalStateException("Cleanup would delete too many records: " + deleted);
331
}
332
333
log.info("Cleaned up {} inactive persons", deleted);
334
335
} catch (Exception e) {
336
Panache.setRollbackOnly();
337
throw new RuntimeException("Cleanup failed", e);
338
}
339
}
340
341
public List<Person> getTopPersonsByActivity(int limit) {
342
// Complex sorting with null handling
343
Sort activitySort = Sort.by("lastLoginDate", Sort.Direction.Descending, Sort.NullPrecedence.NULLS_LAST)
344
.and("createdDate", Sort.Direction.Descending);
345
346
return Person.find("status = :status", Parameters.with("status", PersonStatus.Active))
347
.page(0, limit)
348
.list();
349
}
350
351
@Transactional
352
public void processPersonsBatch(PersonStatus status, int batchSize) {
353
// Use multiple persistence units if needed
354
EntityManager mainEm = Panache.getEntityManager();
355
EntityManager reportEm = Panache.getEntityManager("reporting");
356
357
Page currentPage = Page.ofSize(batchSize);
358
PanacheQuery<Person> query = Person.find("status", status);
359
360
int processedCount = 0;
361
while (true) {
362
List<Person> batch = query.page(currentPage).list();
363
if (batch.isEmpty()) break;
364
365
// Process batch
366
for (Person person : batch) {
367
processActivePerson(person);
368
processedCount++;
369
}
370
371
// Flush every batch
372
Panache.flush();
373
374
// Move to next page
375
currentPage = currentPage.next();
376
}
377
378
log.info("Processed {} persons with status {}", processedCount, status);
379
}
380
}
381
```
382
383
### Error Handling and Best Practices
384
385
Common patterns for using utilities safely:
386
387
```java
388
public class UtilityBestPractices {
389
390
// Safe parameter building
391
public Parameters buildSafeParameters(Map<String, Object> input) {
392
Parameters params = null;
393
for (Map.Entry<String, Object> entry : input.entrySet()) {
394
if (entry.getValue() != null) { // Skip null values
395
if (params == null) {
396
params = Parameters.with(entry.getKey(), entry.getValue());
397
} else {
398
params = params.and(entry.getKey(), entry.getValue());
399
}
400
}
401
}
402
return params != null ? params : Parameters.with("dummy", "dummy"); // Ensure not null
403
}
404
405
// Safe sort building
406
public Sort buildSafeSort(String sortBy, String direction) {
407
if (sortBy == null || sortBy.trim().isEmpty()) {
408
return Sort.by("id"); // Default sort
409
}
410
411
// Validate sort column to prevent SQL injection
412
if (!isValidColumnName(sortBy)) {
413
throw new IllegalArgumentException("Invalid sort column: " + sortBy);
414
}
415
416
Sort.Direction dir = "desc".equalsIgnoreCase(direction)
417
? Sort.Direction.Descending
418
: Sort.Direction.Ascending;
419
420
return Sort.by(sortBy, dir);
421
}
422
423
// Safe pagination
424
public Page buildSafePage(Integer page, Integer size) {
425
int safePage = (page != null && page >= 0) ? page : 0;
426
int safeSize = (size != null && size > 0 && size <= 1000) ? size : 20; // Max 1000
427
return Page.of(safePage, safeSize);
428
}
429
430
// Transaction-safe operations
431
@Transactional
432
public void safeExecuteUpdate(String query, Parameters params) {
433
try {
434
int updated = Panache.executeUpdate(query, params);
435
if (updated == 0) {
436
log.warn("No rows updated by query: {}", query);
437
} else {
438
log.info("Updated {} rows", updated);
439
}
440
} catch (Exception e) {
441
Panache.setRollbackOnly();
442
log.error("Update failed, transaction marked for rollback", e);
443
throw e;
444
}
445
}
446
447
private boolean isValidColumnName(String columnName) {
448
// Simple validation - extend as needed
449
return columnName.matches("^[a-zA-Z_][a-zA-Z0-9_]*$");
450
}
451
}
452
```