Séance 2 (4h)
Contexte : fil rouge e-commerce
À réaliser :
Order ↔ OrderItem (bidirectionnel)Order → User (unidirectionnel)User ↔ Category (preferences, Many-to-Many)@JsonIgnore / @JsonManagedReference pour éviter les bouclesPoints d'attention :
mappedBy)
// Contraintes métier à implémenter - Un Order doit toujours avoir au moins 1 OrderItem - Suppression d'un Order → suppression des OrderItems - totalAmount calculé automatiquement - Gestion du stock produit lors de la création
Tests attendus :
Pour recréer à moindre coût une relation (sans charger complètement l'instance depuis le repository)
val user = entityManager.getReference(User::class.java, userId);
Scénario :
GET /users/{id}/orders
// Retourne les commandes avec leurs items et produits
Mission :
spring.jpa.show-sql=true)À implémenter et comparer :
| Solution | Cas d'usage | Avantages | Inconvénients |
|---|---|---|---|
@EntityGraph | Requêtes standards | Simple | Moins flexible |
JOIN FETCH | Requêtes complexes | Contrôle total | Code JPQL |
@BatchSize | Lazy loading | Transparent | Moins optimal |
| DTO Projection | Lecture seule | Performances max | Plus de code |
Exercices :
/users/{id}/orders avec JOIN FETCH/products (liste)Nouveau besoin métier :
Différencier 3 types de produits :
Tous partagent : id, name, price, stock, category
À explorer (au choix ou comparaison) :
// Option 1 : SINGLE_TABLE (par défaut) @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "product_type") // Option 2 : JOINED @Inheritance(strategy = InheritanceType.JOINED) // Option 3 : TABLE_PER_CLASS @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
Exercice comparatif :
// Repository List<Product> findAll(); // Tous types confondus List<PhysicalProduct> findPhysicalProducts(); // Nouveaux endpoints GET /products?type=PHYSICAL GET /products?type=DIGITAL
Hypersistence Utils est une bibliothèque créée par Vlad Mihalcea qui apporte :
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-63</artifactId>
<version>3.7.0</version>
</dependency>
Objectif : Détecter automatiquement les problèmes de performance sans analyse manuelle des logs
# application.properties - Ajout pour Hypersistence # Détection des problèmes N+1 logging.level.io.hypersistence.utils=DEBUG # Limites d'alerte (optionnel) hypersistence.query.fail.on.pagination.over.collection.fetch=false
// Configuration globale (classe @Configuration)
@Configuration
public class HypersistenceConfiguration {
@Bean
public QueryStackTraceLogger queryStackTraceLogger() {
return new QueryStackTraceLogger();
}
@EventListener
public void onApplicationEvent(ApplicationReadyEvent event) {
// Active la détection des problèmes N+1
QueryStackTraceLogger.INSTANCE.setThreshold(10); // Alerte si > 10 requêtes
}
}
Exercice :
/users/{id}/ordersCas d'usage : Stocker des métadonnées flexibles sur les produits
@Entity
@Table(name = "products")
public class Product {
// ... attributs existants
@Type(JsonType.class)
@Column(columnDefinition = "json")
private Map<String, Object> attributes;
// Pour PhysicalProduct : {"weight": 2.5, "dimensions": "30x20x10"}
// Pour DigitalProduct : {"fileSize": "1.2GB", "format": "PDF"}
}
{
"id": "550e8400-e29b-41d4-a716-446655440020",
"name": "iPhone 15 Pro",
"price": 1199.99,
"stock": 25,
"categoryId": "550e8400-e29b-41d4-a716-446655440010",
"attributes": {
"color": "Titanium Blue",
"storage": "256GB",
"warranty": "2 years"
}
}
Exercice :
attributes à ProductGET /products/{id}/attributesGET /products?attr.color=BlueTsid (Time-Sorted Identifiers) :
// Avant (UUID) @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; // Après (Tsid) - Pour nouvelles entités @Id @TsidGenerator private Long id;
Exercice optionnel :
Review avec Tsid
@Configuration
public class DataSourceProxyConfiguration {
@Bean
public DataSourceProxyBeanPostProcessor dataSourceProxyBeanPostProcessor() {
return new DataSourceProxyBeanPostProcessor() {
@Override
protected DataSourceProxy createDataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource, new QueryCountHolder());
}
};
}
}
Exercice :
assertQueryCount(3) après un appel APIMission : Améliorer l'endpoint recommendations
GET /users/{id}/recommendations
Avec Hypersistence :
Structure JSON recommandée :
// User.preferences (JSON)
{
"priceRange": {"min": 50, "max": 500},
"brands": ["Apple", "Samsung"],
"excludeCategories": ["550e8400-..."]
}
# application.properties - Configuration complète Séance 2 # H2 Database spring.datasource.url=jdbc:h2:file:./data/ecommerce spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= # H2 Console spring.h2.console.enabled=true spring.h2.console.path=/h2-console # JPA/Hibernate spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.use_sql_comments=true spring.jpa.properties.hibernate.generate_statistics=true # Logging SQL et statistiques logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE logging.level.org.hibernate.stat=DEBUG logging.level.org.hibernate.orm.jdbc.bind=TRACE # Hypersistence Utils logging.level.io.hypersistence.utils=DEBUG
Must have :
Nice to have :
Conseils :
@ManyToOne LAZY par défaut