Newer
Older
Pour récupérer le projet vous pouvez utiliser la commande `git clone
https://gitlab.univ-lille.fr/yvan.peter/m4102_tp3.git`
L'arborescence ci-dessous vous montre le contenu du projet qui vous
servira de point de départ. Maven est configuré grâce au fichier
`pom.xml` qui permet entre autre de spécifier les dépendances du
projet.
La classe `ApiV1` sera le point d'entrée de notre application REST qui
permet de configurer le chemin de l'URI (`@ApplicationPath`) ainsi que
les paquetages Java qui contiennent les ressources.
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
~~~
.
├── architecture.svg
├── pom.xml
├── README.md
└── src
├── main
│ ├── java
│ │ └── fr
│ │ └── ulille
│ │ └── iut
│ │ └── pizzaland
│ │ ├── ApiV1.java
│ │ ├── BDDFactory.java
│ │ ├── beans
│ │ ├── dao
│ │ │ ├── UUIDArgumentFactory.java
│ │ │ └── UUIDArgument.java
│ │ ├── dto
│ │ │ └── IngredientDto.java
│ │ ├── Main.java
│ │ └── resources
│ │ ├── BDDClearRessource.java
│ │ └── IngredientResource.java
│ └── resources
│ ├── ingredients.json
│ └── logging.properties
└── test
├── java
│ └── fr
│ └── ulille
│ └── iut
│ └── pizzaland
│ └── IngredientResourceTest.java
└── resources
└── logging.properties
~~~
Nous pouvons tout d'abord réfléchir à l'API REST que nous allons offrir pour la ressource *ingredients*. Celle-ci devrait répondre aux URI suivantes :
| URI | Opération | MIME | Requête | Réponse |
| :----------------------- | :---------- | :--------------------------------------------- | :-- | :---------------------------------------------------- |
| /ingredients | GET | <-application/json<br><-application/xml | | liste des ingrédients (I2) |
| /ingredients/{id} | GET | <-application/json<br><-application/xml | | un ingrédient (I2) ou 404 |
| /ingredients/{id}/name | GET | <-text/plain | | le nom de l'ingrédient ou 404 |
| /ingredients | POST | <-/->application/json<br>->application/x-www-form-urlencoded | Ingrédient (I1) | Nouvel ingrédient (I2)<br>409 si l'ingrédient existe déjà (même nom) |
| /ingredients/{id} | DELETE | | | |
Un ingrédient comporte uniquement un identifiant et un nom. Sa
"id": "f38806a8-7c85-49ef-980c-149dcd81d306",
"name": "mozzarella"
}
Lors de la création, l'identifiant n'est pas connu car il sera fourni
par le JavaBean qui représente un ingrédient. Aussi on aura une
### Architecture logicielle de la solution
La figure ci-dessous présente l'architecture globale qui devra être
mise en place pour notre développement :
![Architecture de la solution](architecture.svg "Architecture")
#### JavaBeans
JavaBean est un standard pour les objets Java permettant de les créer
et de les initialiser et de les manipuler facilement. Pour cela ces
objets doivent respecter un ensemble de conventions :
- la classe est sérialisable
- elle fournit au moins un constructeur vide
- les attributs privés de la classe sont manipulables via des
Les DTO et la classe `Ingredient`décrits dans la suite sont des
JavaBeans.
#### Data Transfer Object (DTO)
Les DTO correspondent à la représentation des données qui sera
transportée par HTTP. Ce sont des Javabeans qui possèdent les même
propriétés que la représentation (avec les getter/setter
correspondants).
Jersey utilisera les *setter* pour initialiser l'objet à partir
de la représentation JSON ou XML et les *getter* pour créer la
représentation correspondante.
#### Data Access Object (DAO)
Le DAO permet de faire le lien entre la représentation objet et le
Nous utiliserons la [librairie JDBI](http://jdbi.org/) qui permet
d'associer une interface à des requêtes SQL.
La classe `BDDFactory` qui vous est fournie permet un accès facilité
aux fonctionnalités de JDBI.
#### La représentation des données manipulées par la ressource
La classe `Ingredient` est un JavaBean qui représente ce qu'est un
ingrédient. Elle porte des méthodes pour passer de cette
représentation aux DTO.
Cela permet de découpler l'implémentation de la ressource qui traite
porter des comportements liés à cette donnée (par ex. calcul de TVA).
## Mise en œuvre
### Une première implémentation : récupérer les ingrédients existants
Nous allons réaliser un développement dirigé par les tests. Dans un
premier temps, nous allons commencer par un test qui récupère une
liste d'ingrédients vide qui sera matérialisée par un tableau JSON
vide `[]`.
Le code suivant qui se trouve dans la classe `IngredientResourceTest`
montre la mise en place de l'environnement (`configure()`) et l'amorce
d'un premier test.
public class IngredientResourceTest extends JerseyTest {
@Override
protected Application configure() {
return new ApiV1();
}
@Test
public void testGetEmptyList() {
Response response = target("/ingredients").request().get();
// Vérification de la valeur de retour (200)
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
// Vérification de la valeur retournée (liste vide)
List<IngredientDto> ingredients;
ingredients = response.readEntity(new GenericType<List<IngredientDto>>(){});
En héritant de JerseyTest, votre classe de test se comporte comme un
[`Client`
JAX-RS](https://docs.oracle.com/javaee/7/api/jakarta/ws/rs/client/Client.html). La
méthode `target()` notamment permet de préparer une requête sur une
URI particulière.
Vous pouvez compiler votre code ainsi que les tests au moyen
des commandes `mvn compile` et `mvn test-compile`. La compilation du
code et des tests se fera automatiquement si nécessaire quand vous
faites un `mvn test`.
Pour pouvoir compiler ce premier test, il faut au minimum fournir le
DTO `IngredientDto`.
Pour commencer, on se contentera de l'implémentation minimale
suivante :
Pour réussir, ce premier test, nous allons mettre en place la
Une première implémentation de la ressource pourra avoir la forme
suivante :
@Path("/ingredients")
public class IngredientResource {
Avec cette première implémentation, on va pouvoir tester notre
ressource :
### Récupérer un ingrédient existant
Nous allons continuer en ajoutant la possibilité de récupérer un
ingrédient particulier à partir de son identifiant.
Pour cela voici un premier test qui permettra de vérifier cela :
@Test
public void testGetExistingIngredient() {
Ingredient ingredient = new Ingredient();
ingredient.setName("Chorizo");
dao.insert(ingredient);
Response response = target("/ingredients").path(ingredient.getId().toString()).request(MediaType.APPLICATION_JSON).get();
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
Ingredient result = Ingredient.fromDto(response.readEntity(IngredientDto.class));
assertEquals(ingredient, result);
Vous pourrez vérifier que le test échoue au moyen de la commande `mvn test`
Afin de réussir ce test, nous devons compléter la classe IngredientDto
avec les getter/setter correspondant aux propriétés de l'object JSON.
public class IngredientDto {
private UUID id;
private String name;
public IngredientDto() {
}
public void setId(UUID id) {
this.id = id;
}
public UUID getId() {
return id;
}
public void setName(String name) {
Du côté de la ressource, on peut fournir une première implémentation :
import jakarta.ws.rs.PathParam;
import fr.ulille.iut.pizzaland.beans.Ingredient;
public IngredientDto getOneIngredient(@PathParam("id") UUID id) {
Ingredient ingredient = new Ingredient();
ingredient.setId(id); // juste pour avoir le même id pour le test
ingredient.setName("Chorizo");
Pour cette méthode, nous avons introduit la classe `Ingredient`. Ce
JavaBean représente un ingrédient manipulé par la ressource.
import java.util.UUID;
import fr.ulille.iut.pizzaland.dto.IngredientDto;
public class Ingredient {
private UUID id = UUID.randomUUID();
private String name;
public Ingredient() {
}
public Ingredient(String name) {
this.name = name;
}
public Ingredient(UUID id, String name) {
IngredientDto dto = new IngredientDto();
dto.setId(i.getId());
dto.setName(i.getName());
}
public static Ingredient fromDto(IngredientDto dto) {
Ingredient ingredient = new Ingredient();
ingredient.setId(dto.getId());
ingredient.setName(dto.getName());
return ingredient;
return "Ingredient [id=" + id + ", name=" + name + "]";
## Introduction de la persistence
Pour aller plus loin et mettre en place la création des ingrédients il
va falloir introduire la persistence. Pour cela, nous allons utiliser
la librairie JDBI qui permet d'associer un modèle objet aux tables de
base de données.
Pour cela nous allons devoir implémenter le DAO (Data Access Object) `IngredientDao` :
import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
import org.jdbi.v3.sqlobject.customizer.Bind;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
@SqlUpdate("CREATE TABLE IF NOT EXISTS ingredients (id VARCHAR(128) PRIMARY KEY, name VARCHAR UNIQUE NOT NULL)")
void createTable();
@SqlUpdate("DROP TABLE IF EXISTS ingredients")
void dropTable();
@SqlUpdate("INSERT INTO ingredients (id, name) VALUES (:id, :name)")
void insert(@BindBean Ingredient ingredient);
@SqlUpdate("DELETE FROM ingredients WHERE id = :id")
void remove(@Bind("id") UUID id);
@SqlQuery("SELECT * FROM ingredients WHERE name = :name")
@RegisterBeanMapper(Ingredient.class)
Ingredient findByName(@Bind("name") String name);
@SqlQuery("SELECT * FROM ingredients")
@RegisterBeanMapper(Ingredient.class)
List<Ingredient> getAll();
@SqlQuery("SELECT * FROM ingredients WHERE id = :id")
@RegisterBeanMapper(Ingredient.class)
Ingredient findById(@Bind("id") UUID id);
}
JDBI fonctionne par annotations :
- Les annotations `sqlUpdate` et `SqlQuery` correspondent à des
requêtes SQL en modification ou non.
- `@RegisterBeanMapper` permet d'associer une classe à un résultat
(les champs de la table sont associés aux propriétés du bean).
- `@Bind` permet d'associer un paramètre de méthode à un paramètre nommé dans la requête SQL.
Reprenons maintenant le code déjà écrit pour aller chercher les
ingrédients dans une base de données (nous utiliserons `Sqlite`).
### Les tests avec la base de données
Nous allons utiliser le DAO pour insérer des données dans la table
afin de réaliser nos tests. Nous utiliserons une base de données de
tests qui est définie via la classe `BDDFactory`.
Dans la classe `IngredientResourceTest`Les méthodes `setEnvUp` et `tearEnvDown` permettent de créer et
public class IngredientResourceTest extends JerseyTest {
private IngredientDao dao;
@Override
protected Application configure() {
BDDFactory.setJdbiForTests();
@Before
public void setEnvUp() {
dao = BDDFactory.buildDao(IngredientDao.class);
dao.createTable();
}
@After
public void tearEnvDown() throws Exception {
dao.dropTable();
}
@Test
public void testGetExistingIngredient() {
Ingredient ingredient = new Ingredient();
ingredient.setName("Chorizo");
dao.insert(ingredient);
Response response = target("/ingredients").path(ingredient.getId().toString()).request(MediaType.APPLICATION_JSON).get();
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
Ingredient result = Ingredient.fromDto(response.readEntity(IngredientDto.class));
assertEquals(ingredient, result);
}
@Produces("application/json")
@Path("/ingredients")
public class IngredientResource {
private static final Logger LOGGER = Logger.getLogger(IngredientResource.class.getName());
private IngredientDao ingredients;
@Context
public UriInfo uriInfo;
public IngredientResource() {
ingredients = BDDFactory.buildDao(IngredientDao.class);
ingredients.createTable();
LOGGER.info("IngredientResource:getAll");
List<IngredientDto> l = ingredients.getAll().stream().map(Ingredient::toDto).collect(Collectors.toList());
@GET
@Path("{id}")
@Produces({ "application/json", "application/xml" })
public IngredientDto getOneIngredient(@PathParam("id") UUID id) {
LOGGER.info("getOneIngredient(" + id + ")");
try {
Ingredient ingredient = ingredients.findById(id);
LOGGER.info(ingredient.toString());
return Ingredient.toDto(ingredient);
} catch (Exception e) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
### Les tests fonctionnent avec la base de données
Nous pouvons maintenant vérifier que la base fonctionne avec la base
de données :
## Complétons maintenant les différents tests
L'implémentation de la classe devrait fonctionner avec le test suivant
:
@Test
public void testGetNotExistingIngredient() {
Response response = target("/ingredients").path(UUID.randomUUID().toString()).request().get();
assertEquals(Response.Status.NOT_FOUND.getStatusCode(),response.getStatus());
}
Il va falloir implémenter la méthode POST pour la création des
ingrédients. Commençons par les différents tests : création, création
de deux ingrédients identiques et création d'ingrédient sans nom.
public void testCreateIngredient() {
IngredientCreateDto ingredientCreateDto = new IngredientCreateDto();
Response response = target("/ingredients").request().post(Entity.json(ingredientCreateDto));
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
IngredientDto returnedEntity = response.readEntity(IngredientDto.class);
assertEquals(target("/ingredients/" + returnedEntity.getId()).getUri(), response.getLocation());
assertEquals(returnedEntity.getName(), ingredientCreateDto.getName());
}
Ingredient ingredient = new Ingredient();
ingredient.setName("Chorizo");
dao.insert(ingredient);
IngredientCreateDto ingredientCreateDto = Ingredient.toCreateDto(ingredient);
Response response = target("/ingredients").request().post(Entity.json(ingredientCreateDto));
assertEquals(Response.Status.CONFLICT.getStatusCode(), response.getStatus());
}
@Test
public void testCreateIngredientWithoutName() {
IngredientCreateDto ingredientCreateDto = new IngredientCreateDto();
Response response = target("/ingredients").request().post(Entity.json(ingredientCreateDto));
assertEquals(Response.Status.NOT_ACCEPTABLE.getStatusCode(), response.getStatus());
}
Nous utiliserons un DTO spécifique `IngredientCreateDto` dans la
mesure où nous n'aurons que le nom de l'ingrédient pour la création.
La classe [`jakarta.ws.rs.client.Entity<T>`](https://docs.oracle.com/javaee/7/api/jakarta/ws/rs/client/Entity.html) permet de définir le corps de
la requête POST et le type de données associée (ici `application/json`).
Nous devons également fournir une implémentation de
`IngredientCreateDto` pour pouvoir compiler notre code :
public class IngredientCreateDto {
private String name;
public void setName(String name) {
this.name = name;
}
Nous pouvons maintenant compiler notre code de test et constater que
ceux-ci échouent.
Failed tests: testCreateSameIngredient(fr.ulille.iut.pizzaland.IngredientResourceTest): expected:<409> but was:<405>
testCreateIngredientWithoutName(fr.ulille.iut.pizzaland.IngredientResourceTest): expected:<406> but was:<405>
testCreateIngredient(fr.ulille.iut.pizzaland.IngredientResourceTest): expected:<201> but was:<405>
Nous pouvons maintenant implémenter notre méthode POST dans la
ressource :
import jakarta.ws.rs.POST;
import fr.ulille.iut.pizzaland.dto.IngredientCreateDto;
public Response createIngredient(IngredientCreateDto ingredientCreateDto) {
Ingredient existing = ingredients.findByName(ingredientCreateDto.getName());
throw new WebApplicationException(Response.Status.CONFLICT);
}
try {
Ingredient ingredient = Ingredient.fromIngredientCreateDto(ingredientCreateDto);
IngredientDto ingredientDto = Ingredient.toDto(ingredient);
URI uri = uriInfo.getAbsolutePathBuilder().path(ingredient.getId().toString()).build();
return Response.created(uri).entity(ingredientDto).build();
e.printStackTrace();
throw new WebApplicationException(Response.Status.NOT_ACCEPTABLE);
}
Comme nous vérifions qu'il n'y a pas déjà un ingrédient avec le nom
fourni, nous devont ajouter une méthode `findbyName` à notre DAO
@SqlQuery("SELECT * FROM ingredients WHERE name = :name")
Nous avons également besoin de rajouter les méthodes de conversion
pour ce DTO à notre bean `Ingredient` :
public static IngredientCreateDto toCreateDto(Ingredient ingredient) {
IngredientCreateDto dto = new IngredientCreateDto();
dto.setName(ingredient.getName());
public static Ingredient fromIngredientCreateDto(IngredientCreateDto dto) {
Ingredient ingredient = new Ingredient();
ingredient.setName(dto.getName());
Vous aurez peut-être un affichage d'exception liée au test de création
de doublon, toutefois le test est réussi puisqu'il a levé une
exception qui a été traduite par un code d'erreur HTTP 406.
### Implémentation de la méthode DELETE
Les tests liés à la méthode DELETE sont les suivants :
public void testDeleteExistingIngredient() {
Ingredient ingredient = new Ingredient();
ingredient.setName("Chorizo");
dao.insert(ingredient);
Response response = target("/ingredients/").path(ingredient.getId().toString()).request().delete();
assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus());
Ingredient result = dao.findById(ingredient.getId());
assertEquals(result, null);
}
@Test
public void testDeleteNotExistingIngredient() {
Response response = target("/ingredients").path(UUID.randomUUID().toString()).request().delete();
assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus());
}
Après avoir constaté que ces tests échouent, nous pouvons fournir une
implémentation pour la méthode DELETE :
@DELETE
@Path("{id}")
public Response deleteIngredient(@PathParam("id") UUID id) {
if ( ingredients.findById(id) == null ) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return Response.status(Response.Status.ACCEPTED).build();
}
Nous devons également implémenter la méthode remove dans
`IngredientDao` :
Avec cette implémentation, nos tests réussissent.
### Implémentation de la méthode GET pour récupérer le nom de l'ingrédient
Commençons par les tests correspondant à cette URI (GET
/ingredients/{id}/name)
public void testGetIngredientName() {
Ingredient ingredient = new Ingredient();
ingredient.setName("Chorizo");
dao.insert(ingredient);
Response response = target("ingredients").path(ingredient.getId().toString()).path("name").request().get();
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
assertEquals("Chorizo", response.readEntity(String.class));
}
@Test
public void testGetNotExistingIngredientName() {
Response response = target("ingredients").path(UUID.randomUUID().toString()).path("name").request().get();
assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus());
}
L'implémentation correspondant à ce test est simple :
public String getIngredientName(@PathParam("id") UUID id) {
Ingredient ingredient = ingredients.findById(id);
if (ingredient == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return ingredient.getName();
### Implémentation d'une méthode de création avec des données de formulaire
La création d'un ingrédient pourrait également se faire via un
formulaire Web. Dans ce cas, le type de représentation sera
`application/x-www-form-urlencoded`.
On peut déjà préparer un test pour cette méthode de création :
public void testCreateWithForm() {
Form form = new Form();
form.param("name", "chorizo");
Entity<Form> formEntity = Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE);
Response response = target("ingredients").request().post(formEntity);
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
String location = response.getHeaderString("Location");
String id = location.substring(location.lastIndexOf('/') + 1);
Ingredient result = dao.findById(UUID.fromString(id));
On peut maintenant fournir une implémentation pour cette méthode :
@POST
@Consumes("application/x-www-form-urlencoded")
public Response createIngredient(@FormParam("name") String name) {
Ingredient existing = ingredients.findByName(name);
if (existing != null) {
throw new WebApplicationException(Response.Status.CONFLICT);
}
try {
Ingredient ingredient = new Ingredient();
ingredient.setName(name);
IngredientDto ingredientDto = Ingredient.toDto(ingredient);
URI uri = uriInfo.getAbsolutePathBuilder().path("" + ingredient.getId()).build();
return Response.created(uri).entity(ingredientDto).build();
} catch (Exception e) {
e.printStackTrace();
throw new WebApplicationException(Response.Status.NOT_ACCEPTABLE);
# Créer une base de données de test
Nous avons maintenant implémenté et testé toutes les méthodes prévues
par notre API. Si nous voulons tester avec des clients, il serait bien
d'avoir quelques ingrédients dans la base de données. Pour cela, nous
allons donner la possibilité de créer des ingrédients au démarrage sur la base
d'une variable d'environnement : `PIZZAENV`.
Quand cette variable aura la valeur `withdb`, nous allons remplir la
base au démarrage avec le code suivant :
import fr.ulille.iut.pizzaland.beans.Ingredient;
import fr.ulille.iut.pizzaland.beans.Pizza;
import fr.ulille.iut.pizzaland.dao.IngredientDao;
import fr.ulille.iut.pizzaland.dao.PizzaDao;
import fr.ulille.iut.pizzaland.dto.PizzaCreateDto;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Logger;
import jakarta.json.bind.JsonbBuilder;
import jakarta.ws.rs.ApplicationPath;
@ApplicationPath("api/v1/")
public class ApiV1 extends ResourceConfig {
private static final Logger LOGGER = Logger.getLogger(ApiV1.class.getName());
public ApiV1() {
packages("fr.ulille.iut.pizzaland");
if ( environment != null && environment.equals("withdb") ) {
LOGGER.info("Loading with database");
try {
FileReader reader = new FileReader( getClass().getClassLoader().getResource("ingredients.json").getFile() );
List<Ingredient> ingredients = JsonbBuilder.create().fromJson(reader, new ArrayList<Ingredient>(){}.getClass().getGenericSuperclass());
IngredientDao ingredientDao = BDDFactory.buildDao(IngredientDao.class);
ingredientDao.dropTable();
ingredientDao.createTable();
for ( Ingredient ingredient: ingredients) {
ingredientDao.insert(ingredient);
}
} catch ( Exception ex ) {
throw new IllegalStateException(ex);
Dans un terminal, nous pouvons maintenant fixer la variable
d'environnemnet et démarrer notre serveur REST au moyen de la
Dans un autre terminal, nous pouvons utiliser `curl` pour tester nos
différentes méthodes :
$ curl -i localhost:8080/api/v1/ingredients
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 760
[{"id":"f38806a8-7c85-49ef-980c-149dcd81d306","name":"mozzarella"},{"id":"d36903e1-0cc0-4bd6-a0ed-e0e9bf7b4037","name":"jambon"},{"id":"bc5b315f-442f-4ee4-96de-486d48f20c2f","name":"champignons"},{"id":"6a04320c-3a4f-4570-96d3-61faf3f898b0","name":"olives"},{"id":"c77deeee-d50d-49d5-9695-c98ec811f762","name":"tomate"},{"id":"c9375542-8142-43f6-b54d-0d63597cf614","name":"merguez"},{"id":"dee27dd6-f9b6-4d03-ac4b-216b5c9c8bd7","name":"lardons"},{"id":"657f8dd4-6bc1-4622-9af7-37d248846a23","name":"fromage"},{"id":"070d8077-a713-49a0-af37-3936b63d5ff2","name":"oeuf"},{"id":"5d9ca5c4-517f-40fd-aac3-5a823d680c1d","name":"poivrons"},{"id":"52f68024-24ec-46c0-8e77-c499dba1e27e","name":"ananas"},{"id":"dfdf6fae-f1b2-45fa-8c39-54e522c1933f","name":"reblochon"}]
Maintenant que vous avez une ressource `ingrédients` fonctionnelle, vous pouvez passer à l'implémentation de la ressource `Pizzas`. Pour cette ressource, vous devrez d'abord définir l'API dans le fichier `pizzas.md` (URI, méthodes, représentations). Ensuite, vous pourrez développer la ressource avec les tests associés.
## Note sur la base de données
Une pizza comprend des ingrédients. Pour développer cette ressource,
vous aurez donc besoin d'un table d'association au niveau de la base
de données. Cela pourra être géré au niveau du DAO grâce à
[JDBI](https://jdbi.org/#_default_methods). Cet extrait de code montre
@SqlUpdate("CREATE TABLE IF NOT EXISTS Pizzas ....")
void createPizzaTable();
@SqlUpdate("CREATE TABLE IF NOT EXISTS PizzaIngredientsAssociation .....")
void createAssociationTable();
@Transaction
default void createTableAndIngredientAssociation() {
createAssociationTable();
createPizzaTable();
Vous écrivez les différentes méthodes annotées avec `@SqlUpdate` ou
`@SqlQuery`. Vous utilisez ensuite ces méthodes au sein d'une méthode
ayant le mot clé `default`. C'est cette méthode que vous utiliserez
dans votre ressource.