preboot-query
Moduł rozszerzający Spring Data JDBC o dynamiczne filtrowanie, sortowanie, paginację i projekcje. Generuje SQL w runtime na podstawie kryteriów — bez pisania custom queries. Optymalizowany pod PostgreSQL.
Zależność Maven
<dependency>
<groupId>io.preboot</groupId>
<artifactId>preboot-query</artifactId>
</dependency>
Wersje zarządzane przez preboot-bom — nie podawaj <version>.
Wymaga: preboot-core, preboot-exporters-api (transitive), spring-boot-starter-data-jdbc, postgresql.
Opcjonalnie: spring-boot-starter-web (dla kontrolerów REST), springdoc-openapi (dla OpenAPI docs).
Szybki start
1. Zdefiniuj encję
@Table("orders")
@Data
public class Order {
@Id
private Long id;
private String orderNumber;
private BigDecimal amount;
private String status;
private LocalDateTime createdAt;
@MappedCollection(idColumn = "order_id")
private Set<OrderItem> orderItems = new HashSet<>();
}
2. Stwórz interfejs repozytorium
public interface OrderRepository extends FilterableRepository<Order, Long> {}
3. Zaimplementuj repozytorium
@Repository
class OrderRepositoryImpl extends FilterableFragmentImpl<Order, Long> {
public OrderRepositoryImpl(FilterableFragmentContext context) {
super(context, Order.class);
}
}
4. Filtruj
SearchParams params = SearchParams.criteria(
FilterCriteria.eq("status", "COMPLETED"),
FilterCriteria.gt("amount", new BigDecimal("100"))
).build();
Page<Order> orders = orderRepository.findAll(params);
5. (Opcjonalnie) Wystaw REST API
@RestController
@RequestMapping("/api/orders")
public class OrderController extends CrudFilterableController<Order, Long> {
public OrderController(OrderRepository repository) {
super(repository);
}
}
Gotowe endpointy: GET /{id}, POST /search, POST /find, POST /count, POST /, PUT /{id}, PATCH /{id}, DELETE /{id}.
Główne koncepty
Architektura
FilterableRepository<T, ID>
├── extends CrudRepository<T, ID> — standardowe CRUD
└── extends FilterableFragment<T> — dynamiczne filtrowanie
├── findAll(SearchParams) → Page<T>
├── findAllAsStream(SearchParams) → Stream<T>
├── findOne(SearchParams) → Optional<T>
├── count(SearchParams) → long
├── findAllProjectedBy(params, type) → Page<P>
└── findOneProjectedBy(params, type) → Optional<P>
FilterableUuidRepository<T extends HasUuid, ID>
├── extends FilterableRepository<T, ID>
└── extends UuidRepository<T> — findByUuid, existsByUuid, deleteByUuid
Kluczowe klasy
| Klasa |
Rola |
FilterableRepository<T, ID> |
Interfejs repozytorium — CrudRepository + filtrowanie |
FilterableUuidRepository<T, ID> |
Jak wyżej + operacje UUID |
FilterableFragmentImpl<T, ID> |
Implementacja bazowa — rozszerzasz ją w swoim repo |
FilterableUuidFragmentImpl<T, ID> |
Jak wyżej + UUID (findByUuid, deleteByUuid) |
FilterableFragmentContext |
@Service — agreguje wszystkie zależności (JdbcTemplate, SqlBuilder, etc.) |
SearchParams |
Parametry wyszukiwania — filtry, paginacja, sortowanie |
FilterCriteria |
Kryterium filtrowania — operatory eq, neq, like, gt, lt, between, in, etc. |
SortOrder |
Sortowanie — SortOrder.asc("field"), SortOrder.desc("field") |
@AggregateReference |
Adnotacja — relacja do innego agregatu (JOIN w zapytaniach) |
HasUuid |
Interfejs — encja z UUID (getUuid/setUuid) |
FilterableController<T, ID> |
REST controller — read-only z filtrowaniem |
CrudFilterableController<T, ID> |
REST controller — pełne CRUD + filtrowanie |
UuidFilterableController<T, ID> |
REST controller — read-only z UUID |
CrudUuidFilterableController<T, ID> |
REST controller — pełne CRUD z UUID |
SearchRequest |
Record — JSON body dla POST /search |
ExportRequest |
Record — JSON body dla POST /export/{format} |
Hierarchia kontrolerów
FilterableController<T, ID> — GET /{id}, POST /search, /find, /count, /export
└── CrudFilterableController<T, ID> — + POST /, PUT /{id}, PATCH /{id}, DELETE /{id}
UuidFilterableController<T, ID> — GET /{uuid}, POST /search, /find, /count, /export
└── CrudUuidFilterableController<T, ID> — + POST /, PUT /{uuid}, PATCH /{uuid}, DELETE /{uuid}
Operatory filtrowania
| Operator |
Metoda |
SQL |
Opis |
eq |
FilterCriteria.eq(field, value) |
= :val |
Equals (case-sensitive) |
neq |
FilterCriteria.neq(field, value) |
!= :val |
Not equals |
eqic |
FilterCriteria.eqic(field, value) |
LOWER(col) = LOWER(:val) |
Equals (case-insensitive) |
like |
FilterCriteria.like(field, value) |
ILIKE :val% |
Pattern matching (case-insensitive) |
gt |
FilterCriteria.gt(field, value) |
> :val |
Greater than |
lt |
FilterCriteria.lt(field, value) |
< :val |
Less than |
gte |
FilterCriteria.gte(field, value) |
>= :val |
Greater than or equal |
lte |
FilterCriteria.lte(field, value) |
<= :val |
Less than or equal |
between |
FilterCriteria.between(field, from, to) |
BETWEEN :from AND :to |
Between (inclusive) |
in |
FilterCriteria.in(field, values...) |
IN (:vals) |
Value in set |
notin |
FilterCriteria.notIn(field, values...) |
NOT IN (:vals) |
Value not in set |
ao |
FilterCriteria.ao(field, values...) |
&& ARRAY[:vals] |
Array overlap (PostgreSQL) |
isnull |
FilterCriteria.isNull(field) |
IS NULL |
Is null |
isnotnull |
FilterCriteria.isNotNull(field) |
IS NOT NULL |
Is not null |
Logiczne AND/OR
// OR
FilterCriteria.or(List.of(
FilterCriteria.eq("status", "COMPLETED"),
FilterCriteria.eq("status", "PENDING")
));
// AND (explicit)
FilterCriteria.and(List.of(
FilterCriteria.eq("status", "COMPLETED"),
FilterCriteria.gt("amount", 200)
));
// Domyślnie wiele kryteriów w SearchParams.criteria() = AND
SearchParams.criteria(
FilterCriteria.eq("status", "COMPLETED"), // AND
FilterCriteria.gt("amount", 200) // AND
).build();
Zależności od innych modułów PreBoot
- preboot-core (wymagane) —
JsonMapper, utilities
- preboot-exporters-api (wymagane) —
DataExporter interfejs dla eksportu danych
- preboot-securedata (opcjonalnie) — rozszerza o multi-tenant security
Typowe przepływy
Filtrowanie + paginacja + sortowanie
SearchParams params = SearchParams.builder()
.page(0)
.size(20)
.sort(List.of(SortOrder.desc("amount"), SortOrder.asc("status")))
.filters(List.of(
FilterCriteria.eq("status", "COMPLETED"),
FilterCriteria.gt("amount", new BigDecimal("100"))
))
.build();
Page<Order> result = orderRepository.findAll(params);
Encja z UUID
@Table("orders")
public class Order implements HasUuid {
@Id private Long id;
private UUID uuid;
// ... pola
@Override public UUID getUuid() { return uuid; }
@Override public void setUuid(UUID uuid) { this.uuid = uuid; }
}
public interface OrderRepository extends FilterableUuidRepository<Order, Long> {}
@Repository
class OrderRepositoryImpl extends FilterableUuidFragmentImpl<Order, Long> {
public OrderRepositoryImpl(FilterableFragmentContext context) {
super(context, Order.class);
}
}
// Użycie:
Optional<Order> order = orderRepository.findByUuid(uuid);
orderRepository.deleteByUuid(uuid);
Projekcje z @Value (SpEL)
public interface OrderSummary {
String getOrderNumber();
BigDecimal getAmount();
@Value("#{target.amount > 150 ? 'High Value' : 'Standard'}")
String getValueCategory();
}
Page<OrderSummary> summaries = orderRepository.findAllProjectedBy(
SearchParams.empty(), OrderSummary.class
);
REST API — JSON request
POST /api/orders/search
{
"page": 0,
"size": 20,
"sort": [
{"field": "amount", "direction": "DESC"}
],
"filters": [
{"field": "status", "operator": "eq", "value": "COMPLETED"},
{"field": "amount", "operator": "gt", "value": 100}
]
}
Nested filtering (relacje)
SearchParams params = SearchParams.criteria(
FilterCriteria.eq("orderItems.productCode", "PROD-A"),
FilterCriteria.gt("orderItems.quantity", 3)
).build();
Page<Order> orders = orderRepository.findAll(params);
Database views
@Table("v_order_summary")
@Data
public class OrderSummaryView {
@Id private Long id;
@Column("order_number") private String orderNumber;
private BigDecimal amount;
private String status;
@Column("item_count") private Long itemCount;
}
public interface OrderSummaryViewRepository extends FilterableRepository<OrderSummaryView, Long> {}
@Repository
class OrderSummaryViewRepositoryImpl extends FilterableFragmentImpl<OrderSummaryView, Long> {
public OrderSummaryViewRepositoryImpl(FilterableFragmentContext context) {
super(context, OrderSummaryView.class);
}
}
Pułapki i częste błędy
Brak implementacji repozytorium — sam interfejs extends FilterableRepository nie wystarczy. Musisz stworzyć *Impl rozszerzający FilterableFragmentImpl. Nazwa klasy MUSI kończyć się na Impl i odpowiadać nazwie interfejsu.
@Immutable na encji widoku — NIE używaj @Immutable z Spring Data JDBC na encjach widoków. W Spring Data JDBC 4.0.0 powoduje to null we wszystkich polach.
Nazewnictwo kolumn w widokach — jeśli kolumna widoku ma alias inny niż snake_case pola Java, użyj @Column("alias"). Spring Data JDBC konwertuje camelCase → snake_case automatycznie.
like operator dodaje % automatycznie — FilterCriteria.like("name", "ORD") generuje ILIKE 'ORD%'. Nie dodawaj % sam.
UUID entity bez HasUuid — jeśli chcesz findByUuid(), encja MUSI implementować HasUuid, repo FilterableUuidRepository, impl FilterableUuidFragmentImpl.
Async EventPublisher z kontrolerami eksportu — asynchroniczny eksport wymaga QueryControllersPort — bez niego isAsyncExportSupported() zwraca false.
Max page size — domyślnie 100. Przekroczenie zwraca 400 Bad Request. Override getMaxPageSize() w kontrolerze jeśli potrzebujesz więcej.
Sort precedence — gdy podasz zarówno sortField/sortDirection jak i sort, sort wygrywa. sortField/sortDirection jest deprecated.
Brak FilterableFragmentContext beana — ten bean jest @Service i wymaga NamedParameterJdbcTemplate, SqlBuilder, RelationalMappingContext, JdbcConverter, ConversionService, JdbcAggregateTemplate, PropertyResolver w kontekście. Wszystkie są auto-konfigurowane przez Spring Boot.
between z null — FilterCriteria.between(field, null, to) rzuci IllegalArgumentException. Oba argumenty muszą być non-null.
Kiedy sięgnąć do references/
- api-reference.md — pełne sygnatury metod, parametry, wyjątki, hierarchia kontrolerów, hooki CRUD
- examples.md — kompletne przykłady: filtrowanie, OR/AND, projekcje, @AggregateReference, kontrolery REST, JSON requests, widoki bazodanowe, eksport, testowanie
1---2name: preboot-query3description: Skill do używania biblioteki preboot-query. Użyj tego skilla zawsze gdy użytkownik chce implementować dynamiczne filtrowanie, sortowanie, paginację, repozytorium z wyszukiwaniem, REST API z filtrowaniem, projekcje, eksport danych, lub pracuje z Spring Data JDBC i potrzebuje dynamicznych zapytań. Obejmuje: FilterableRepository, FilterableUuidRepository, FilterCriteria, SearchParams, SearchRequest, FilterableController, CrudFilterableController, UuidFilterableController, CrudUuidFilterableController, AggregateReference, projekcje SpEL, SortOrder, eksport danych. Triggeruje się na: dynamic filtering, search, pagination, sorting, filterable repository, criteria query, Spring Data JDBC query, REST API search, CRUD controller, export CSV XLSX, projection, aggregate reference, UUID repository, database view, ILIKE, between, IN operator, nested filtering, OR AND conditions, complex query, SearchParams, FilterCriteria, findAll with filters, page size, unpaged.4---5
6# preboot-query
7
8Moduł rozszerzający Spring Data JDBC o dynamiczne filtrowanie, sortowanie, paginację i projekcje. Generuje SQL w runtime na podstawie kryteriów — bez pisania custom queries. Optymalizowany pod PostgreSQL.
9
10## Zależność Maven
11
12```xml
13<dependency>
14 <groupId>io.preboot</groupId>
15 <artifactId>preboot-query</artifactId>
16</dependency>
17```
18
19Wersje zarządzane przez `preboot-bom` — nie podawaj `<version>`.
20
21Wymaga: `preboot-core`, `preboot-exporters-api` (transitive), `spring-boot-starter-data-jdbc`, `postgresql`.
22Opcjonalnie: `spring-boot-starter-web` (dla kontrolerów REST), `springdoc-openapi` (dla OpenAPI docs).
23
24## Szybki start
25
26### 1. Zdefiniuj encję
27
28```java
29@Table("orders")
30@Data
31public class Order {
32 @Id
33 private Long id;
34 private String orderNumber;
35 private BigDecimal amount;
36 private String status;
37 private LocalDateTime createdAt;
38
39 @MappedCollection(idColumn = "order_id")
40 private Set<OrderItem> orderItems = new HashSet<>();
41}
42```
43
44### 2. Stwórz interfejs repozytorium
45
46```java
47public interface OrderRepository extends FilterableRepository<Order, Long> {}
48```
49
50### 3. Zaimplementuj repozytorium
51
52```java
53@Repository
54class OrderRepositoryImpl extends FilterableFragmentImpl<Order, Long> {
55 public OrderRepositoryImpl(FilterableFragmentContext context) {
56 super(context, Order.class);
57 }
58}
59```
60
61### 4. Filtruj
62
63```java
64SearchParams params = SearchParams.criteria(
65 FilterCriteria.eq("status", "COMPLETED"),
66 FilterCriteria.gt("amount", new BigDecimal("100"))
67).build();
68
69Page<Order> orders = orderRepository.findAll(params);
70```
71
72### 5. (Opcjonalnie) Wystaw REST API
73
74```java
75@RestController
76@RequestMapping("/api/orders")
77public class OrderController extends CrudFilterableController<Order, Long> {
78 public OrderController(OrderRepository repository) {
79 super(repository);
80 }
81}
82```
83
84Gotowe endpointy: `GET /{id}`, `POST /search`, `POST /find`, `POST /count`, `POST /`, `PUT /{id}`, `PATCH /{id}`, `DELETE /{id}`.
85
86## Główne koncepty
87
88### Architektura
89
90```
91FilterableRepository<T, ID>
92├── extends CrudRepository<T, ID> — standardowe CRUD
93└── extends FilterableFragment<T> — dynamiczne filtrowanie
94 ├── findAll(SearchParams) → Page<T>
95 ├── findAllAsStream(SearchParams) → Stream<T>
96 ├── findOne(SearchParams) → Optional<T>
97 ├── count(SearchParams) → long
98 ├── findAllProjectedBy(params, type) → Page<P>
99 └── findOneProjectedBy(params, type) → Optional<P>
100
101FilterableUuidRepository<T extends HasUuid, ID>
102├── extends FilterableRepository<T, ID>
103└── extends UuidRepository<T> — findByUuid, existsByUuid, deleteByUuid
104```
105
106### Kluczowe klasy
107
108| Klasa | Rola |
109|-------|------|
110| `FilterableRepository<T, ID>` | Interfejs repozytorium — CrudRepository + filtrowanie |
111| `FilterableUuidRepository<T, ID>` | Jak wyżej + operacje UUID |
112| `FilterableFragmentImpl<T, ID>` | Implementacja bazowa — rozszerzasz ją w swoim repo |
113| `FilterableUuidFragmentImpl<T, ID>` | Jak wyżej + UUID (findByUuid, deleteByUuid) |
114| `FilterableFragmentContext` | `@Service` — agreguje wszystkie zależności (JdbcTemplate, SqlBuilder, etc.) |
115| `SearchParams` | Parametry wyszukiwania — filtry, paginacja, sortowanie |
116| `FilterCriteria` | Kryterium filtrowania — operatory eq, neq, like, gt, lt, between, in, etc. |
117| `SortOrder` | Sortowanie — `SortOrder.asc("field")`, `SortOrder.desc("field")` |
118| `@AggregateReference` | Adnotacja — relacja do innego agregatu (JOIN w zapytaniach) |
119| `HasUuid` | Interfejs — encja z UUID (getUuid/setUuid) |
120| `FilterableController<T, ID>` | REST controller — read-only z filtrowaniem |
121| `CrudFilterableController<T, ID>` | REST controller — pełne CRUD + filtrowanie |
122| `UuidFilterableController<T, ID>` | REST controller — read-only z UUID |
123| `CrudUuidFilterableController<T, ID>` | REST controller — pełne CRUD z UUID |
124| `SearchRequest` | Record — JSON body dla POST /search |
125| `ExportRequest` | Record — JSON body dla POST /export/{format} |
126
127### Hierarchia kontrolerów
128
129```
130FilterableController<T, ID> — GET /{id}, POST /search, /find, /count, /export
131└── CrudFilterableController<T, ID> — + POST /, PUT /{id}, PATCH /{id}, DELETE /{id}
132
133UuidFilterableController<T, ID> — GET /{uuid}, POST /search, /find, /count, /export
134└── CrudUuidFilterableController<T, ID> — + POST /, PUT /{uuid}, PATCH /{uuid}, DELETE /{uuid}
135```
136
137### Operatory filtrowania
138
139| Operator | Metoda | SQL | Opis |
140|----------|--------|-----|------|
141| `eq` | `FilterCriteria.eq(field, value)` | `= :val` | Equals (case-sensitive) |
142| `neq` | `FilterCriteria.neq(field, value)` | `!= :val` | Not equals |
143| `eqic` | `FilterCriteria.eqic(field, value)` | `LOWER(col) = LOWER(:val)` | Equals (case-insensitive) |
144| `like` | `FilterCriteria.like(field, value)` | `ILIKE :val%` | Pattern matching (case-insensitive) |
145| `gt` | `FilterCriteria.gt(field, value)` | `> :val` | Greater than |
146| `lt` | `FilterCriteria.lt(field, value)` | `< :val` | Less than |
147| `gte` | `FilterCriteria.gte(field, value)` | `>= :val` | Greater than or equal |
148| `lte` | `FilterCriteria.lte(field, value)` | `<= :val` | Less than or equal |
149| `between` | `FilterCriteria.between(field, from, to)` | `BETWEEN :from AND :to` | Between (inclusive) |
150| `in` | `FilterCriteria.in(field, values...)` | `IN (:vals)` | Value in set |
151| `notin` | `FilterCriteria.notIn(field, values...)` | `NOT IN (:vals)` | Value not in set |
152| `ao` | `FilterCriteria.ao(field, values...)` | `&& ARRAY[:vals]` | Array overlap (PostgreSQL) |
153| `isnull` | `FilterCriteria.isNull(field)` | `IS NULL` | Is null |
154| `isnotnull` | `FilterCriteria.isNotNull(field)` | `IS NOT NULL` | Is not null |
155
156### Logiczne AND/OR
157
158```java
159// OR
160FilterCriteria.or(List.of(
161 FilterCriteria.eq("status", "COMPLETED"),
162 FilterCriteria.eq("status", "PENDING")
163));
164
165// AND (explicit)
166FilterCriteria.and(List.of(
167 FilterCriteria.eq("status", "COMPLETED"),
168 FilterCriteria.gt("amount", 200)
169));
170
171// Domyślnie wiele kryteriów w SearchParams.criteria() = AND
172SearchParams.criteria(
173 FilterCriteria.eq("status", "COMPLETED"), // AND
174 FilterCriteria.gt("amount", 200) // AND
175).build();
176```
177
178### Zależności od innych modułów PreBoot
179
180- **preboot-core** (wymagane) — `JsonMapper`, utilities
181- **preboot-exporters-api** (wymagane) — `DataExporter` interfejs dla eksportu danych
182- **preboot-securedata** (opcjonalnie) — rozszerza o multi-tenant security
183
184## Typowe przepływy
185
186### Filtrowanie + paginacja + sortowanie
187
188```java
189SearchParams params = SearchParams.builder()
190 .page(0)
191 .size(20)
192 .sort(List.of(SortOrder.desc("amount"), SortOrder.asc("status")))
193 .filters(List.of(
194 FilterCriteria.eq("status", "COMPLETED"),
195 FilterCriteria.gt("amount", new BigDecimal("100"))
196 ))
197 .build();
198
199Page<Order> result = orderRepository.findAll(params);
200```
201
202### Encja z UUID
203
204```java
205@Table("orders")
206public class Order implements HasUuid {
207 @Id private Long id;
208 private UUID uuid;
209 // ... pola
210
211 @Override public UUID getUuid() { return uuid; }
212 @Override public void setUuid(UUID uuid) { this.uuid = uuid; }
213}
214
215public interface OrderRepository extends FilterableUuidRepository<Order, Long> {}
216
217@Repository
218class OrderRepositoryImpl extends FilterableUuidFragmentImpl<Order, Long> {
219 public OrderRepositoryImpl(FilterableFragmentContext context) {
220 super(context, Order.class);
221 }
222}
223
224// Użycie:
225Optional<Order> order = orderRepository.findByUuid(uuid);
226orderRepository.deleteByUuid(uuid);
227```
228
229### Projekcje z @Value (SpEL)
230
231```java
232public interface OrderSummary {
233 String getOrderNumber();
234 BigDecimal getAmount();
235
236 @Value("#{target.amount > 150 ? 'High Value' : 'Standard'}")
237 String getValueCategory();
238}
239
240Page<OrderSummary> summaries = orderRepository.findAllProjectedBy(
241 SearchParams.empty(), OrderSummary.class
242);
243```
244
245### REST API — JSON request
246
247```json
248POST /api/orders/search
249{
250 "page": 0,
251 "size": 20,
252 "sort": [
253 {"field": "amount", "direction": "DESC"}
254 ],
255 "filters": [
256 {"field": "status", "operator": "eq", "value": "COMPLETED"},
257 {"field": "amount", "operator": "gt", "value": 100}
258 ]
259}
260```
261
262### Nested filtering (relacje)
263
264```java
265SearchParams params = SearchParams.criteria(
266 FilterCriteria.eq("orderItems.productCode", "PROD-A"),
267 FilterCriteria.gt("orderItems.quantity", 3)
268).build();
269
270Page<Order> orders = orderRepository.findAll(params);
271```
272
273### Database views
274
275```java
276@Table("v_order_summary")
277@Data
278public class OrderSummaryView {
279 @Id private Long id;
280 @Column("order_number") private String orderNumber;
281 private BigDecimal amount;
282 private String status;
283 @Column("item_count") private Long itemCount;
284}
285
286public interface OrderSummaryViewRepository extends FilterableRepository<OrderSummaryView, Long> {}
287
288@Repository
289class OrderSummaryViewRepositoryImpl extends FilterableFragmentImpl<OrderSummaryView, Long> {
290 public OrderSummaryViewRepositoryImpl(FilterableFragmentContext context) {
291 super(context, OrderSummaryView.class);
292 }
293}
294```
295
296## Pułapki i częste błędy
297
2981. **Brak implementacji repozytorium** — sam interfejs `extends FilterableRepository` nie wystarczy. Musisz stworzyć `*Impl` rozszerzający `FilterableFragmentImpl`. Nazwa klasy MUSI kończyć się na `Impl` i odpowiadać nazwie interfejsu.
299
3002. **`@Immutable` na encji widoku** — NIE używaj `@Immutable` z Spring Data JDBC na encjach widoków. W Spring Data JDBC 4.0.0 powoduje to null we wszystkich polach.
301
3023. **Nazewnictwo kolumn w widokach** — jeśli kolumna widoku ma alias inny niż snake_case pola Java, użyj `@Column("alias")`. Spring Data JDBC konwertuje `camelCase` → `snake_case` automatycznie.
303
3044. **like operator dodaje `%` automatycznie** — `FilterCriteria.like("name", "ORD")` generuje `ILIKE 'ORD%'`. Nie dodawaj `%` sam.
305
3065. **UUID entity bez HasUuid** — jeśli chcesz `findByUuid()`, encja MUSI implementować `HasUuid`, repo `FilterableUuidRepository`, impl `FilterableUuidFragmentImpl`.
307
3086. **Async EventPublisher z kontrolerami eksportu** — asynchroniczny eksport wymaga `QueryControllersPort` — bez niego `isAsyncExportSupported()` zwraca `false`.
309
3107. **Max page size** — domyślnie 100. Przekroczenie zwraca 400 Bad Request. Override `getMaxPageSize()` w kontrolerze jeśli potrzebujesz więcej.
311
3128. **Sort precedence** — gdy podasz zarówno `sortField`/`sortDirection` jak i `sort`, `sort` wygrywa. `sortField`/`sortDirection` jest deprecated.
313
3149. **Brak `FilterableFragmentContext` beana** — ten bean jest `@Service` i wymaga `NamedParameterJdbcTemplate`, `SqlBuilder`, `RelationalMappingContext`, `JdbcConverter`, `ConversionService`, `JdbcAggregateTemplate`, `PropertyResolver` w kontekście. Wszystkie są auto-konfigurowane przez Spring Boot.
315
31610. **between z null** — `FilterCriteria.between(field, null, to)` rzuci `IllegalArgumentException`. Oba argumenty muszą być non-null.
317
318## Kiedy sięgnąć do references/
319
320- **api-reference.md** — pełne sygnatury metod, parametry, wyjątki, hierarchia kontrolerów, hooki CRUD
321- **examples.md** — kompletne przykłady: filtrowanie, OR/AND, projekcje, @AggregateReference, kontrolery REST, JSON requests, widoki bazodanowe, eksport, testowanie