Помните, как мы учились писать сайтики при помощи Play Framework ? Вы, конечно же, обратили внимание, что вопрос работы с какой-либо СУБД был оставлен в стороне. Пришло время исправить эту вопиющую несправедливость!

Чего-чего, а ORM в мире Scala хоть отбавляй. Притом, ситуация с этими ORM быстро меняется. В книжках по Play, вышедших всего лишь год или около того назад, рекомендуется использовать либо Anorm , либо Squeryl . При этом Anorm — это и не ORM вовсе, а просто такая обертка для написания запросов на обычном SQL. Вот Squeryl является полноценным ORM’ом. Еще есть какие-то Ebean и SORM . В общем, любой найдет себе ORM по вкусу. Однако на момент написания этих строк, конкретно для Scala и Play Framework, насколько я могу судить, Typesafe рекомендует использовать Slick .

Как я уже отмечал, в мире Scala вещи очень быстро меняются. Когда я изучал Slick, то натыкался на десятки устаревших туториалов, примеры из которых либо не компилировались, либо компилировались, но не работали. Также к моменту, когда вы будете читать эту заметку, на смену Slick может прийти более совершенный ORM. Поэтому заклинаю вас, прежде, чем следовать чему-то, описанному в этой заметке, внимательно изучите сайт Typesafe, а также спросите у знакомых программистов на Scala или на StackOverflow, чем в это время суток модно ходить в базы данных при программировании на Scala + Play.

Итак, чтобы прикрутить Slick к своему проекту, открываем build.sbt и прописываем в libraryDependencies следующие зависимости:

«joda-time» % «joda-time» % «2.4» ,
«org.joda» % «joda-convert» % «1.6» ,
«mysql» % «mysql-connector-java» % «5.1.32» ,
«com.typesafe.slick» % «slick_2.11» % «2.1.0» ,
«com.github.tototoshi» %% «slick-joda-mapper» % «1.2.0» ,
«com.typesafe.play» %% «play-slick» % «0.8.0» ,

Здесь и далее предполагается, что в качестве СУБД вы выбрали либо MySQL, либо MariaDB. Если это не так, соответствующие коннекторы и тп нужно модифицировать очевидным способом. Примите во внимание, что к моменту, когда вы будете читать эту заметку, могут появиться более свежие версии библиотек.

Далее прописываем в application.conf:

slick.default=»models.*»
logger.scala.slick.jdbc.JdbcBackend.statement=DEBUG
db.default.driver=com.mysql.jdbc.Driver
db.default.url=»jdbc:mysql://localhost/dbname?user=aaa&password=bbb»

Убедитесь, что после создания базы данных dbname вы не забыли сказать:

ALTER DATABASE DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;

Рассмотрим, как при помощи Slick определить схему базы данных для простенького форума. Создадим файл app/models/Tables.scala и напишем в нем:

package models

import org. joda . time . DateTime
import play. api . db . slick . Config . driver . simple . _
import com. github . tototoshi . slick . JdbcJodaSupport . _

object T {
val users = TableQuery [ UsersTable ]
val topics = TableQuery [ TopicsTable ]
val comments = TableQuery [ CommentsTable ]
}

case class User ( id : Long = 0 , login : String, email : String,
password : String, salt : String, created : DateTime )
case class Topic ( id : Long = 0 , author : Long, title : String,
text : String, created : DateTime )
case class Comment ( id : Long = 0 , theme : Long, author : Long,
text : String, created : DateTime )

class UsersTable ( tag : Tag ) extends Table [ User ] ( tag, «users» ) {
def id = column [ Long ] ( «id» , O. PrimaryKey , O. AutoInc )
def login = column [ String ] ( «login» , O. NotNull )
def email = column [ String ] ( «email» , O. NotNull )
def password = column [ String ] ( «password» , O. NotNull )
def salt = column [ String ] ( «salt» , O. NotNull )
def created = column [ DateTime ] ( «created» , O. NotNull ,
O. DBType ( «datetime» ) )

def * = ( id, login, email, password, salt, created ) <>
( User. tupled , User. unapply )

def login _ idx = index ( «users_login_idx» , login, unique = true )
def email _ idx = index ( «users_email_idx» , email, unique = true )
}

class TopicsTable ( tag : Tag ) extends Table [ Topic ] ( tag, «topics» ) {
def id = column [ Long ] ( «id» , O. PrimaryKey , O. AutoInc )
def author = column [ Long ] ( «author» , O. NotNull )
def title = column [ String ] ( «title» , O. NotNull )
def text = column [ String ] ( «text» , O. NotNull , O. DBType ( «text» ) )
def created = column [ DateTime ] ( «created» , O. NotNull ,
O. DBType ( «datetime» ) )

def * = ( id, author, title, text, created ) <>
( Topic. tupled , Topic. unapply )

def author _ fk = foreignKey ( «topics_author_fk» , author, T. users ) (
_ . id ,
onUpdate = ForeignKeyAction. Restrict ,
onDelete = ForeignKeyAction. Cascade )
def created _ idx = index ( «topics_created_idx» , created )
}

class CommentsTable ( tag : Tag ) extends Table [ Comment ] ( tag, «comments» ) {
def id = column [ Long ] ( «id» , O. PrimaryKey , O. AutoInc )
def topic = column [ Long ] ( «topic» , O. NotNull )
def author = column [ Long ] ( «author» )
def text = column [ String ] ( «text» , O. NotNull , O. DBType ( «text» ) )
def created = column [ DateTime ] ( «created» , O. NotNull ,
O. DBType ( «datetime» ) )

def * = ( id, topic, author, text, created ) <>
( Comment. tupled , Comment. unapply )

def author _ fk = foreignKey ( «comments_author_fk» , author, T. users ) (
_ . id ,
onUpdate = ForeignKeyAction. Restrict ,
onDelete = ForeignKeyAction. Cascade )
def theme _ fk = foreignKey ( «comments_topic_fk» , topic, T. topics ) (
_ . id ,
onUpdate = ForeignKeyAction. Restrict ,
onDelete = ForeignKeyAction. Cascade )

def created _ idx = index ( «comments_topic_created_idx» , ( topic,
created ) )
}

Как видите, Slick предоставляет мощный DSL для определения таблиц и связей между ними. Все, что можно сделать с помощью CREATE TABLE ... можно сделать и здесь. При первом запуске приложения Slick автоматически создаст все таблицы, никакой предварительной инициализации не требуется.

Чтобы делать запросы к БД, в контроллере прописываем следующие импорты:

import models. _
import play. api . db . slick . _
import org. joda . time . _
import scala. slick . driver . MySQLDriver . simple . _
import com. github . tototoshi . slick . MySQLJodaSupport . _

Вместо какого-нибудь:

def page ( page : Long ) = Action {
// …
}

… пишем:

def page ( page : Long ) = DBAction { implicit rs =>
// …
}

Теперь рассмотрим некоторые примеры запросов.

Получение числа топиков в БД:

// SELECT count(*) …
val res : Int = T. topics . length . run

Получение списка топиков на заданной странице с информацией об их авторах:

// SELECT … LEFT JOIN … ORDER BY … LIMIT …
val topics = T. topics . sortBy ( _ . created . desc )
. drop ( ( page — 1 ) * topicsPerPage )
. take ( topicsPerPage )
val topicsSeq = ( topics leftJoin T. users on ( _ . author === _ . id ) ) . map {
case ( t, u ) => ( t. id , t. title , u. login , t. created )
} . list

Получение информации об одном топике по topicId:

// SELECT … LEFT JOIN … WHERE …
( T. topics . filter ( _ . id === topicId ) leftJoin T. users on
( _ . author === _ . id ) ) . map {
case ( t, u ) => ( t. id , t. title , u. login , t. text , t. created )
} . run . headOption

Выборка комментариев к соответствующему топику:

// SELECT … LEFT JOIN … WHERE …
val commTmp = T. comments . filter ( _ . topic === topicId )
. sortBy ( _ . created . asc )
val comments = ( commTmp leftJoin T. users on ( _ . author === _ . id ) ) . map {
case ( c, u ) => ( c. id , c. text , u. login , c. created )
} . list

Наконец, для сильных духом, пример с UNION и GROUP BY:

def sitemapXml ( ) = DBAction { implicit rs =>
val indexUpdateTime = T. topics . map ( _ . created )
. max . run . getOrElse ( new DateTime ( ) )

val topics1 = T. topics . map { r => ( r. id , r. created ) }
val topics2 = T. comments . map { r => ( r. topic , r. created ) }
val allTopics = topics1 union topics2
val groupedTopics = allTopics. groupBy ( t => t. _ 1 ) . map {
case ( topicId, group ) => ( topicId, group. map ( _ . _ 2 ) . max )
}
// где-то при группировке Slick превращает created из DateTime
// в Option[DateTime], поэтому здесь сделан map
val sortedPagesInfo = groupedTopics. sortBy ( _ . _ 2. desc ) . take ( 50 )
. list . map {
case ( topicId, optCreated ) => ( topicId, optCreated. get )
}

Ok ( views. xml . sitemap ( indexUpdateTime, sortedPagesInfo ) )
}

Хорошо, мы убедились в выразительной мощи Slick. Но что же на счет эффективности? С одной стороны, генерируемые запросы получаются не очень похожими на те, что были бы написаны руками. Все запросы будут выводиться в консоль во время работы приложения. Для этого ранее мы и прописали в конфиг строчку:

logger.scala.slick.jdbc.JdbcBackend.statement=DEBUG

Запросы, несмотря на то, что выглядят они страшно, выполняются вполне себе быстро. Желательно, конечно,чтобы приложение писало, скажем, в Graphite , запросы для генерации каких страниц сколько времени выполняются. Ну или хотя бы чтобы в СУБД был включен лог медленных запросов.

Больше примеров и информации по Slick вы можете найти в его официальной документации .

ORM вообще, и Slick в частности, дают программисту кучу удобняшек, в том числе строгую статическую типизацию , по сути — кучу готовых функций типа createUser, updateComment и так далее, а также автоматические миграции схемы БД . Платить за это приходится тем, что некоторые запросы могут выполняться менее эффективно, чем если бы мы написали их руками. Но специально для этого случая в Slick и предусмотрена возможность писать запросы вручную . Как по мне, даже при самом худшем раскладе, как минимум, мы абсолютно ничего не теряем. А значит нет никаких причин не использовать ORM в своих проектах.

Вы согласны?

admin

Share
Published by
admin

Recent Posts

Консоль удаленного рабочего стола(rdp console)

Клиент удаленного рабочего стола (rdp) предоставляет нам возможность войти на сервер терминалов через консоль. Что…

2 месяца ago

Настройка сети в VMware Workstation

В VMware Workstation есть несколько способов настройки сети гостевой машины: 1) Bridged networking 2) Network…

2 месяца ago

Логи брандмауэра Windows

Встроенный брандмауэр Windows может не только остановить нежелательный трафик на вашем пороге, но и может…

2 месяца ago

Правильный способ отключения IPv6

Вопреки распространенному мнению, отключить IPv6 в Windows Vista и Server 2008 это не просто снять…

2 месяца ago

Ключи реестра Windows, отвечающие за параметры экранной заставки

Параметры экранной заставки для текущего пользователя можно править из системного реестра, для чего: Запустите редактор…

2 месяца ago

Как управлять журналами событий из командной строки

В этой статье расскажу про возможность просмотра журналов событий из командной строки. Эти возможности можно…

2 месяца ago