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 :
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}/orders
Cas 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
à Product
GET /products/{id}/attributes
GET /products?attr.color=Blue
Tsid (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