or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

active-record.mdindex.mdquery-pagination.mdrepository-pattern.mdutilities.md

query-pagination.mddocs/

0

# Query Building and Pagination

1

2

Advanced query building capabilities including pagination, sorting, filtering, and result processing through the PanacheQuery interface. The PanacheQuery interface provides a fluent API for building complex queries with pagination, sorting, locking, and result processing.

3

4

## Capabilities

5

6

### PanacheQuery Interface

7

8

The core query interface that enables fluent query building and result processing.

9

10

```java { .api }

11

public interface PanacheQuery<Entity> {

12

// Projection

13

public <T> PanacheQuery<T> project(Class<T> type);

14

15

// Pagination

16

public <T extends Entity> PanacheQuery<T> page(Page page);

17

public <T extends Entity> PanacheQuery<T> page(int pageIndex, int pageSize);

18

public <T extends Entity> PanacheQuery<T> nextPage();

19

public <T extends Entity> PanacheQuery<T> previousPage();

20

public <T extends Entity> PanacheQuery<T> firstPage();

21

public <T extends Entity> PanacheQuery<T> lastPage();

22

23

// Range-based queries

24

public <T extends Entity> PanacheQuery<T> range(int startIndex, int lastIndex);

25

26

// Query configuration

27

public <T extends Entity> PanacheQuery<T> withLock(LockModeType lockModeType);

28

public <T extends Entity> PanacheQuery<T> withHint(String hintName, Object value);

29

30

// Hibernate filters

31

public <T extends Entity> PanacheQuery<T> filter(String filterName);

32

public <T extends Entity> PanacheQuery<T> filter(String filterName, Parameters parameters);

33

public <T extends Entity> PanacheQuery<T> filter(String filterName, Map<String, Object> parameters);

34

35

// Result retrieval

36

public long count();

37

public <T extends Entity> List<T> list();

38

public <T extends Entity> Stream<T> stream();

39

public <T extends Entity> T firstResult();

40

public <T extends Entity> Optional<T> firstResultOptional();

41

public <T extends Entity> T singleResult();

42

public <T extends Entity> Optional<T> singleResultOptional();

43

44

// Pagination info

45

public boolean hasNextPage();

46

public boolean hasPreviousPage();

47

public int pageCount();

48

public Page page();

49

}

50

```

51

52

### Query Projection

53

54

Transform query results into different types using projection classes.

55

56

```java { .api }

57

public <T> PanacheQuery<T> project(Class<T> type);

58

```

59

60

**Usage:**

61

```java

62

// Projection class

63

public class PersonSummary {

64

public String name;

65

public String email;

66

67

public PersonSummary(String name, String email) {

68

this.name = name;

69

this.email = email;

70

}

71

}

72

73

// Using projection with explicit select

74

List<PersonSummary> summaries = Person.find("select name, email from Person where status = ?1", PersonStatus.Active)

75

.project(PersonSummary.class)

76

.list();

77

78

// Automatic projection based on constructor

79

List<PersonSummary> autoProjected = Person.find("status = ?1", PersonStatus.Active)

80

.project(PersonSummary.class)

81

.list();

82

```

83

84

### Page-Based Pagination

85

86

Navigate through result sets using page-based pagination.

87

88

```java { .api }

89

// Set pagination

90

public <T extends Entity> PanacheQuery<T> page(Page page);

91

public <T extends Entity> PanacheQuery<T> page(int pageIndex, int pageSize);

92

93

// Navigate pages

94

public <T extends Entity> PanacheQuery<T> nextPage();

95

public <T extends Entity> PanacheQuery<T> previousPage();

96

public <T extends Entity> PanacheQuery<T> firstPage();

97

public <T extends Entity> PanacheQuery<T> lastPage();

98

99

// Page information

100

public boolean hasNextPage();

101

public boolean hasPreviousPage();

102

public int pageCount();

103

public Page page();

104

```

105

106

**Usage:**

107

```java

108

// Basic pagination

109

PanacheQuery<Person> query = Person.find("status", PersonStatus.Active);

110

List<Person> page1 = query.page(0, 20).list(); // First 20 results

111

List<Person> page2 = query.page(1, 20).list(); // Next 20 results

112

113

// Using Page object

114

Page page = Page.of(0, 10);

115

List<Person> results = Person.findAll().page(page).list();

116

117

// Navigation

118

PanacheQuery<Person> paginatedQuery = Person.findAll().page(0, 10);

119

if (paginatedQuery.hasNextPage()) {

120

List<Person> nextPage = paginatedQuery.nextPage().list();

121

}

122

123

// Page information

124

PanacheQuery<Person> activeQuery = Person.find("status", PersonStatus.Active).page(0, 10);

125

long totalCount = activeQuery.count(); // Total matching entities

126

int totalPages = activeQuery.pageCount(); // Total pages

127

Page currentPage = activeQuery.page(); // Current page info

128

129

// Jump to specific pages

130

List<Person> firstPage = activeQuery.firstPage().list();

131

List<Person> lastPage = activeQuery.lastPage().list();

132

```

133

134

### Range-Based Queries

135

136

Use fixed ranges instead of page-based pagination for specific result windows.

137

138

```java { .api }

139

public <T extends Entity> PanacheQuery<T> range(int startIndex, int lastIndex);

140

```

141

142

**Usage:**

143

```java

144

// Get specific range of results

145

List<Person> results = Person.findAll(Sort.by("name"))

146

.range(10, 19) // Get results 10-19 (10 results starting from index 10)

147

.list();

148

149

// Get first 5 results

150

List<Person> top5 = Person.find("status", PersonStatus.Active)

151

.range(0, 4)

152

.list();

153

154

// Note: Range-based queries disable further pagination operations

155

PanacheQuery<Person> rangeQuery = Person.findAll().range(0, 9);

156

// rangeQuery.nextPage(); // This would throw UnsupportedOperationException

157

```

158

159

### Query Locking

160

161

Apply database locking strategies to queries.

162

163

```java { .api }

164

public <T extends Entity> PanacheQuery<T> withLock(LockModeType lockModeType);

165

```

166

167

**Usage:**

168

```java

169

import jakarta.persistence.LockModeType;

170

171

// Pessimistic locking

172

@Transactional

173

public Person updatePersonWithLock(Long id) {

174

Person person = Person.find("id", id)

175

.withLock(LockModeType.PESSIMISTIC_WRITE)

176

.firstResult();

177

178

if (person != null) {

179

person.status = PersonStatus.Updated;

180

// Entity is locked until transaction completes

181

}

182

return person;

183

}

184

185

// Optimistic locking

186

List<Person> lockedPersons = Person.find("status", PersonStatus.Active)

187

.withLock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)

188

.list();

189

```

190

191

### Query Hints

192

193

Set JPA query hints for performance tuning.

194

195

```java { .api }

196

public <T extends Entity> PanacheQuery<T> withHint(String hintName, Object value);

197

```

198

199

**Usage:**

200

```java

201

// Fetch size hint

202

List<Person> persons = Person.findAll()

203

.withHint("org.hibernate.fetchSize", 50)

204

.withHint("org.hibernate.readOnly", true)

205

.list();

206

207

// Cache hints

208

List<Person> cachedPersons = Person.find("status", PersonStatus.Active)

209

.withHint("org.hibernate.cacheable", true)

210

.withHint("org.hibernate.cacheRegion", "person-region")

211

.list();

212

```

213

214

### Hibernate Filters

215

216

Enable and configure Hibernate filters for dynamic query constraints.

217

218

```java { .api }

219

public <T extends Entity> PanacheQuery<T> filter(String filterName);

220

public <T extends Entity> PanacheQuery<T> filter(String filterName, Parameters parameters);

221

public <T extends Entity> PanacheQuery<T> filter(String filterName, Map<String, Object> parameters);

222

```

223

224

**Usage:**

225

```java

226

// Filter definition on entity

227

@Entity

228

@FilterDef(name = "statusFilter", parameters = @ParamDef(name = "status", type = "string"))

229

@Filter(name = "statusFilter", condition = "status = :status")

230

public class Person extends PanacheEntity {

231

public String name;

232

public PersonStatus status;

233

}

234

235

// Using filters in queries

236

List<Person> activePersons = Person.findAll()

237

.filter("statusFilter", Parameters.with("status", "ACTIVE"))

238

.list();

239

240

// Multiple filters

241

List<Person> filteredPersons = Person.findAll()

242

.filter("statusFilter", Parameters.with("status", "ACTIVE"))

243

.filter("cityFilter", Map.of("city", "NYC"))

244

.list();

245

```

246

247

### Result Retrieval Methods

248

249

Various methods for obtaining query results in different formats.

250

251

```java { .api }

252

// Count results

253

public long count();

254

255

// Get as List

256

public <T extends Entity> List<T> list();

257

258

// Get as Stream (requires transaction)

259

public <T extends Entity> Stream<T> stream();

260

261

// Get single results

262

public <T extends Entity> T firstResult();

263

public <T extends Entity> Optional<T> firstResultOptional();

264

public <T extends Entity> T singleResult();

265

public <T extends Entity> Optional<T> singleResultOptional();

266

```

267

268

**Usage:**

269

```java

270

PanacheQuery<Person> query = Person.find("status", PersonStatus.Active);

271

272

// Count without loading entities

273

long totalActive = query.count();

274

275

// Get as list

276

List<Person> activePersons = query.list();

277

278

// Get as stream (requires transaction)

279

@Transactional

280

public void processActivePersons() {

281

Person.find("status", PersonStatus.Active)

282

.stream()

283

.forEach(this::processActivePerson);

284

}

285

286

// Single result methods

287

Person firstPerson = Person.find("status", PersonStatus.Active)

288

.page(0, 1)

289

.firstResult(); // Returns null if no results

290

291

Optional<Person> maybePerson = Person.find("email", "john@example.com")

292

.firstResultOptional(); // Returns Optional

293

294

// Single result (expects exactly one)

295

try {

296

Person uniquePerson = Person.find("email", "unique@example.com")

297

.singleResult(); // Throws if 0 or >1 results

298

} catch (NoResultException | NonUniqueResultException e) {

299

// Handle error

300

}

301

302

// Safe single result

303

Optional<Person> singlePerson = Person.find("id", 1L)

304

.singleResultOptional(); // Returns Optional, throws if >1 result

305

```

306

307

### Complex Query Building Examples

308

309

Combining multiple query features for sophisticated data retrieval.

310

311

```java

312

// Complex paginated query with sorting and filtering

313

public class PersonService {

314

315

public PagedResult<PersonSummary> searchPersons(

316

String namePattern,

317

PersonStatus status,

318

String city,

319

int page,

320

int size) {

321

322

PanacheQuery<Person> query = Person.find(

323

"name ILIKE :name AND status = :status AND city = :city",

324

Parameters.with("name", "%" + namePattern + "%")

325

.and("status", status)

326

.and("city", city)

327

);

328

329

// Get total count for pagination info

330

long totalCount = query.count();

331

332

// Get paginated results with projection

333

List<PersonSummary> results = query

334

.page(page, size)

335

.project(PersonSummary.class)

336

.list();

337

338

return new PagedResult<>(results, page, size, totalCount);

339

}

340

341

@Transactional

342

public void bulkProcessPersons(PersonStatus status, int batchSize) {

343

PanacheQuery<Person> query = Person.find("status", status);

344

345

int totalPages = query.page(0, batchSize).pageCount();

346

347

for (int page = 0; page < totalPages; page++) {

348

List<Person> batch = query.page(page, batchSize).list();

349

processBatch(batch);

350

}

351

}

352

353

@Transactional

354

public Optional<Person> findAndLockPerson(String email) {

355

return Person.find("email", email)

356

.withLock(LockModeType.PESSIMISTIC_WRITE)

357

.withHint("jakarta.persistence.lock.timeout", 5000L)

358

.firstResultOptional();

359

}

360

}

361

362

// Supporting classes

363

public class PagedResult<T> {

364

public final List<T> content;

365

public final int page;

366

public final int size;

367

public final long totalElements;

368

public final int totalPages;

369

370

public PagedResult(List<T> content, int page, int size, long totalElements) {

371

this.content = content;

372

this.page = page;

373

this.size = size;

374

this.totalElements = totalElements;

375

this.totalPages = (int) Math.ceil((double) totalElements / size);

376

}

377

}

378

```

379

380

### Performance Considerations

381

382

Tips for optimal query performance with PanacheQuery:

383

384

```java

385

// Use count() before loading large result sets

386

PanacheQuery<Person> query = Person.find("status", PersonStatus.Active);

387

long count = query.count();

388

if (count > 1000) {

389

// Consider pagination or filtering

390

List<Person> page = query.page(0, 100).list();

391

} else {

392

List<Person> all = query.list();

393

}

394

395

// Use streaming for large datasets

396

@Transactional

397

public void exportPersons() {

398

Person.findAll()

399

.withHint("org.hibernate.fetchSize", 100)

400

.stream()

401

.forEach(this::exportPerson);

402

}

403

404

// Prefer list() over firstResult() when you need null safety

405

Optional<Person> person = Person.find("email", email)

406

.firstResultOptional(); // Better than firstResult() for null safety

407

408

// Use projection for read-only queries

409

List<String> names = Person.find("status", PersonStatus.Active)

410

.project(PersonNameProjection.class)

411

.list();

412

```