Squeryl: Двигаемся дальше
Итак, продолжаем наше знакомство со Sueryl (начало тут). Для начала, вернёмся к селектам и рассмотрим одну из интереснейших фич Squeryl.
Составные селекты
Итак, select
в любой его форме возвращает нам объект класса Query
(кроме lookup
, возвращающего Option
). Этот класс играет сразу две роли. Во-первых, из него можно просто получить результат выборки, используя его в качестве обычной коллекции (он расширяет трейт Iterable
). А во-вторых (и это самое интересное) - его можно использовать для построения более сложных запросов к базе. Например, подсунуть его в from
вместо таблицы:
1 2 3 4 5 6 7 8 9 |
|
В этом примере мы использовали запрос rated
для построения второго запроса. (Кстати, в первом посте я не упомянул о маленьком но полезном кусочке синтаксического сахара: упрощённом синтаксисе для select
. Пример его использования можно увидеть в первом запросе.)
Можно и использовать результат одного селекта внутри блока where
второго:
1 2 3 4 5 6 7 |
|
Ну и, в конце концов можно добавить к Query
разные полезные модификаторы. Для постраничной выборки:
1
|
|
для выборки уникальных строк:
1
|
|
или для выборки элементов для обновления (директива FOR UPDATE в SQL):
1 2 |
|
Всё это, при желании, даёт возможность писать код максимально соответствующий принципу DRY (“don’t repeat yourself”, если вдруг кто не знает).
Агрегация
Агрегация - это, наверное, единственная часть DSL, где Squeryl отходит от SQL: вместо использования select
, агрегирующие функции описываются в конструкии compute
.
1 2 3 4 5 6 7 8 9 |
|
Результатом такого кода становится объект вида Query[ GroupWithMeasures[Int, Int] ]
, который можно использовать явно, обращаясь к полям key
и measures
, либо приведя его к коллекции типа Map
, вызовом метода toMap
.
Join
С джойнами тоже всё довольно просто. Обычный INNER JOIN так же как и в SQL можно делать неявно, просто сделав select
из двух таблиц и добавив условие в where
:
1 2 3 4 5 |
|
Можно использовать OUTER JOIN и всё, к чему вы привыкли в SQL:
1 2 3 4 5 |
|
В случае использования внешнего джойна, для юзера не всегда находится соответствующая аватарка, поэтому a
в этом примере имеет тип Option.
Relations
Зачастую, одна из самых сложных для понимания и использования вещей в обычных ORM - отношения one-to-many и many-to-many между таблицами. Именно там кроется большая часть граблей, на которые наступают неопытные разработчики.
Подобно scala collections, имеющим две ипостаси: immutable и mutable, отношения в Squeryl делятся на stateless, являющиеся, по-сути, заранее подготовленными запросами (Query
) и stateful хранящими все связанные записи в памяти в виде коллекции (Iterable
).
Для использования любого из этих вариантов, мы должны описать связь между таблицами в нашей схеме.
1 2 3 4 5 6 7 8 9 10 11 |
|
В этом посте я возьму для примера отношения one-to-many. DSL для описания many-to-many не сильно от него отличается и хорошо описан в документации.
1 2 3 4 5 6 7 8 9 |
|
Теперь мы можем использовать наш релейшен так же как и обычный select
.
1 2 3 |
|
Кроме этого, stateless relations имеют несколько полезных методов для манипулирования дочерними элементами:
assign
- привязывает дочерний элемент родительскому, выставляя значение foreign key (имеет смысл только если foreign key - изменяемое поле)associate
- делаетassign
+ сохраняет эту связь в базеdeleteAll
- удаляет все дочерние элементы из базы
Объявление stateful relation не сильно сложнее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
По сути, stateful relations являются простыми врапперами вокруг stateless, добавляющими кеширование данных в памяти, что избавляет Squeryl от необходимость делать запрос к базе при каждом обращении к коллекции.
Недостатки
Трудно сказать, является это недостатком Squeryl или моим, однако не все механизмы работы этого DSL мне до конца ясны. В частности, я так и не понял, с применением какой магии обычные поля классов модели конвертируются в метаданные столбцов в базе. Ведь это делается не только при создании таблицы, но и во время компиляции (или уже в рантайме) замыканий внутри запросов (например, where
).
Один раз, когда мне надо было вынести один из часто используемых паттернов для апдейтов в отдельный метод, магия DSL отказалась работать и мне пришлось расковырять библиотеку и извлечь на свет страннейший артефакт чёрной магии под названием (осторожно): createEqualityExpressionWithLastAccessedFieldReferenceAndConstant
. Вам страшно? Мне тоже. Хорошо что такой случай был пока один, и у вас есть все шансы не напороться даже на него (уж больно он специфический).
А вот с казалось бы элементарными, но не всегда работающими implicit conversions, являющимися неотъемлемой частью любого скаловского DSL, дело обстоит хуже. Приведу один только пример. В этом кусочке условия в where
я пытаюсь использовать ещё одну замечательную фичу - динамический запрос (то есть запрос, части которого включаются по условию, обычно при заполнении Option
, подробнее тут):
1 2 |
|
Очевидно, здесь DSL вступает в конфликт с синтаксическим сахаром для булевских условий (поскольку для строковых полей всё работает). Перепробовав несколько вариантов, я пришёл к вот такому вот монструозному решению:
1 2 3 4 |
|
Обратите внимание на изящный двойной not
, который пришлось использовать в качестве подсказки компилятору. Судя по всему, я оказался не единственным, кто столкнулся с этой проблемой: на StackOveflow мне сообщили что в следующей версии этот сахар будет выключен и посоветовали более изящное решение с применением уже знакомого нам оператора ~
:
1 2 |
|
Заключение
Несмотря на некоторые недостатки, умолчать о которых было бы просто нечестно, Squeryl - замечательная библиотека, позволяющая решить кучу типичных проблем при использовании реляционных БД, не увеличивая сложность и размер кода. Лично я буду использовать её и дальше, что и вам советую делать.
Эти два поста, посвящённые squeryl были в некотором роде заготовкой для выступления на нашем Scala-евенте (хотя это ещё вопрос, что вышло полуфабрикатом, а что полноценным туториалом). Видео моего (и не только) выступления можно найти в небольшом отчёте с него, а слайды у меня на дропбоксе (кавайные белочки инсайде). Удачи и до встреч.