Во втором посте из цикла "#ВопросЭксперту" Михаил Поливаха рассказал, что из себя представляет поддержка SpEL в Spring Data JDBC и как мы с вами можем сделать её ещё лучше.
–––
Периодически получаю вопросы, связанные с работой Spel-a в Sping Data JDBC (на самом деле, все ниже сказанное относится также к R2DBC модулю). Решил написать пост с объяснением, почему имеем то, что имеем, и, что также важно - что можно улучшить.
Чтобы пояснить проблематику, предлагаю посмотреть на запрос ниже и сказать, все ли в нем корректно:
@Modifying
@Query("update :#{#entityName} set captured = false where captured = true and captured_time < :#{#dateMillis.toEpochMilli()}")
Long updateCaptureFlag(Instant dateMillis);
На самом деле ответ следующий - в целом, этот запрос, с точки зрения SPEL-а, написан вполне корректно. Единственный момент - работать он не будет из-за одного нюанса, который заключается в placeholder-е - в
entityName. Начнем с того, что в рамках запроса в репозитории создается EvaluationContext. Если коротко - это контекст, в рамках которого происходит evalutaion (там находятся пары ключ значение, объекты и т.д - потенциально все, на что мы ссылаемся в рамках Spel-а). Однако в нашем случае Sprign Data JDBC/R2DBC создает его лишь со значениями параметров вызываемого query метода. Иными словами, в рамках expression-а мы можем лишь резолвить ссылки на параметры query метода (
updateCaptureFlag например, в нашем случае). Этот момент в целом можно расширить, если работать с EvaluationContextExtension, но конкретно сделать так, чтобы entityName резолвился в имя сущности в текущем контексте запроса не получится.Наверняка опытные пользователи Spring Data JPA знают, что в ней достаточно давно существует специальный placeholder для того, чтобы внутри запроса по SPEL-у запросить имя сущности. Это бывает полезно, если какой-то общий запрос описывается в родительском репозитории, а в каждом наследнике этого репозитория этот placeholder в запросе будет резолвится в соответствующе конкретное имя сущности. То есть технически на самом деле это реализовать вполне реально.
С другой стороны, обратите внимание, как резолвится
#entityName:> The entityName is resolved as follows: If the domain type has set the name property on the @Entity annotation, it is used. Otherwise, the simple class-name of the domain type is used.
Фактически, для Spring Data JDBC/R2DBC имя класса бесполезно. Данные модули не работают с JPA, там нет Persistence Context-а, JPQL и пр. Поэтому изначально при проектировании этого не было заложено, но, как оказалось, потребность в этом есть.
Тогда, если сформулировать корректно, нас должна интересовать возможность иметь placeholder с указанием на актуальное имя таблицы, которое ассоциировано с тем aggregate-ом, которым мы оперируем.
Именно такой тред обсуждения мы недавно завели на GitHub: https://github.com/spring-projects/spring-data-relational/issues/1856
Поэтому, если есть интерес, можете зайти и выразить поддержку этому треду комментарием/лайком.