Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici

Liaison à une base de données

Introduction

Une des librairies utilisées pour gérer la liaison à une base de données est Slick (Scala Language-Integrated Connection Kit). Elle est utilisée pour les bases de données relationnelles mais également pour des bases de données en mémoire, pour simuler les comportements de production en test.

Voici une liste non exhaustive des bases de données supportées :

  • Microsoft SQL Server

  • MySQL

  • Oracle

  • PostgreSQL

  • SQLite

  • H2

Slick permet de définir chaque table comme une classe et d’utiliser les méthodes utilitaires liées aux collections pour requêter ces tables. Elle permet également d’utiliser des requêtes SQL directement. Ces requêtes sont compilées en même temps que le reste du code, ce qui permet de s’assurer de leur syntaxe.

Installation

Pour intégrer Slick au projet, il faut ajouter les dépendances suivantes au fichier build.sbt :

"com.typesafe.slick" %% "slick" % "3.3.3", 
"com.typesafe.slick" %% "slick-hikaricp" % "3.3.3" 

Il faut également ajouter comme dépendance le driver de la base de données à utiliser. Dans notre cas, nous allons utiliser une base de données H2 en mémoire :

"com.h2database" % "h2" % "1.4.200" 

Dans notre exemple, le logging est géré par la librairie "akka-slf4j" et on ajoute une ligne dans le fichier logback.xml pour configurer le niveau de log pour Slick à warn au lieu de debug :

<logger name="slick" level="warn"/> 

Pour définir une base de données, il faut ajouter dans le fichier application.conf un objet composé des informations suivantes :

  • url pour se connecter à la base de données.

  • driver à utiliser pour ce type de base de données.

  • connectionPool pour définir si un pool de connexion est activé.

  • keepAliveConnection pour garder une connexion ouverte pendant la durée de vie de la base de données.

Dans notre exemple, nous allons utiliser une base de données de type H2 nommée eni et donc le driver org.h2.Driver ajouté dans les dépendances. Comme...

Tables

1. Définition

Comme expliqué dans l’introduction, chaque table est définie par une classe qui étend la classe Table[T]T est un tuple contenant autant d’éléments que de colonnes dans la table.

Cette table prend comme arguments un tag et un nom de table. C’est pour cela que la classe qui étend la classe Table[T] prend comme argument un tag et définit un nom de table.

Prenons comme exemple une table UTILISATEURS qui définit les colonnes suivantes :

  • id : la clé primaire de type Int qui est auto-incrémentée.

  • nom : le nom de l’utilisateur d’une taille maximale de 20 caractères.

  • prenom : le prénom optionnel de l’utilisateur.

  • age : l’âge de l’utilisateur dont la valeur est par défaut 18.

  • email : l’email de l’utilisateur dont la valeur est unique dans cette table.

Cette table étend donc le type Table[(Int, String, Option[String], Int, String)].

class Utilisateurs(tag: Tag) extends Table[(Int, String, 
Option[String], Int, String)](tag, "UTILISATEURS") { ... } 

Dans la classe qui définit la table, on définit les colonnes de cette dernière grâce à la méthode column[T]T correspond au type de donnée souhaité dans la colonne. Les types suivants sont supportés par défaut :...

Requêtes

1. Récupération

Pour créer une requête destinée à récupérer l’intégralité des éléments d’une TableQuery[T], on utilise la méthode result.

Dans notre exemple, cela correspond à la requête SQL suivante :

println(utilisateurs.result.statements.mkString("\n")) 
// select "ID", "NOM", "PRENOM", "AGE", "EMAIL" from 
"UTILISATEURS" 

Lorsqu’on récupère tous les éléments de la table utilisateurs, on obtient les trois éléments créés lors de l’initialisation.

val recuperation = db.run(utilisateurs.result).map { result => 
 println(s"${result.length} utilisateurs enregistrés") 
 result.foreach(println) 
} 
// recuperation: scala.concurrent.Future[Unit] = Future(<not 
completed>) 
 
Await.result(recuperation, Duration.Inf) 
// 3 utilisateurs enregistrés 
// (1,Dubois,Some(Dimitri),29,ddubois@test.fr) 
// (2,Mercier,None,72,lmercier@test.fr) 
// (3,Polka,Some(Marie),65,mpolka@test.fr) 

On utilise la méthode map sur le lancement de chaque requête pour récupérer le résultat et l’imprimer dans la console.

Pour récupérer uniquement un ou plusieurs champs, on utilise la méthode map sur le result et on renvoie le ou les champs souhaités.

Dans notre exemple, pour récupérer uniquement les emails de la table utilisateurs, on utilise la fonction anonyme _.email dans la méthode map. Cela correspond à la requête suivante :

println(utilisateurs.map(_.email).result.statements.mkString("\n") 
) 
// select "EMAIL" from "UTILISATEURS" 

En lançant cette requête, on récupère les trois emails des éléments créés lors de l’initialisation.

val recuperationChamps =  
db.run(utilisateurs.map(_.email).result).map { result => 
 println("Emails des utilisateurs") 
 result.foreach(println) 
} 
// recuperationChamps: scala.concurrent.Future[Unit] = Future(<not completed>) 
 
Await.result(recuperationChamps, Duration.Inf) 
// Emails...