Spring Boot starter для типового CRUD + List: базовый сервисный слой, строковый DSL фильтров поверх JPA, soft delete, пагинация и MapStruct DTO.
- Требования
- Установка
- Quickstart
- Архитектура
- Entity и аннотации
- Фильтры
- Пагинация и сортировка
- Soft delete
- DTO
- Конфигурация
- Ошибки API
- Ограничения
- Тесты
| Java | 21+ |
| Spring Boot | 4.x |
| Spring Data JPA | да |
| MapStruct | да |
| Web | spring-boot-starter-web у вас - для RestExceptionHandler |
<dependency>
<groupId>io.github.twlnky</groupId>
<artifactId>spring-query-utils-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>MapStruct в приложении:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.6.3</version>
<scope>provided</scope>
</dependency>@Entity
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@FilterFieldAllies(alias = "itemName", operator = SqlOperator.LIKE)
private String name;
@FilterFieldAllies(operator = SqlOperator.GREATER)
private Integer score;
@SoftDeleteFlag
private boolean deleted;
}public interface ItemRepository
extends JpaRepository<Item, Long>, JpaSpecificationExecutor<Item> {
}@Mapper(componentModel = "spring")
public interface ItemDtoMapper extends EntityDtoMapper<Item, ItemDto> {
ItemDto toDto(Item model);
Item toModel(ItemDto dto);
@Override
default Class<Item> modelClass() {
return Item.class;
}
@Override
default Class<ItemDto> dtoClass() {
return ItemDto.class;
}
}@Service
public class ItemService extends AbstractCRUDLService<Item, Long, ItemRepository> {
public ItemService(ItemRepository repository, DtoMapper dtoMapper, EntityManager em) {
super(repository, dtoMapper, em, Item.class);
}
}@GetMapping("/items")
public PageableResult<List<ItemDto>> list(
@RequestParam(required = false) List<String> filter,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String sortField,
@RequestParam(defaultValue = "DESC") String direction
) {
Filter f = new Filter();
if (filter != null) {
f.getFilter().addAll(filter);
}
Sort sort = sortField != null
? new Sort(sortField, Sort.SortDirection.valueOf(direction))
: null;
return itemService.list(f, new Pagination(page, size), sort, ItemDto.class);
}GET /items?filter=itemName:LIKE:test&filter=score:GREATER:10&page=0&size=20&sortField=score&direction=DESCПомечает поле entity как доступное для фильтрации и сортировки.
| Атрибут | Назначение |
|---|---|
alias |
ключ |
value |
JPA-путь, если поле вложенное |
operator |
оператор по умолчанию, если в строке фильтра оператор пустой |
Поле boolean: delete(id) выставляет true, getById / list / update не возвращают удалённые записи.
Формат одного условия:
ключ:ОПЕРАТОР:значение
- разделитель — первые два
:, остаток строки = value (можно использовать:внутри value); - несколько условий в одном
Filter→ AND.
| ОПЕРАТОР | Пример |
|---|---|
EQUALS |
status:EQUALS:ACTIVE |
NOT_EQUALS |
status:NOT_EQUALS:DELETED |
LIKE |
itemName:LIKE:test |
GREATER |
score:GREATER:10 |
LESS |
score:LESS:100 |
IN |
status:IN:ACTIVE,PENDING |
IS_NULL |
comment:IS_NULL: |
Filter filter = new Filter();
filter.
getFilter().
add("itemName:LIKE:alpha");
filter.
getFilter().
add("score:GREATER:10");
itemService.
list(filter, new Pagination(0, 20), null);Pagination pagination = new Pagination(0, 20);
Sort sort = new Sort("score", Sort.SortDirection.DESC);| Правило | Значение |
|---|---|
page |
больше или равно 0 |
size |
1 или spring.query-utils.max-page-size (default 100) |
sort.field |
только @Id и поля с @FilterFieldAllies |
| Метод | Поведение |
|---|---|
delete(id) |
deleted = true, строка в БД остаётся |
getById / list / update |
не видят deleted = true |
Starter не генерирует мапперы за вас — только собирает их в DtoMapper.
- DTO-класс в приложении.
- Интерфейс с
@Mapper(componentModel = "spring"), extendsEntityDtoMapper<M, D>— см. Quickstart, шаг 3. - В сервисе:
list(..., ItemDto.class),getById(id, ItemDto.class)или напрямуюdtoMapper.toDto(entity, ItemDto.class).
# включить / выключить auto-config (default true)
spring.query-utils.enabled=true
# максимальное количество записей после запроса (default 100)
spring.query-utils.max-page-size=100
# RestExceptionHandler (default true) нужен spring-boot-starter-web
spring.query-utils.exception-handler-enabled=trueПри spring-boot-starter-web и включённом handler:
| Исключение | HTTP | Тело |
|---|---|---|
ResourceNotFoundException |
404 | ProblemDetail |
ValidationException |
400 | ProblemDetail |
- только AND между фильтрами
- soft delete - только
boolean - один
EntityDtoMapperна пару entity/DTO DriverUtils/DatabaseTypeне используются в CRUD-потоке
mvn test