Date de première publication : 2026/01/07
L'objectif de ce TP est d'exposer une API REST (de type HATEOAS) de données stockées dans une base non relationnelles comme MongoDB.
Les données exposées concernent la gestion de collections/d'albums de "billets souvenir".
Mongo DB
Les machines à l'ISIMA sont configurées pour vous permettre d'utiliser docker. Si vous voulez travailler sur votre propre machine, il vous faudra installer ce qu'il faut. Par exemple, sous MacOS, il faut installer Docker Desktop
Pour utiliser docker à l'ISIMA, vous devez toutefois lancer :
docker-rootless-init.sh
docker pull mongo:latest
docker run -d \
--name mongodb \
-p 27017:27017 \
-v mongodb_data:$HOME/Documents/MONGO \
mongo:latest
Voici quelques commandes docker pour la gestion de votre image :
docker ps -a
docker stop mongodb
docker start mongodb
docker rm mongodb # donnees conservees
docker volume rm mongodb_data # suppression des donnees
J'aime bien manipuler la base MongoDB en ligne avec le shell :
docker exec -it mongodb mongosh
Voici quelques commandes :
show dbs
use base
show collections
db.types.deleteMany({});
db.collections.find();
Vous pouvez également installer Mongo DB Compass qui propose une interface graphique pour manipuler les "bases"
Mise en place du projet
Il faut créer un nouveau projet Spring Boot et choisir les options suivantes :
On a sélectionné les options suivantes :
- Project : Gradle - groovy - le logiciel d'automatisation
- Langage : Java
- Spring Boot : 4.0.1 - la version stable la plus récente
- Dependencies
- Spring Boot DevTools
- Data Rest
- Data Mongo
- Lombok
- Project Metadata
- Group : app
- Artifact : ZZouvenir
- Name : ZZouvenir
- Package name : app
- Java : 17
Dans le fichier application.properties, il faudra dire quelle base utiliser (on peut spécifier utilisateur et mot de passe entre autres) :
spring.mongodb.uri=mongodb://localhost:27017/zzouvenir
Nous allons avoir besoin de décrire les données à mettre en base :
- BilletType : la base de données des types de billet. On se contentera de les identifier par une amorce et une année (même si on sait que cela ne suffit pas)
- Album/Collection : un album aura un nom et comportera une liste de billets.
- Billet : chaque billet a un type et un numéro pour en faire quelque chose d'unique
Avec les options SpringBoot choisies, toutes les classes sont des Document - les instances peuvent être exposées.
Chaque document a besoin d'un identifiant , soit au travers d'un attribut annoté @Id, soit un champ _id calculé automatiquement par MongoDB.
La base MongoDB ne gère pas les relations comme pourrait le faire une base SQL et notamment les suppressions en cascade par exemple.
On peut gérer la notion de "clé étrangère" avec les annotations @DocumentReference (NOSQL - à privilégier), @DBRef (NOSQL) ou encore @OneToMany (objet).
Voici donc un squelette de code pour gérer tout cela : (n'hésitez pas à utiliser Lombok pour générer les méthodes)
import org.springframework.data.annotation.Id;
public class BilletType {
@Id
String id;
String annee;
String amorce;
String numero;
public BilletType(String amorce, String annee, String numero) {
this.id = amorce+annee+numero;
this.annee = annee;
this.amorce = amorce;
this.numero = numero;
}
@Override
public String toString() {
return String.format(
"BilletType[id=%s, amorce='%s', annee='%s', numero='%s' ]",
id, amorce, annee, numero);
}
}
import org.springframework.data.annotation.Id;
import java.util.*;
public class Album {
@Id
String id;
List<Billet> billets;
public Album(String id) {
this.id = id;
billets = new ArrayList<Billet>();
}
public void add(Billet billet) {
billets.add(billet);
}
}
Pour finir, la classe Billet :
public class Billet {
String id;
BilletType type;
String numero;
public Billet(BilletType type, String numero) {
this.id = type.getId()+numero;
this.type = type;
this.numero = numero;
}
}
Il faut encore écrire les repositories BilletTypeRepository et AlbumRepository.
La classe Billet n'a pas de repository, ce n'est pas un document. Les informations de billet sont embarquées dans la collection/album.
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
// annotation optionnelle, sauf si on veut changer le nom de publication
@RepositoryRestResource(collectionResourceRel = "types", path = "types")
public interface BilletTypeRepository extends MongoRepository<BilletType, String> {
}
On doit encore tester que cela marche. Créons quelques données :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ZZouvenirApplication implements CommandLineRunner {
@Autowired
BilletTypeRepository types;
@Autowired
AlbumRepository albums;
public static void main(String[] args) {
SpringApplication.run(ZZouvenirApplication.class, args);
}
@Override
public void run(String... strings) throws Exception {
albums.deleteAll();
types.deleteAll();
BilletType type1, type2;
types.save(new BilletType("UEGS", "2016", "1"));
types.save(new BilletType("UEGS", "2026", "2"));
types.save(new BilletType("UEGS", "2017", "3"));
types.save(new BilletType("UEGS", "2018", "4"));
types.save(new BilletType("UEGS", "2019", "5"));
types.save(new BilletType("UEGS", "2023", "6"));
types.save(new BilletType("UEGS", "2018", "7"));
types.save(new BilletType("UEGS", "2024", "8"));
types.save(type1 = new BilletType("UEGS", "2025", "9"));
types.save(type2 = new BilletType("UEQZ", "2020", "1"));
Album album = new Album("loic");
Billet billet = new Billet(type1, "000001");
album.add(billet);
album.add(new Billet(type2, "000002"));
albums.save(album);
System.out.println("BilletTypes found with findAll():");
System.out.println("-------------------------------");
for (BilletType btype : types.findAll()) {
System.out.println(btype);
}
}
}
On peut vérifier que la base est bien peuplée avec CURL ou un navigateur :
curl http://localhost:8080
curl http://localhost:8080/types
curl http://localhost:8080/albums
curl http://localhost:8080/albums/loic
curl -i -X POST -H "Content-Type:application/json" -d "{ \"id\" : \"autre\" }" http://localhost:8080/albums
Par défaut, tous les repositories sont publiés. On peut se limiter à ceux qui sont annotés avec
@RepositoryRestResource, il faut alors ajouter la configuration suivante :
spring.data.rest.detection-strategy=annotated
On peut aussi interdire un type d'action à réaliser, soit en écrivant une configuration :
@Configuration
public class RestConfig implements RepositoryRestConfigurer {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.getExposureConfiguration()
.forDomainType(BilletType.class) .withItemExposure((metadata, httpMethods) -> httpMethods.disable(HttpMethod.DELETE))
.withCollectionExposure((metadata, httpMethods) -> httpMethods.disable(HttpMethod.DELETE));
}
}
// le code ci dessus concerne la classe (dite métier ou domaine) et les méthodes que l'on ne veut pas exposer.
soit en annotant un repository ou une de ses méthodes :
@RepositoryRestResource
public interface BilletTypeRepository extends ... {
@Override
@RestResource(exported = false)
void deleteById(String id);
@Override
@RestResource(exported = false)
void delete(BilletType entity);
}
Utilisation de l'API Rest
On peut écrire (ou faire écrire à une IA) une page HTML qui récupère avec de l'AJAX les éléments stockés en base
Si la page web n'est pas hébergée sur le même serveur que celui de l'API, il faudra permettre la consultation en désactivant la sécurité du navigateur.
Un des moyens est de positionner une annotation @CrossOrigin sur les repositories consultés.
import org.springframework.web.bind.annotation.CrossOrigin;
Allez plus loin
Il faudrait maintenant sécuriser l'API pour que seuls certains utilisateurs (avec des rôles) puissent accéder aux informations.


