DBI
ORM Abstraction sur un schema simple de correspondance entre un modele objet et un modele relationnel existant.
Ces correspondances sont les plus simples et les plus evidentes a mettre en oeuvre.
La methode table()
definit la table de la base de donnees sur laquelle
va etre effectuee la correspondance.
La methode table_alias()
permet d'utiliser un nom alternatif en particulier
si la table porte un nom qui est un mot reserve du SQL.
La methode sequence()
ou auto_increment()
permet de definir une sequence,
comme son nom l'indique.
columns()
)
Il y a quatre familles de colonnes avec un mot-clef associe.
All
est utilise pour enumerer toutes les colonnes.
Primary
concerne la ou les colonnes qui constituent la clef primaire.
Si cette famille n'est pas decrite, on considere qu'il s'agit d'une clef
primaire sur un seul champ, et en l'occurence, le premier de All
.
Essential
regroupe les colonnes indispensables qui sont retournees lors d'une
interrogation.
Enfin, TEMP
est utilise pour des champs calcules ou d'autres valeurs qui ne
seront pas stockees en base.
1-n
, 0-1
, n-m
Les relations 1-n, 0-1 sont traditionnellement faciles a modeliser.
Pour les relations n-m, la modelisation est plus complexe, mais en gros, on a une table d'association et eventuellement des attributs sur cette association.
has_a()
, has_many()
, might_have()
)
Chaque colonne decrite grace a la methode columns()
est utilisable en lecture
et en ecriture. Toutefois, les colonnes TEMP ne sont pas vraiment utilisables en
ecriture. Il est possible de modifier leur valeur, mais elle ne sera pas
enregistree.
Class::DBI::Loader
Class::DBI::Loader est un module qui permet d'analyser une base de donnees a la recherche de la structure.
Les metadonnees sont exploitees pour construire automatiquement les classes, pour les SGBD supportes. Sybase PostgreSQL, Informix, Oracle, DB2, MySQL, SQLite. CDBI::L::GraphViz est utilise pour exporter des graphes GraphViz.
Class::DBI::Loader::Relationship
Class::DBI::Loader::Relationship permet d'exprimer les relations entre classes et donc entre tables a l'aide de descriptions en langage naturel.
Les singuliers et les pluriels sont pris en charge, pour faciliter l'ecriture.
Class::DBI::AbstractSearch
Class::DBI::AbstractSearch permet d'enrichir l'expression de certaines requetes en ajoutant des criteres de recherche interpretes comme dans SQL.
Il est posible de specifier des noms de colonne avec des restrictions ou des ordre de tri, par exemple.
package Asso::DBI; use base 'Class::DBI'; Asso::DBI->connection( 'dbi:SQLite:dbname=rando.db', '', '' );
package Asso::rando; use base 'Asso::DBI'; Asso::rando->table('rando'); Asso::rando->columns(All => qw/ id jour titre distance rythme publication descriptif /);
__PACKAGE__->has_many( randoanimateurs => [ 'Asso::RandoAnimateur' => 'animateur' ] );
Un exemple de description complete du modele :
#!/usr/bin/perl use strict; use warnings; package Asso::DBI; use base 'Class::DBI'; __PACKAGE__->connection( 'dbi:SQLite:dbname=rando.db', '', '' ); package Asso::Rando; use base 'Asso::DBI'; __PACKAGE__->table('rando'); __PACKAGE__->columns( All => qw/ id jour titre distance rythme publication descriptif / ); __PACKAGE__->has_many( animateurs => [ 'Asso::RandoAnimateur' => 'animateur' ] ); __PACKAGE__->constrain_column( rythme => [qw/L M S R/] ); __PACKAGE__->has_many( inscriptions => 'Asso::Inscription' ); package Asso::Adherent; use base 'Asso::DBI'; __PACKAGE__->table('adherent'); __PACKAGE__->columns( All => qw/id nom prenom naissance email adhesion echeance/ ); __PACKAGE__->has_many( animateurs => 'Asso::Animateur' ); __PACKAGE__->has_many( inscriptions => 'Asso::Inscription' ); package Asso::Inscription; use base 'Asso::DBI'; __PACKAGE__->table('inscription'); __PACKAGE__->columns( Primary => qw/rando_id adherent_id/ ); __PACKAGE__->columns( All => qw/rando_id adherent_id/ ); __PACKAGE__->has_a( rando_id => 'Asso::Rando' ); __PACKAGE__->has_a( adherent_id => 'Asso::Adherent' ); package Asso::Animateur; use base 'Asso::DBI'; __PACKAGE__->table('animateur'); __PACKAGE__->columns( All => qw/adherent_id date_diplome parrain_id/ ); __PACKAGE__->has_a( adherent_id => 'Asso::Adherent' ); __PACKAGE__->has_a( parrain_id => 'Asso::Adherent' ); __PACKAGE__->has_many( randos => [ 'Asso::RandoAnimateur' => 'rando' ] ); package Asso::RandoAnimateur; use base 'Asso::DBI'; __PACKAGE__->table('rando_animateur'); __PACKAGE__->columns( Primary => qw/rando_id animateur_id/ ); __PACKAGE__->columns( All => qw/rando_id animateur_id/ ); __PACKAGE__->has_a( rando_id => 'Asso::Rando' ); __PACKAGE__->has_a( animateur_id => 'Asso::Animateur' ); 1;
CDBI::L
use Class::DBI::Loader; my $loader = Class::DBI::Loader->new( dsn => 'dbi:SQLite:dbname=rando.db', namespace => '' );
Dans cette optique, il suffit de fournir a Class::DBI::Loader()
une chaine de
connexion et une chaine de caractere qui permet de definir un espace de nommage
On y gagne en description, mais il faut que la base de donnees soit supportee pour le reverse engineering
Asso::Inscription->has_a( rando_id => 'Asso::Rando' );
Asso::Rando->has_many( inscriptions => 'Asso::Inscription' );
La relation est exploitable dans les deux sens. Il y a donc une relation has_a dans un sens et la reciproque has_many dans l'autre
use Class::DBI::Loader::Relationship; my $loader = Class::DBI::Loader->new( dsn => 'dbi:SQLite:dbname=rando.db', namespace => '' ); $loader->relationship( "a rando has rando_animateurs" );
Asso::Animateur->has_many( randos => [ 'Asso::RandoAnimateur' => 'rando' ] );
Asso::RandoAnimateur->has_a( animateur_id => 'Asso::Animateur' );
Asso::RandoAnimateur->has_a( rando_id => 'Asso::Rando' );
Asso::Rando->has_many( animateurs => [ 'Asso::RandoAnimateur' => 'animateur' ] );
$loader->relationship( "a rando has animateurs on rando_animateurs" );
Asso::Rando->add_constraint( 'liste_rythme', rythme => \&verifie_rythme );
sub verifie_rythme { my ($rythme) = @_; return $rythme =~ /^[LMSR]$/; }
Cette methode permet d'appeler une fonction pour valider des contraintes qui peuvent etre complexes. Dans l'exemple, la contrainte est enumerative et peut etre avantageusement remplacee par le raccourci ci-apres
Asso::Rando->constraint_column( rythme => [qw/L M S R/]);
La methode constraint_column specifie une simple instructions pour valider une correspondance ou une egalite. Dans l'exemple, la contrainte est verifiee a l'aide d'une liste
Asso::Rando->add_trigger( after_create => sub { my ( $self, %args ) = @_; $self->rythme='L'; } );
Cet exemple force une valeur lors de l'ajout d'un enregistrement, et n'est pas vraiment utile. Neanmoins, il illustre la facilite d'ecriture des declencheurs
Permet de rechercher
Idem a ce qui precede mais permet de retourner des listes d'objets sur des chaines partielles (%)
Cette methode retourne un objet
foreach my $rando (Asso::Rando->retrieve_all) { printf( "%s : %s (%s)\n", $rando->titre, $rando->descriptif, $rando->rythme ); }
Cette methode retourne tous les objets
Rando::rando->insert( { jour => '2006-11-25', titre => 'La Villette', distance => 0.5, rythme => L, publication => 1, descriptif => 'Le marathon des journees Perl francophones debutera samedi 25', } );
$rando->delete;
Si l'on a un objet que l'on souhaite supprimer
Asso::Rando->retrieve(1)->delete;
On peut aussi supprimer un objet que l'on aurait juste recupere par son identifiant
Asso::Rando->search(jour => '2006-11-25')->delete_all;
On peut aussi supprimer toute une liste d'objets retournes par la methode search
Asso::Rando->search_like(titre => 'La Ville%')->delete_all;
Idem avec search_like
$rando = Asso::Rando->retrieve(1); $rando->titre('FPW2006'); $rando->update;
L'accesseur titre permet de lire ou de modifier la valeur
Il n'y a que la clef primaire qui ne doit pas etre modifie ou ca va mal se passer
my $iterator = Asso::Rando->retrieve_all; while (my $rando = $iterator->next) { printf( "%s : %s (%s)\n", $rando->titre, $rando->descriptif, $rando->rythme ); }
Au lieu de retourner une liste d'objets, on affecte le resultat a un scalaire, et on itere a l'aide de la methode next. L'objet est alors utilise exactement comme dans le cas decrit precedement
Le gain est appreciable dans les recherches qui retournent enormement de lignes (+ de quelques centaines de milliers)
Raccourcis d'ecriture possibles
set_sql()
si besoin
Pour des jointures, ou autres operateurs comme UNION
Simplement en ayant des classes de base qui utilisent des chaines de connexion DBI differentes
Class::DBI
-
http://search.cpan.org/dist/Class-DBI/
Class::DBI::Loader
-
http://search.cpan.org/dist/Class-DBI-Loader/
Class::DBI::AbstractSearch
-
http://search.cpan.org/dist/Class-DBI-AbstractSearch/
Guide du debutant de Class::DBI
-
http://wiki.class-dbi.com/wiki/Beginners_guide