Rose::DB Perl ORM Быстрый страт
<ul><li>Создаём класс с коннектом к базе </li></ul><ul><li>package My::DB; </li></ul><ul><li>use warnings; </li></ul><ul><...
<ul><li>При этом, учитываем, что в  Rose :: DB  существует 2 параметра подключения к базе:  </li></ul><ul><li>type  и   do...
<ul><li>Выливаем классы таблиц из базы </li></ul><ul><li>#!/usr/bin/perl </li></ul><ul><li>use strict; </li></ul><ul><li>u...
<ul><li>Допустим у вас есть класс  My::DB::MlPerson  для получения объекта строки,  </li></ul><ul><li>с  person _ id  дост...
<ul><li>Теперь перейдём к вопросам более сложных запросов в базу </li></ul><ul><li>Когда вы выливали структуру таблицы в к...
<ul><li>Но вот когда нам требуется обработать набор строк, вступает в свою силу My::DB::MlPerson::Manager. </li></ul><ul><...
<ul><li>НО эти методы умеют принимать аргументы для уточнения результатов поиска и возврата соответствующей ссылки на масс...
<ul><li>Далее , </li></ul><ul><li>My::DB::MlPerson::Manager->get_ml_person_count </li></ul><ul><li>возвращает число записе...
<ul><li>Вы всегда можете получить объект  My :: DB </li></ul><ul><li>$p = My::DB::MlPerson->new(...); </li></ul><ul><li>$d...
<ul><li>Пусть у нас будут описаны в  My :: DB  два коннекта для баз с одинаковыми  </li></ul><ul><li>структурами. Одна с  ...
<ul><li>Отредактируем My::DB::MlPerson и добавим в метод  setup  вот такой аргумет: </li></ul><ul><li>foreign_keys => </li...
<ul><li>В общем-то, в данном случае можно было бы и опустить  rel _ type .  </li></ul><ul><li>Да и к тому же, можено вообщ...
<ul><li>Что   сразу   не   понравилось : </li></ul><ul><li>$p = My::DB::MlPerson->new(person_id=>74952020)->load; </li></u...
Upcoming SlideShare
Loading in …5
×

Rose::DB

1,270 views
1,157 views

Published on

quick start

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,270
On SlideShare
0
From Embeds
0
Number of Embeds
8
Actions
Shares
0
Downloads
9
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Rose::DB

  1. 1. Rose::DB Perl ORM Быстрый страт
  2. 2. <ul><li>Создаём класс с коннектом к базе </li></ul><ul><li>package My::DB; </li></ul><ul><li>use warnings; </li></ul><ul><li>use strict; </li></ul><ul><li>use base qw(Rose::DB); </li></ul><ul><li>__PACKAGE__->use_private_registry; </li></ul><ul><li>__PACKAGE__->register_db( </li></ul><ul><li>driver => 'mysql', </li></ul><ul><li>type => 'main', </li></ul><ul><li>database => 'databasename', </li></ul><ul><li>host => '*****.***.***', </li></ul><ul><li>username => 'user', </li></ul><ul><li>password => '*****', </li></ul><ul><li>connect_options => </li></ul><ul><li>{ </li></ul><ul><li>AutoCommit => 1, </li></ul><ul><li> RaiseError => 1, </li></ul><ul><li>} </li></ul><ul><li>); </li></ul><ul><li>1; </li></ul>
  3. 3. <ul><li>При этом, учитываем, что в Rose :: DB существует 2 параметра подключения к базе: </li></ul><ul><li>type и domain. Т.о. вы можете варьировать между девелоперской и продакшн </li></ul><ul><li>базой с одинаковыми структурами данных. Для это можно описать несколько </li></ul><ul><li>вызовов метода register_db . </li></ul><ul><li>В документации приводится даже пример: </li></ul><ul><li>use My::DB; </li></ul><ul><li>if($ENV{'MYCORP_PRODUCTION_SERVER'}) </li></ul><ul><li>{ </li></ul><ul><li>My::DB->default_domain('production'); </li></ul><ul><li>} </li></ul><ul><li>else </li></ul><ul><li>{ </li></ul><ul><li>My::DB->default_domain('development'); </li></ul><ul><li>} </li></ul><ul><li>Аналогично можно указывать и default_type для коннекта. </li></ul><ul><li>Rose::DB::Cache – тут описывается вариант работы под mod _ perl с/без использования Apache :: DBI </li></ul>
  4. 4. <ul><li>Выливаем классы таблиц из базы </li></ul><ul><li>#!/usr/bin/perl </li></ul><ul><li>use strict; </li></ul><ul><li>use warnings; </li></ul><ul><li>use lib '/home/****/'; </li></ul><ul><li>use My::DB; </li></ul><ul><li>use Rose::DB::Object::Loader; </li></ul><ul><li>my $loader = Rose::DB::Object::Loader->new( </li></ul><ul><li>db => My::DB->new('main'), </li></ul><ul><li>class_prefix => 'My::DB', </li></ul><ul><li>with_foreign_keys => 1, </li></ul><ul><li>with_relationships => 1 </li></ul><ul><li>); </li></ul><ul><li>$loader->make_modules(module_dir=>'/home/****/',exclude_tables=>'Tmp'); </li></ul><ul><li>exit; </li></ul><ul><li>В данном случае передаётся доп параметр 'Tmp' , чтобы исключить таблицы /Tmp/ (regexp) из итогового набора таблиц. </li></ul><ul><li>При этом учитываем, что если у вас уже есть классы с описанием таблиц (со своими кастомными добавками), то они просто перезатрутся. </li></ul><ul><li>Так же нужно учитывать, что если у вас есть поля с типом char(х), то это будет отмечено в калссах, и когда вы будете забирать значения из из объекта строки для данной таблицы, значение будет дополнено пробелами до «x» символов. </li></ul>
  5. 5. <ul><li>Допустим у вас есть класс My::DB::MlPerson для получения объекта строки, </li></ul><ul><li>с person _ id достаточно дать: </li></ul><ul><li>$p = My::DB::MlPerson->new(person_id=>74952020)->load </li></ul><ul><li>В конструктор передаётся первичный или уникальный ключ. </li></ul><ul><li>«Прогрузка» данных произойдёт только в момент load . </li></ul><ul><li>Может так случиться, что в базе нет строки с укзанным значением ключа, и тогда вам вернётся warn . Поскольку иметь такие отбивки не интересно, да и не наглядно, рекомендуется использовать конструкцию с использованием параметра speculative: </li></ul><ul><li>unless($p->load(speculative => 1)) { </li></ul><ul><li>warn &quot;not found&quot;; </li></ul><ul><li>} </li></ul><ul><li>Получим и изменим значение поля password для нашей персоны: </li></ul><ul><li>$p->password; </li></ul><ul><li>$p->password('new_pass'); </li></ul><ul><li>При установке значения в поле, проверяется его валидность на соответствие в описании класса. </li></ul><ul><li>Сохраним изменённый объект строки в базе ( update ): </li></ul><ul><li>$p->save; </li></ul><ul><li>Создадим новую запись в базе: </li></ul><ul><li>$p = My::DB::MlPerson->new(person_id=>74952020,password=>'pass'); </li></ul><ul><li>$p->save ; </li></ul><ul><li>В данном контексте (создание записи), в конструктор совсем необязательно передавать авто-инкрементируемый ключ. </li></ul><ul><li>Удаление записей абсолютно бесхитростно: </li></ul><ul><li>$p = My::DB::MlPerson->new(person_id=>74952020); </li></ul><ul><li>$p->delete; </li></ul><ul><li>Как видно, load в данном случае не требуется. </li></ul>
  6. 6. <ul><li>Теперь перейдём к вопросам более сложных запросов в базу </li></ul><ul><li>Когда вы выливали структуру таблицы в классы, то для каждой таблицы </li></ul><ul><li>были созданы 2 модуля (на примере MlPerson): </li></ul><ul><li>My::DB::MlPerson </li></ul><ul><li>My::DB::MlPerson::Manager </li></ul><ul><li>Идеология состоит в том, что если вам требуется создать некий дополнительный метод для таблицы, который бы возвращал обработанное значение имеющихся полей, то это удобно делать в My::DB::MlPerson. </li></ul><ul><li>Примером является случай, когда у вас в поле хранится сериализованная структура данных. Скажем, сериализованная через Storable. Пример для такого поля stor_field: </li></ul><ul><li>package My::DB::MlPerson; </li></ul><ul><li>… . </li></ul><ul><li>use Storable qw(thaw freeze); </li></ul><ul><li>$Storable::interwork_56_64bit = 1; </li></ul><ul><li>sub alias_to_stor_field{ </li></ul><ul><li>my ($row_obj,$new_val) = @_; </li></ul><ul><li>if ($new_val and ref $new_val) { </li></ul><ul><li># ставим новое значение </li></ul><ul><li>$row_obj->stor_field(freeze($new_val)); </li></ul><ul><li>return 1; </li></ul><ul><li>} elsif ($row_obj->stor_field) { </li></ul><ul><li># десериализуем имеющееся </li></ul><ul><li>return thaw($row_obj->stor_field); </li></ul><ul><li>} ; </li></ul><ul><li>return undef; </li></ul><ul><li>} </li></ul>
  7. 7. <ul><li>Но вот когда нам требуется обработать набор строк, вступает в свою силу My::DB::MlPerson::Manager. </li></ul><ul><li>В самом модуле вы увидите: </li></ul><ul><li>__PACKAGE__->make_manager_methods(' НазваниеТаблицыВБазе '); </li></ul><ul><li>Этот метод делает доступными следующие методы для класса My::DB::MlPerson::Manager: </li></ul><ul><li>get_НазваниеТаб л ицыВБазе </li></ul><ul><li>get_НазваниеТаблицыВБазе_iterator </li></ul><ul><li>get_НазваниеТаблицыВБазе_count </li></ul><ul><li>delete_НазваниеТаблицыВБазе </li></ul><ul><li>update_НазваниеТаблицыВБазе </li></ul><ul><li>Итак , для нашей таблицы ml_person , это будут: </li></ul><ul><li>My::DB::MlPerson::Manager->get_ml_person </li></ul><ul><li>возвращает ссылку на массив всех строк в таблице (что удобно в очень редких случаях, когда обращаешься по индексу, а не разыменовываешь массив. И, всё равно, не удобно.) </li></ul><ul><li>My::DB::MlPerson::Manager->get_ml_person_iterator </li></ul><ul><li>возвращает итератор для обхода всей таблицы. </li></ul><ul><li>my $i = My::DB::MlPerson::Manager->get_ml_person_iterator; </li></ul><ul><li>while( my $p = $i->next ) { </li></ul><ul><li>print $p->name; </li></ul><ul><li>$i-> finish if(...); </li></ul><ul><li>} </li></ul><ul><li>Ахтунг на лицо. И всё было бы совсем плохо, НО … </li></ul>
  8. 8. <ul><li>НО эти методы умеют принимать аргументы для уточнения результатов поиска и возврата соответствующей ссылки на массив или итератор: </li></ul><ul><li>my $i = My::DB::MlPerson::Manager->get_ml_person_iterator( </li></ul><ul><li>query => [ </li></ul><ul><li>name => {like => '%Hat'}, </li></ul><ul><li>person_id => {ge => 7}, </li></ul><ul><li>or => [ </li></ul><ul><li>age => 15, </li></ul><ul><li>age => {lt => 10}, </li></ul><ul><li>], </li></ul><ul><li>], </li></ul><ul><li>sort_by => 'name', </li></ul><ul><li>limit => 10, </li></ul><ul><li>offset => 50 </li></ul><ul><li>); </li></ul><ul><li>while( my $p = $i->next ) { </li></ul><ul><li>print $p->name; </li></ul><ul><li>$i-> finish if(...); </li></ul><ul><li>} </li></ul><ul><li>Эти аргументы эквивалентны квере: </li></ul><ul><li>SELECT person_id, name, age,…,password FROM ml_person WHERE </li></ul><ul><li>name LIKE '%Hat' AND </li></ul><ul><li>person_id >= 7 AND </li></ul><ul><li>(age = 15 OR age < 10.00) </li></ul><ul><li>ORDER BY name </li></ul><ul><li>LIMIT 10 OFFSET 50 </li></ul>
  9. 9. <ul><li>Далее , </li></ul><ul><li>My::DB::MlPerson::Manager->get_ml_person_count </li></ul><ul><li>возвращает число записей в таблице. Так же умеет принимать аргументы, чтобы конкретизировать where в запросе на подсчёт. </li></ul><ul><li>My::DB::MlPerson::Manager->delete_ml_person </li></ul><ul><li>удаляет записи в соответствии с переданными аргументами. </li></ul><ul><li>My::DB::MlPerson::Manager->update_ml_person </li></ul><ul><li>обновляет записи в соответствии с переданными аргументами. </li></ul><ul><li>Здесь стоит отметить, как передаётся аргумент set в кверю: </li></ul><ul><li>My::DB::MlPerson::Manager->update_ml_person( </li></ul><ul><li>set => </li></ul><ul><li>{ </li></ul><ul><li>age => 25, </li></ul><ul><li>}, </li></ul><ul><li>where => </li></ul><ul><li>[ </li></ul><ul><li>age => 24, </li></ul><ul><li>person_id => { gt => 100 }, </li></ul><ul><li>] </li></ul><ul><li>) ; </li></ul><ul><li>В любом из классов-описаний таблиц, вы можете выбрать стиль подключения, если перегрузите метод: </li></ul><ul><li>Собственное подключение к базе: sub init _ db { My :: DB -> new } </li></ul><ul><li>Использовать уже созданное или новое: sub init _ db { My :: DB -> new _ or _ cached } </li></ul>
  10. 10. <ul><li>Вы всегда можете получить объект My :: DB </li></ul><ul><li>$p = My::DB::MlPerson->new(...); </li></ul><ul><li>$db = $p->db; </li></ul><ul><li>И, организовать транзакционный механизм (если автокоммит отключен): </li></ul><ul><li>$p = My::DB::MlPerson->new(...); </li></ul><ul><li>$db = $p->db; </li></ul><ul><li>$db->begin_work; # Начало транзакции </li></ul><ul><ul><li># Используем наш $db для каждого создаваемого объекта-строки </li></ul></ul><ul><ul><li>$p1 = My::DB::MlPerson->new(name => 'Bike', db => $db); </li></ul></ul><ul><ul><li>$p1->save; </li></ul></ul><ul><ul><li>$p2 = My::DB::MlPerson ->new(name => 'Sled', db => $db); </li></ul></ul><ul><ul><li>$p2->save; </li></ul></ul><ul><ul><li>$p3 = My::DB::MlPerson->new(name => 'Kite', db => $db); </li></ul></ul><ul><ul><li>$p3->save; </li></ul></ul><ul><li>if(...) # Применяем изменения или откатываемся </li></ul><ul><li>{ </li></ul><ul><li>$db->commit; </li></ul><ul><li>} </li></ul><ul><li>else </li></ul><ul><li>{ </li></ul><ul><li>$db->rollback; </li></ul><ul><li>} ; </li></ul><ul><li>А теперь вернёмся к My :: DB и возможности регистрировать несколько коннектов к базам, играя параметрами type и domain . </li></ul>
  11. 11. <ul><li>Пусть у нас будут описаны в My :: DB два коннекта для баз с одинаковыми </li></ul><ul><li>структурами. Одна с domain =>’ production ’ и вторая с domain =>’ archive ’. </li></ul><ul><li>Назначение баз понятно. </li></ul><ul><li>$production_db = My::DB->new('production'); </li></ul><ul><li>$archive_db = My::DB->new('archive'); </li></ul><ul><li># Загрузим с продакшн базы данные по конкретной персоне </li></ul><ul><li>$p = My::DB::MlPerson->new(person_id => 'John', db => $production_db); </li></ul><ul><li>$p->load; </li></ul><ul><li># Скопируем данные по персоне в архив </li></ul><ul><li>$p->db($archive_db); </li></ul><ul><li>$p->save(insert => 1); # аргумент insert позволит либо записать строку, либо обновить значения </li></ul><ul><li># Удалим обозначенную персону из продакшн базы </li></ul><ul><li>$p->db($production_db); </li></ul><ul><li>$p->delete; </li></ul><ul><li>Рассмотрим зависимости. </li></ul><ul><li>Если у вас в базе они не были проставлены, то при генерации классов-таблиц, вы их то же не увидите. </li></ul><ul><li>Прелесть в том, что можно прописать их ручками. </li></ul>
  12. 12. <ul><li>Отредактируем My::DB::MlPerson и добавим в метод setup вот такой аргумет: </li></ul><ul><li>foreign_keys => </li></ul><ul><li>[ </li></ul><ul><ul><li>crm => </li></ul></ul><ul><ul><li>{ </li></ul></ul><ul><ul><li>class => 'My::DB::CrmBridge', </li></ul></ul><ul><ul><li>key_columns => { person_id => 'f_person_id' }, </li></ul></ul><ul><ul><li> relationship_type => 'one to one', </li></ul></ul><ul><ul><li>}, </li></ul></ul><ul><li>] </li></ul><ul><li>Итак, что у нас здесь: </li></ul><ul><li>создана связь между классами (не таблицами): My::DB::MlPerson и My::DB::CrmBridge. </li></ul><ul><li>следующим образом: </li></ul><ul><li>ml _ person . person _ id является внешним ключом для crm _ bridge . f _ person _ id . Соответствие определено как один-к-одному. Обращение к отноению идёт по методу с именем crm . </li></ul><ul><li>relationship _ type может так же быть записан как rel _ type (синоним) </li></ul><ul><li>И, теперь мы можем сделать вот такую штуку: </li></ul><ul><li>$ p = My :: DB :: MlPerson->new(person_id=>74952020)->load; </li></ul><ul><li>$p->crm->any_field </li></ul><ul><li>где, any_field - это любое поле из таблицы crm _ bridge . </li></ul>
  13. 13. <ul><li>В общем-то, в данном случае можно было бы и опустить rel _ type . </li></ul><ul><li>Да и к тому же, можено вообще вынести этот аргумент в виде вызова </li></ul><ul><li>метода (но очь важно, чтобы это было определено до вызова setup ): </li></ul><ul><li>package My::DB::MlPerson; </li></ul><ul><li>__PACKAGE__->meta->foreign_keys( </li></ul><ul><li>'crm' => { </li></ul><ul><li>class => 'My::DB::CrmBridge', </li></ul><ul><li>key_columns => { person_id => 'f_person_id' } </li></ul><ul><li>} </li></ul><ul><li>); </li></ul><ul><li>__PACKAGE__->meta->setup( …………) </li></ul><ul><li>А вот, интересный случай когда персона входит в несколько групп (в том же классе My::DB::MlPerson до setup ): </li></ul><ul><li>__PACKAGE__->meta->relationships </li></ul><ul><li>( </li></ul><ul><li>group => </li></ul><ul><li>{ </li></ul><ul><li>type => 'one to many', </li></ul><ul><li>class => 'My::DB::MlGroup', </li></ul><ul><li>column_map => { person_id => 'f_person_id' }, </li></ul><ul><li>}, </li></ul><ul><li>); </li></ul><ul><li>Здесь обозначено отношение один-ко-многим (одна персона из ml _ persons может иметь несколько записей в ml _ group ). Как уже понятно, можно обозначит это отношение и в виде аргумента в методе setup , если вам так удобнее. </li></ul>
  14. 14. <ul><li>Что сразу не понравилось : </li></ul><ul><li>$p = My::DB::MlPerson->new(person_id=>74952020)->load; </li></ul><ul><li>$bor = $p->group; </li></ul><ul><li>ref $bor eq 'ARRAY'; </li></ul><ul><li>другими словами – при обращении к отношению, вы получаете ссылку на массив или массив (в зависимости от контекста). На лицо явная избыточность. По документации не нашёл, как получить возможность обратиться к отношению с возможностью уточнения запроса (типа выбрать только конкретные группы для персоны) без выгребания всех записей из ml _ group . И уж тем более, в продакшн всегда интереснее получить итератор, а тут #@ я! </li></ul><ul><li>И, более того, по дефолту (а это очевидно настраивается), повторное обращение: </li></ul><ul><li>$p->group; </li></ul><ul><li>уже не стало лезть в базу (а ведь со стороны кто-то мог поменять значение в строках таблицы), а просто отдало значение из Кеша (!). </li></ul><ul><li>В общем, в данном вопросе, имхо, рулит DBIx :: Class . </li></ul><ul><li>Что можно назвать удобным, так это удаление групп, в которых состоит персона: </li></ul><ul><li>$p = My::DB::MlPerson->new(person_id=>74952020)->load; </li></ul><ul><li>$p-> group([ ]); </li></ul><ul><li>$p->save; </li></ul><ul><li>А вот пример тотально удаления всех связанных строк: </li></ul><ul><li>$p->delete(cascade => 1); </li></ul><ul><li>Для углубления вопроса по тому как составлять квери, рекомендую посмотреть Rose::DB::Object::Manager, где к сожалению не нашёл примера по вызову хранимок. </li></ul><ul><li>Тему сисек считаю не раскрытой, но для быстрого старта – вполне достаточно. </li></ul>

×