0% ont trouvé ce document utile (0 vote)
52 vues12 pages

Atelier 1 Fullstack

Transféré par

bonprix1982
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
52 vues12 pages

Atelier 1 Fullstack

Transféré par

bonprix1982
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd

Installation & configuration IntelliJ IDEA pour Spring

Boot
1 Télécharger & installer IntelliJ IDEA

 Version recommandée : IntelliJ IDEA Community (gratuite, largement suffisante


pour Spring Boot).
 Lien : https://www.jetbrains.com/idea/download
 Choisir Community Edition (ou Ultimate si tu as une licence).

2 Lancer IntelliJ & configurer le JDK 17

1. Ouvrir File → Settings (ou Ctrl+Alt+S).


2. Aller dans Build, Execution, Deployment → Build Tools → Gradle/Maven (ou
Java Compiler).
3. Dans Project SDK → cliquer sur Add JDK → sélectionner le dossier de ton JDK
17 :
o Windows : C:\Program Files\Eclipse Adoptium\jdk-17...
o Linux : /usr/lib/jvm/java-17-openjdk-amd64
o macOS :
/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home

💡 Vérifie que Project language level est bien en 17.

3 Créer un projet Spring Boot avec IntelliJ

Option A — via Spring Initializr intégré

1. File → New → Project


2. Choisir Spring Initializr
3. Service URL : https://start.spring.io
4. Renseigner :
o Group : com.example
o Artifact : demo
o Language : Java
o Java version : 17
5. Dépendances :
o Spring Web
o Spring Boot DevTools (facultatif mais pratique)
o Validation
o Lombok (optionnel)
6. Terminer → IntelliJ télécharge et configure le projet automatiquement.

Option B — importer un projet existant

1. File → Open
2. Sélectionner le dossier contenant pom.xml
3. IntelliJ détecte Maven et importe automatiquement les dépendances.

4 Structure dans IntelliJ

Dans le panneau Project :

src/main/java → code source


src/main/resources → configs (application.properties)
src/test/java → tests
pom.xml → dépendances Maven

5 Lancer le projet Spring Boot dans IntelliJ

Cliquer sur le petit ▶ vert à gauche de main


 Ouvrir DemoApplication.java

 Ou menu Run → Run 'DemoApplication'
 Accéder ensuite à :

1) Ajouter le starter web (sinon pas de port 8080)


Dans pom.xml :

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

2) Désactiver l’auto-intégration Docker Compose (pour


enlever l’ERROR)
Dans src/main/resources/application.properties (crée le fichier si besoin) :

spring.docker.compose.enabled=false
server.port=8080

Tu pourras remettre à true plus tard si tu veux que Spring démarre compose.yaml
automatiquement, mais il faut Docker en marche.

3) Vérifier la classe main


package org.example.projet1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Projet1Application {
public static void main(String[] args) {
SpringApplication.run(Projet1Application.class, args);
}
}

4) Ajouter un contrôleur minimal (pour avoir une page)


src/main/java/org/example/projet1/HelloController.java

package org.example.projet1;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
@GetMapping("/")
public String home() {
return "Bienvenue dans mon application Spring Boot 🚀";
}
}

Puis Accéder à la page web via : http://127.0.0.1:8080/

6 Redémarrage à chaud avec DevTools

Si spring-boot-devtools est dans pom.xml :

 File → Settings → Build, Execution, Deployment → Compiler → cocher Build


project automatically
 Puis Ctrl+Alt+Shift+/ → Registry → cocher
compiler.automake.allow.when.app.running
 Résultat : dès que tu sauvegardes, Spring Boot redémarre.

Astuce : Si tu veux, je peux te préparer un projet Spring Boot prêt à ouvrir dans IntelliJ
IDEA avec :

 Java 17
 Maven
 Dépendances Spring Web + DevTools
 Contrôleur /api/hello déjà codé
 Configuration IntelliJ prête

Veux-tu que je te fasse ce ZIP IntelliJ prêt à l’emploi pour ta formation ?


Parfait—voici un atelier de 2h “Création d’une API REST Spring Boot” prêt à dérouler, avec
Entity + Repository (JPA/Hibernate), Service, Controller, CRUD complet, et gestion
propre des réponses HTTP/erreurs.
Exemple de domaine : Product.

0) Pré-requis & dépendances (5 min)


pom.xml (extrait minimal)

xml
CopierModifier
<properties>
<java.version>17</java.version>
<spring-boot.version>3.3.0</spring-boot.version>
</properties>

<dependencies>
<!-- Web REST -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- JPA/Hibernate -->


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- DB de démo -->


<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

<!-- Validation @Valid -->


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!-- OpenAPI/Swagger UI (bonus) -->


<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.properties

properties
CopierModifier
spring.datasource.url=jdbc:h2:mem:demo;MODE=PostgreSQL;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.h2.console.enabled=true
# Swagger UI: http://localhost:8080/swagger-ui/index.html

Activer l’auditing (timestamps) dans l’app principale :

java
CopierModifier
@SpringBootApplication
@EnableJpaAuditing
public class DemoApplication { public static void main(String[] args)
{ SpringApplication.run(DemoApplication.class, args); } }

1) Modèle Entity & Repository (20 min)


Product.java

java
CopierModifier
package com.example.demo.product;

import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.math.BigDecimal;
import java.time.Instant;

@Entity @Table(name = "products", indexes = {


@Index(name="uk_sku", columnList="sku", unique=true),
@Index(name="idx_name", columnList="name")
})
@EntityListeners(AuditingEntityListener.class)
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
public class Product {

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)


private Long id;

@NotBlank @Size(max=120)
private String name;

@NotBlank @Size(max=40)
private String sku; // unique business key

@PositiveOrZero
@Column(precision = 12, scale = 2, nullable = false)
private BigDecimal price;

@Size(max=255)
private String description;

@CreatedDate @Column(updatable = false)


private Instant createdAt;

@LastModifiedDate
private Instant updatedAt;

@Version
private Long version; // pour gestion d’update concurrent
}

ProductRepository.java

java
CopierModifier
package com.example.demo.product;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;

import java.util.Optional;

public interface ProductRepository extends JpaRepository<Product, Long> {


Optional<Product> findBySku(@Param("sku") String sku);
boolean existsBySku(String sku);
}

2) DTO & mapping léger (10 min)


ProductRequest.java

java
CopierModifier
package com.example.demo.product.dto;

import jakarta.validation.constraints.*;
import java.math.BigDecimal;

public record ProductRequest(


@NotBlank @Size(max=120) String name,
@NotBlank @Size(max=40) String sku,
@PositiveOrZero BigDecimal price,
@Size(max=255) String description
) {}

ProductResponse.java

java
CopierModifier
package com.example.demo.product.dto;
import java.math.BigDecimal;
import java.time.Instant;

public record ProductResponse(


Long id, String name, String sku, BigDecimal price,
String description, Instant createdAt, Instant updatedAt
) {}

ProductMapper.java

java
CopierModifier
package com.example.demo.product;

import com.example.demo.product.dto.*;

public class ProductMapper {


public static Product toEntity(ProductRequest r){
return Product.builder()
.name(r.name()).sku(r.sku()).price(r.price()).description(r.descrip
tion())
.build();
}
public static ProductResponse toResponse(Product p){
return new ProductResponse(p.getId(), p.getName(), p.getSku(),
p.getPrice(),
p.getDescription(), p.getCreatedAt(), p.getUpdatedAt());
}
public static void update(Product p, ProductRequest r){
p.setName(r.name()); p.setSku(r.sku());
p.setPrice(r.price()); p.setDescription(r.description());
}
}

3) Service (règles métier + erreurs) (20 min)


NotFoundException / ConflictException

java
CopierModifier
package com.example.demo.shared;

public class NotFoundException extends RuntimeException {


public NotFoundException(String msg){ super(msg); }
}
public class ConflictException extends RuntimeException {
public ConflictException(String msg){ super(msg); }
}

ProductService.java

java
CopierModifier
package com.example.demo.product;

import com.example.demo.product.dto.*;
import com.example.demo.shared.*;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service @RequiredArgsConstructor
public class ProductService {
private final ProductRepository repo;

public Page<ProductResponse> list(Pageable pageable){


return repo.findAll(pageable).map(ProductMapper::toResponse);
}

public ProductResponse get(Long id){


var p = repo.findById(id).orElseThrow(() -> new
NotFoundException("Product %d not found".formatted(id)));
return ProductMapper.toResponse(p);
}

@Transactional
public ProductResponse create(ProductRequest req){
if (repo.existsBySku(req.sku())) throw new ConflictException("SKU
already exists");
var saved = repo.save(ProductMapper.toEntity(req));
return ProductMapper.toResponse(saved);
}

@Transactional
public ProductResponse update(Long id, ProductRequest req){
var p = repo.findById(id).orElseThrow(() -> new
NotFoundException("Product %d not found".formatted(id)));
// empêcher duplication SKU
repo.findBySku(req.sku()).filter(x -> !x.getId().equals(id))
.ifPresent(x -> { throw new ConflictException("SKU already
exists"); });
ProductMapper.update(p, req);
return ProductMapper.toResponse(p); // dirty checking
}

@Transactional
public void delete(Long id){
if (!repo.existsById(id)) throw new NotFoundException("Product %d not
found".formatted(id));
repo.deleteById(id);
}
}

4) Controller (CRUD + pagination +


@Valid) (20 min)
ProductController.java

java
CopierModifier
package com.example.demo.product;
import com.example.demo.product.dto.*;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.*;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;

@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {

private final ProductService service;

// GET /api/products?page=0&size=10&sort=price,desc
@GetMapping
public ResponseEntity<Page<ProductResponse>> list(Pageable pageable){
return ResponseEntity.ok(service.list(pageable));
}

// GET /api/products/{id}
@GetMapping("/{id}")
public ResponseEntity<ProductResponse> get(@PathVariable Long id){
return ResponseEntity.ok(service.get(id));
}

// POST /api/products
@PostMapping
public ResponseEntity<ProductResponse> create(@Valid @RequestBody
ProductRequest req){
var created = service.create(req);
return ResponseEntity
.created(java.net.URI.create("/api/products/" + created.id()))
.body(created);
}

// PUT /api/products/{id}
@PutMapping("/{id}")
public ResponseEntity<ProductResponse> update(@PathVariable Long id,
@Valid @RequestBody ProductRequest req){
return ResponseEntity.ok(service.update(id, req));
}

// DELETE /api/products/{id}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id){
service.delete(id);
return ResponseEntity.noContent().build();
}
}

5) Gestion centralisée des erreurs & codes


HTTP (15 min)
GlobalExceptionHandler.java
java
CopierModifier
package com.example.demo.shared;

import org.springframework.http.*;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
import java.util.*;

@RestControllerAdvice
public class GlobalExceptionHandler {

record ApiError(Instant timestamp, int status, String error, String


message, String path, Map<String,String> validation){}

@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ApiError> handleNotFound(NotFoundException ex,
org.springframework.web.context.request.WebRequest req){
var body = new ApiError(Instant.now(), 404, "Not Found",
ex.getMessage(), getPath(req), null);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body);
}

@ExceptionHandler(ConflictException.class)
public ResponseEntity<ApiError> handleConflict(ConflictException ex,
org.springframework.web.context.request.WebRequest req){
var body = new ApiError(Instant.now(), 409, "Conflict",
ex.getMessage(), getPath(req), null);
return ResponseEntity.status(HttpStatus.CONFLICT).body(body);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiError>
handleValidation(MethodArgumentNotValidException ex,
org.springframework.web.context.request.WebRequest req){
Map<String,String> errors = new LinkedHashMap<>();
ex.getBindingResult().getFieldErrors().forEach(fe ->
errors.put(fe.getField(), fe.getDefaultMessage()));
var body = new ApiError(Instant.now(), 400, "Bad Request", "Validation
failed", getPath(req), errors);
return ResponseEntity.badRequest().body(body);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ApiError> handleOther(Exception ex,
org.springframework.web.context.request.WebRequest req){
var body = new ApiError(Instant.now(), 500, "Internal Server Error",
ex.getMessage(), getPath(req), null);
return
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body);
}

private String getPath(org.springframework.web.context.request.WebRequest


req){
return
Optional.ofNullable(req.getDescription(false)).orElse("").replace("uri=",""
);
}
}
Codes HTTP utilisés

 200 OK : lecture / update réussi


 201 Created + header Location : création
 204 No Content : suppression
 400 Bad Request : validation
 404 Not Found : ressource absente
 409 Conflict : contrainte métier (SKU dupliqué)
 500 : erreur non gérée

6) Démonstration API (cURL/Postman)


(10–15 min)
Créer :

bash
CopierModifier
curl -X POST http://localhost:8080/api/products \
-H "Content-Type: application/json" \
-d '{"name":"Laptop","sku":"LP-001","price":999.90,"description":"14-
inch"}'
# -> 201 Created, Location: /api/products/1

Lister (pagination tri):

bash
CopierModifier
curl "http://localhost:8080/api/products?page=0&size=5&sort=price,desc"

Lire :

bash
CopierModifier
curl http://localhost:8080/api/products/1

Mettre à jour :

bash
CopierModifier
curl -X PUT http://localhost:8080/api/products/1 \
-H "Content-Type: application/json" \
-d '{"name":"Laptop Pro","sku":"LP-
001","price":1099.90,"description":"16-inch"}'

Supprimer :

bash
CopierModifier
curl -X DELETE http://localhost:8080/api/products/1 -i
Erreurs typiques :

bash
CopierModifier
# Validation: price négatif
curl -X POST http://localhost:8080/api/products \
-H "Content-Type: application/json" \
-d '{"name":"X","sku":"BAD","price":-5,"description":"oops"}'
# -> 400 + détails des champs invalides

# Conflit: SKU déjà pris


# -> 409 Conflict

7) Bonus utiles (10–15 min)


 Swagger/OpenAPI : ouvrir http://localhost:8080/swagger-ui/index.html
 H2 Console : http://localhost:8080/h2-console (JDBC URL:
jdbc:h2:mem:demo)
 Tri & pagination Spring Data déjà gérés par Pageable
 Idempotence : utilisez PUT pour remplacer la ressource entière, PATCH (si ajouté)
pour partiel
 Validation : toutes les entrées client passent par @Valid → erreurs structurées

Découpage temporel proposé (≈2h)


 0–10 min : Dépendances + config
 10–30 min : Entity + Repository
 30–40 min : DTO/Mapper
 40–60 min : Service (règles, exceptions)
 60–90 min : Controller + endpoints
 90–105 min : Gestion globale des erreurs
 105–120 min : Tests via cURL/Postman + Swagger

Si tu veux, je peux te fournir un ZIP de projet prêt à ouvrir dans IntelliJ avec tout ce code
déjà en place (build Maven, endpoints, Swagger).

Vous aimerez peut-être aussi