Squeryl: основы
Этим и следующим постом (который, надеюсь, будет вот совсем скоро) я хочу познакомить вас с замечательной ORM-библиотекой для scala, которую сейчас использую в одном из рабочих проектов. Не надо пугаться аббревиатуры ORM: возожности маппинга к объектам здесь принципиально реализованы на совсем базовом уровне, а вот симпатичный DSL для запросов стоит того, чтобы на него взглянуть.
Каждый, кто когда-либо пытался использовать “традиционные” ORM типа Hibernate знает, что тот уровень абстракций, которые они предоставляют, хорошо работает только для простых запросов. Как только возникает потребность сделать выборку более или менее сложную либо по условию, либо по выдаваемому набору данных, разработчику приходится начинать сражение с высокоуровневым языком запросов (типа HQL), использование которого несёт в себе сразу ряд проблем. Во-первых, это ещё один язык запросов, похожий на SQL, но им не являющийся. Во-вторых, сразу теряются остатки type safety. В-третьих, при любом изменении схемы нужно проверять и править ручками все строки запросов в коде. Добавьте сюда сложную внутреннюю логику ORM, когда программисту довольно сложно сходу понять, когда и какие именно запросы будут сделаны к базе и частенько страдающую производительность.
Squeryl - это scala-библиотека, призванная решить хотя бы часть этих проблем, и делает она это с широкомасштабным применением средств языка, а именно заточенностью под DSL и строгой типизацией всего что только можно. Таким образом, запросы имеют структуру, похожую на обычный SQL, являясь при этом scala-кодом. Scala-код, в отличие от SQL-запросов, тщательно проверяется компилятором, который сделает вам ататат, как только вы попытаетесь вписать строку в столбец типа Int или хотя бы сделать сравнение столбца типа Date с булевским значением.
Инициализация
Итак, для начала нужно инициализировать драйвер и реализовать фабрику сессий. Не надо пугаться названия, это всего лишь функция, возвращающая экземпляр класса Session:
1 2 3 4 5 6 7 8 9 10 |
|
Это довольно простая реализация, в реальном проекте скорее всего будет уместнее использовать connection pool, типа DBCP или BoneCP.
Транзакции
С транзакциями всё просто: транзакция осуществляется с помощью двух методов transaction
и inTransaction
, которые, принимая на вход функцию, оборачивают её выполнение в транзакцию.
1 2 3 4 5 6 7 8 |
|
Их отличие состоит в том, что transaction
всегда открывает новую транзакцию перед началом выполнения блока и коммитит после завершения, a inTransaction
делает это только в том случае, если не находится внутри другой транзакции.
Схема.
Следующим шагом, как и в других ORM, будет объявление схемы. Есть два режима работы со squeryl: использование примитивных типов (PrimitiveTypeMode) и типов-врапперов (CustomTypeMode), которые могут, например, включать в себя валидацию. Собственно для работы с DSL, необходим импорт членов одного из этих объектов (как это сделано в первой строчке предыдущего листинга).
Классы схемы могут быть любыми скаловскими классами, поля, объявленные в конструкторе будут столбцами. Поля могут быть как mutable, так и immutable. Я предпочитаю для большей ясности использовать второй вариант в сочетании с case-классами и при необходимости просто вызывать метод copy. Однако, стоит заметить что этот способ имеет свои недостатки (например, не будет работать “из коробки” optimistic concurrency control, поскольку поле с версией не будет копироваться при вызове copy).
1 2 3 4 5 6 7 |
|
Трейт KeyedEntity просто говорит Squeryl что id является первичным ключом, позволяя, например, упростить поиск записи по id до вызова единственного метода lookup(id)
над таблицей.
Таблицы и связи между ними описываются в классе, отнаследованным от Schema, в большинстве случаев можно сделать его синглтоном:
1 2 3 4 5 6 7 8 9 |
|
Наличие схемы, как в любом приличном ORM избавляет необходимости писать тонны SQL CREATE ручками. Для создания схемы в базе можно воспользоваться методом create
.
1 2 3 |
|
Аналогично, для импорта схемы в виде SQL, есть метод printDdl
, а для очистки базы - drop
.
Insert
Итак, мы объявили схему и можем приступить к тому, ради чего, собственно, всё и затевалось. Для начала создадим несколько пользователей:
1 2 3 4 5 |
|
Select
Select в squeryl является практически двойником оного в SQL за исключением того, написан на Scala со всеми вытекающими плюшками:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Обратите внимание на тройное ===
(1). В отличие от обычного двойного ==
- это не встроенный в scala оператор, а конструкция DSL Squeryl.
Трейт KeyedEntity
даёт нам возможность использовать упрощённый синтаксис для поиска записи по id:
1
|
|
Update
Операция обновления в squeryl поддерживает два вида синтаксиса: частичное и полное обновление. Полное обновление, как следует из названия, использует в качестве источника обновлённых данных объект целиком. Поскольку в нашем примере классы целиком иммутабельны, мы будем подсовывать в update объекты, созданные через метод copy.
1 2 |
|
Полный update всегда обновляет только одну запись, поэтому ничего не возвращает.
При частичном обновлении используется конструкция DSL, похожая на ту, которую мы использовали в select:
1 2 3 4 5 6 7 8 |
|
Как это принято в JDBC, update возвращает количество обновлённых записей.
Опять обратим внимание (1) на использование DSL-оператора присваивания (:=
) вместо ‘родного’ =
и на, конструкцию .~
, применяющуюся в PrimitiveTypeMode для того, чтобы компилятор не путал оператор +
у скаловского Int с таким же оператором в DSL. Вместо конструкци field.~ + value
можно использовать field plus value
, кому как нравится. Кроме того, мы использовали (2) ещё один оператор сравнения - SQL LIKE.
Delete
Удаление выполняется аналогично: либо по id (для классов, с примешанным KeyedEntity) либо по условию.
1 2 3 4 5 6 7 8 9 |
|
Продолжение следует
В следующем посте я постараюсь рассмотреть “продвинутые” функции squeryl: джойны, связи 1-to-many и many-to-many, составные запросы, итд. И ещё постараюсь не забыть пройтись с критикой по тем местам библиотеки, которые ещё требуют полировки. Критика и отзывы, как обычно приветствуются. До встречи.