HPUNIX Сайт о ОС и не только!

Шпаргалка по работе с DBIxClass

23 августа 2012 - unix
Шпаргалка по работе с DBIxClass

Отлично обмысленный ORM может значительно упростить жизнь программеру. Но если это так, то откуда берутся клики, что «ORM — это антипаттерн»? Думается, дело в том, что не все ORM идиентично неплохи (ORM для C++ из этой хабрастатьи просто ужасны).

В этой заметке пойдет речь о DBIx::Class — отлично обмысленном и являющимся де-факто стандартным ORM для Perl.

Наши ожидания от ORM

Обычный ORM должен владеть приблизительно такими качествами:

  • Упругость, другими словами мы как и раньше можем писать SELECT, INSERT, UPDATE и DELETE запросы, делать JOIN, GROUP BY, ORDER BY и LIMIT, также использовать транзакции и подзапросы — все как в SQL, просто другими словами;
  • Сопоставимость с разными СУБД, в число которых как минимум должны заходить MySQL, PostgreSQL, Oracle, Microsoft SQL Server, SQLite;
  • Переносимость меж поддерживаемыми СУБД — если приложение нормально работает с SQLite, то должно работать и с PostgreSQL без конфигураций в коде (на практике все таки требуется тестирование сопоставимости);
  • Производительность, сопоставимая с SQL — там, где возможно обойтись одним запросом, не должно употребляться 10 (да, мы не можем использовать запросы типа «INSERT INTO … ON DUPLICATE KEY …», но это стоимость за переносимость, а не внедрение ORM);
  • Более читаемый и лаконичный код, а такие функции, как createUser либо updateUserInfo вообщем должны быть сгенерированы автоматом;
  • Проверка запросов на шаге компиляции (в Perl «компиляция» есть прогон модульных тестов);
  • Схожие запросы генерируются идиентично с точностью до б, что обеспечивает более действенное кэширование со стороны СУБД;
  • Разные оптимизации, к примеру, соединение с СУБД не устанавливается, пока не будет выполнен 1-ый запрос, и ни один запрос не производится до воззвания к результатам его выполнения;
  • Всякие плюшки типа кэширования, профилирования запросов, секционирования данных, поддержки плагинов, способности восстановления соединения после разрыва и тп;

DBIx::Class полностью соответствует приведенному описанию. Да, внедрение ORM связано с возникновением некого оверхеда. Как и в случае с высокоуровневыми языками программирования. Но это не мешает нам писать на Perl, Python либо Java?

Довольно один раз поработать с DBIx::Class, и становится понятно, что оверхед того стоит.

Примеры использования DBIx::Class

Чтоб работать с базой данных, DBIx::Class’у требуется схема базы данных. Имеется ввиду не UML диаграмма, а набор классов, описывающий БД. Проще всего сделать схему при помощи утилиты dbicdump (см DBIx::Class::Schema::Loader):

dbicdump -o dump_directory=./lib My::Namespace::Schema \
'dbi:mysql:blojek:localhost:3306' user qwerty

В коде пишем:

use My::Namespace::Schema;

# ...

my $schema = My::Namespace::Schema->connect(
    $connect_string, $db_user, $db_pass, {
      quote_names => 1,
      mysql_enable_utf8 => 1,
  });

Подробнее о mysql_enable_utf8 и аналогах для других СУБД можно прочесть тут. На практике, чтоб не плодить соединения с БД, употребляется класс-одиночка, а характеристики для соединения с БД читаются из конфига, к примеру, при помощи DBIx::Class::Schema::Config.

Сейчас попробуем написать какой-либо обычной INSERT-запрос:

my $user_rs = $schema->resultset('User')->create({
    login => $login,
    pass => $hash,
  });

Не правда ли, это удобнее, чем писать свою функцию createUser?

Следует инспектировать генерируемые запросы, чтобы испытывать уверенность в их эффективности. Чтоб узреть SQL запрос, сгенерированный DBIx::Class, воспользуемся переменной окружения DBIC_TRACE:

DBIC_TRACE=1 ./adduser.pl

Увидим последующее:

INSERT INTO `users` ( `login`, `pass`) VALUES ( ?, ? ):
  'test', 'd8578edf8458ce06'

Направьте внимание, что таблица именуется «users», но соответственный ей класс именуется «User», в единственном числе.

SELECT-запросы пишутся последующим образом:

my $book_rs = $schema->resultset('Book')->search({
    author_id => $author_id # WHERE aythor_id = ?
    created => { '>', time() - 60*60*24 } # AND created > ?
    status => [ $foo, $bar ] # AND (status = ? OR status = ?)
  });
# запрос не будет выполнен до вызова ->next
while(my $book = $book_rs->next) {
  print $book->title."\n";
  print $book->status."\n";
}

Чтоб выполнить запрос «SELECT * FROM table» довольно сделать вызов способа search() без аргументов. Также этот способ может быть вызван с 2-мя аргументами:

my $book_rs = $schema->resultset('Book')->search({
Шпаргалка по работе с DBIxClass
    author_id => $author_id,
  }, {
    order_by => { -desc => 'created' }, # ORDER BY created DESC
    rows => 10, page => 2, # LIMIT X, Y
    columns => [qw/book_id title/], # SELECT book_id, title FROM ...
  });

1-ый аргумент может быть равен undef:

# SELECT conf_id FROM configs ORDER BY version DESC LIMIT 1
my $last_conf_id = $schema->resultset('Config')->search(undef, {
    order_by => { -desc => 'version' },
    rows => 1,
    columns => [qw/conf_id/],
  })->single->id;

Способ single() класса DBIx::Class::ResultSet похож на next(), но возвращает только первую найденную строчку, которой соответствует класс DBIx::Class::Row. В свою очередь способ id() класса DBIx::Class::Row возвращает значение первичного ключа, в нашем примере — conf_id.

В последующем примере в перечень @list будут помещены указатели на хэши, ключи и значения которых соответсвуют именам и значениям столбцов таблицы:

my $log_rs = $schema->resultset('Log')->search(undef,
      { order_by => { -desc => 'tstamp' }, rows => 100 });
$log_rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
my @list = $log_rs->all();

Способ all() возвращает все отысканные строчки. Способ result_class() — это аксессор к классу, который употребляется для сотворения объектов, соответственных строчкам таблицы. В этом случае заместо объектов ворачиваются обыденные хэши.

Для поиска строчки по первичному либо уникальному ключу заместо search() удобнее использовать find():

my $user = $schema->resultset('User')->find($user_id);
print $user->login."\n";

Принципиально осознавать, когда следует использовать find(), когда single(), а когда search(). К примеру, find({ login => $login, pass => $hash }) будет находить строчку только по уникальному ключу, другими словами login. Задумайтесь, к какому ненужному последствию это может привести.

Заместо find() в этом случае следует использовать single().

UPDATE-запросы пишутся так:

# UPDATRE users SET birthday = ? WHERE user_id = ?
$user->birthday($new_birthday);
$user->update;

либо, к примеру, так:

my $post_rs = $schema->resultset('Post');
# принципиально: в update() - не строчка, а указатель на строчку
$post_rs->search({ uid => $uid })->update({ karma => \'karma - 1' });

Аналогично с DELETE-запросами:

$user->delete;
$post_rs->search({ uid => $uid })->delete;

В DBIx::Class предусмотрены способы find_or_new(), find_or_create(), update_or_new() и update_or_create(), которые время от времени бывают очень комфортны:

# ищем домен в БД и если его вдруг нет, создаем новый
my $domain_id = $schema->resultset('Domain')
  ->find_or_create({ domain => $domain },{ columns => ['domain_id']})
  ->id;

В итоге — пример использования транзакций:

use Try::Tiny;

#...

$schema->storage->txn_begin();
try {
  for my $id (@id_list) {
    # здесь какие-то запросы
  }
  $schema->storage->txn_commit();
} catch {
  my $err = $_;
  $schema->storage->txn_rollback();
  # ...
};

Но по способности лучше использовать txn_do() либо txn_scope_guard().

Приведенное описание DBIx::Class никаким образом не претендуют на полноту. За кадром остались JOIN’ы, подзапросы, профайлинг и почти все другое. Но я надеюсь, что вы поймали базы и при желании можете разобраться в остальном без помощи других.

Во время исследования DBIx::Class я рекомендую держать открытой документацию к классу DBIx::Class::ResultSet. В ней содержатся ответы на большая часть вопросов, которые могут у вас появиться. Также направьте внимание на DBIx::Class::Manual::Features.

Некие кандидатуры DBIx::Class

Посреди вероятных альтернатив DBIx::Class хотелось бы отметить последующие:

  • SQL::Abstract — генератор SQL запросов, переносимых меж разными СУБД. Не поддерживает JOIN’ы. Очень упрощает написание INSERT-запросов. Употребляется в DBIx::Class.
  • SQL::Abstract::More — то же самое, только с поддержкой JOIN’ов.
  • SQL::Maker — очередной генератор SQL запросов.
  • DBIx::Custom — итог скрещивания генератора запросов с DBI.
  • ORLite — согласно описанию, «экстремально легкий ORM, заточенный под SQLite». Оказывается, бывает и такое. Как по мне, достаточно странноватая мысль, ибо теряем по последней мере одно из преимуществ ORM.
  • Class::DBI — настоящий ORM. Издавна не обновлялся. В некой степени на нем основан DBIx::Class.
  • Class::DBI::Lite — ORM, родившийся из недовольства его создателя модулем Class::DBI. Как и DBIx::Class, употребляет SQL::Abstract. Нередко обновляется.

Есть и другие ORM для Perl, к примеру, Fey::ORM и Rose::DB. Вобщем, я полностью доволен DBIx::Class, в связи с чем же не вижу острой необходимости в знакомстве с кандидатурами.

Вопросы читателям

Воспользовались ли вы когда-нибудь ORM и используете ли их в текущее время? Если нет, то планируете ли испытать ORM в дальнейшем? Если да, то какой ORM и для какого языка вы используете/использовали и каковы ваши воспоминания от него?

Много ли общего c DBIx::Class у применяемого/использованного вами ORM? Какие дополнительные аргументы вы могли бы привести за внедрение ORM либо против него?

Дополнение: Особенная благодарность выражается Peter Rabbitson за указания на некорректности и дополнения к заметке.

Похожие статьи

  • Моя шпаргалка по работе с Git

    Чуть раньше я открыл себе Git. И понимаете, я проникся. Другими словами, по-настоящему проникся.

    Сейчас я использую Git не только лишь на работе (где я с ним, фактически, познакомился), да и для сво...

  • Моя шпаргалка по работе в Vim

    Самое главное — побороть боязнь белоснежного листа. Я всегда говорю это для себя, когда необходимо начать работу над кое-чем и не знаешь, как подступиться. Так что я решил не разламывать голову над тем, о чем будет пе...

  • Уголок кэпа Восемь тыщ двести двенадцать о поиске работы, собеседованиях и тп

    Как вы сможете держать в голове, я здесь не так давно оговорился о собственной новейшей работе. А так как новейшей работе обычно предшествует составление резюме и прохождение собеседований, мне вроде бы положено написать заме...

  • Persistent и работа с базами данных в Yesod

    Предлагаю вашему вниманию перевод очередной главы из восхитительной книжки «Developing Web Applications with Haskell and Yesod». Эта глава, как и большая часть других, будет увлекательна даже тем, кто не...

  • Работа с постоянными выражениями в Scala

    Не так давно мне захотелось прикрутить к блогу виджет, содержащий перечень самых просматриваемых заметок. Так как WordPress не собирает подобающую статистику, количество просмотров предполагалось...

Теги:
Рейтинг: +7 Голосов: 26 1710 просмотров
Комментарии (0)

Нет комментариев. Ваш будет первым!

Найти на сайте: параметры поиска

Windows 7

Среда Windows 7 на первых порах кажется весьма непривычной для многих.

Windows 8

Если резюмировать все выступления Microsoft на конференции Build 2013.

Windows XP

Если Windows не может корректно завершить работу, в большинстве случаев это

Windows Vista

Если к вашему компьютеру подключено сразу несколько мониторов, и вы регулярно...