Мы рады представить вам новую рубрику, в которой эксперты сообщества Spring АйО будут отвечать на актуальные и интересные вопросы. Для дебютного выпуска мы пригласили Михаила Поливаху, контрибьютора в Spring Data JDBC, который любезно согласился принять участие. Большое спасибо ему за это! Если вам понравится этот формат, поддержите нас лайками и репостами.
Итак, ниже ответ Михаила на заявленный в заголовке вопрос.
–––
Друзья, по поводу аналога Criteria API в Spring Data JDBC и механизма динамического построения запросов в целом.
TL;DR: Аналог, какой-никакой, имеется, он даже работает😅. Но он менее функциональный, чем тот же Spring Data Specification API.
Long answer:
Если говорить про динамическое построение запроса в целом, то мы можем разбить это на несколько частей:
1. Динамическое построение условий.
Например,
LIKE
, BETWEEN
, IS NOT NULL
и любые другие keyword-ы, которые предназначены для фильтрации набора данных, но которые никак не влияют на структуру запроса в целом. Вот это в Spring Data JDBC имеется. Основные классы, которыми вы будете оперировать в таком случае, - Criteria
и Query
. Использовать это API достаточно просто, единственное - вам придется использовать напрямую JdbcAggregateTemplate
. На данный момент распознавание методов с Query
как параметром метода в Repository/CrudRepository
не поддерживается (речь не про default методы, а именно про абстрактные методы в интерфейсе, которые воспринимаются как PartTreeJdbcQuery
запросы), и скорее всего не будет поддерживаться, так как мало кому это пока было нужно (то есть никакого аналога интерфейсу JpaSpecificationExecutor
в Spring Data JDBC нет). Вот пример использования:
Сущность:
@Data
@Table("users")
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class User {
@Id
@EqualsAndHashCode.Include
private Long id;
private String status;
private String name;
private OffsetDateTime createdAt;
}
И пример запроса:
@Autowired
private JdbcAggregateTemplate repository;
@Test
void testCriteriaApi() {
Criteria criteria = Criteria
.where("status").is("READY").ignoreCase(true)
.and("created_at").between(OffsetDateTime.now().minusDays(2), OffsetDateTime.now().plusDays(2))
.and("name").like("%J%");
Query query = Query.query(criteria);
Page<User> allByQuery = repository.findAll(query, User.class, Pageable.unpaged());
Assertions.assertThat(allByQuery.getTotalElements()).isEqualTo(3);
}
Надеюсь, здесь все понятно. Код довольно легко читаемый, вот какой SQL мы сгенерируем:
SELECT
"users"."id" AS "id",
"users"."name" AS "name",
"users"."status" AS "status",
"users"."created_at" AS "created_at"
FROM
"users"
WHERE
UPPER("users"."status") = UPPER(?)
AND "users".created_at BETWEEN ?
AND ?
AND "users"."name" LIKE ?
2. Динамическое построение структуры запроса
Теперь, есть другая часть той же Criteria API - это уже динамическое построение самой структуры запроса. Частично с этим помогает
CriteriaQuery
из Criteria API. Это все возможные группировки, формирование селект листа, формирование подзапросов и т.п. Вот этого в Spring Data JDBC нет и в ближайшее время не планируется. Это огромный пласт работы, который предстоит сделать, если, конечно, это кому-то нужно. Сейчас там конь не валялся по этому вопросу.