From b2c7c0ed4ac81994e28c1d6a28321741c6983276 Mon Sep 17 00:00:00 2001 From: Ralf Ueberfuhr Date: Wed, 11 Feb 2026 07:05:22 +0100 Subject: [PATCH] add H2 database and Hibernate ORM Panache to persist recipes --- .gitignore | 3 +++ pom.xml | 9 ++++++++ .../schulung/quarkus/recipes/Ingredient.java | 13 ++++++++++- .../de/schulung/quarkus/recipes/Recipe.java | 9 ++++++++ .../quarkus/recipes/RecipesRepository.java | 9 ++++++++ .../quarkus/recipes/RecipesService.java | 22 +++++++++---------- src/main/resources/application.properties | 3 +++ .../quarkus/recipes/RecipesResourceTests.java | 2 ++ .../quarkus/recipes/RecipesServiceTests.java | 2 ++ 9 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 src/main/java/de/schulung/quarkus/recipes/RecipesRepository.java diff --git a/.gitignore b/.gitignore index 91a800a..610fce9 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,9 @@ nb-configuration.xml # Local environment .env +# Database +.local-db + # Plugin directory /.quarkus/cli/plugins/ # TLS Certificates diff --git a/pom.xml b/pom.xml index 8a3917b..bea9076 100644 --- a/pom.xml +++ b/pom.xml @@ -55,6 +55,15 @@ io.quarkus quarkus-hibernate-validator + + io.quarkus + quarkus-jdbc-h2 + runtime + + + io.quarkus + quarkus-hibernate-orm-panache + io.quarkus quarkus-junit-mockito diff --git a/src/main/java/de/schulung/quarkus/recipes/Ingredient.java b/src/main/java/de/schulung/quarkus/recipes/Ingredient.java index 27c6189..4ddc77e 100644 --- a/src/main/java/de/schulung/quarkus/recipes/Ingredient.java +++ b/src/main/java/de/schulung/quarkus/recipes/Ingredient.java @@ -1,16 +1,26 @@ package de.schulung.quarkus.recipes; +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.Size; - import lombok.Getter; import lombok.Setter; +@Entity(name = "Ingredient") +@Table(name = "ingredients") @Getter @Setter public class Ingredient { + /** + * The ingredient ID (internal, not exposed via JSON). + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @JsonIgnore + private Long id; /** * The ingredient name. */ @@ -26,6 +36,7 @@ public class Ingredient { * The unit of the ingredient. */ @NotNull + @Enumerated(EnumType.STRING) private IngredientUnit unit; } diff --git a/src/main/java/de/schulung/quarkus/recipes/Recipe.java b/src/main/java/de/schulung/quarkus/recipes/Recipe.java index e016622..0fe5c74 100644 --- a/src/main/java/de/schulung/quarkus/recipes/Recipe.java +++ b/src/main/java/de/schulung/quarkus/recipes/Recipe.java @@ -1,6 +1,7 @@ package de.schulung.quarkus.recipes; import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.persistence.*; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; @@ -11,6 +12,8 @@ import java.time.OffsetDateTime; import java.util.List; +@Entity(name = "Recipe") +@Table(name = "recipes") @Getter @Setter public class Recipe { @@ -18,6 +21,8 @@ public class Recipe { /** * The recipe ID (read-only, assigned by the server). */ + @Id + @GeneratedValue(strategy = GenerationType.UUID) @JsonProperty(access = JsonProperty.Access.READ_ONLY) private String id; /** @@ -49,18 +54,22 @@ public class Recipe { /** * Difficulty level of the recipe. */ + @Enumerated(EnumType.STRING) private Difficulty difficulty; /** * List of ingredients. */ @NotNull @Size(max = 100) + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "recipe_id") private List<@Valid @NotNull Ingredient> ingredients; /** * Preparation instructions. */ @NotNull @Size(max = 2000) + @Column(length = 2000) private String preparation; } diff --git a/src/main/java/de/schulung/quarkus/recipes/RecipesRepository.java b/src/main/java/de/schulung/quarkus/recipes/RecipesRepository.java new file mode 100644 index 0000000..09a084a --- /dev/null +++ b/src/main/java/de/schulung/quarkus/recipes/RecipesRepository.java @@ -0,0 +1,9 @@ +package de.schulung.quarkus.recipes; + +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class RecipesRepository + implements PanacheRepositoryBase { +} diff --git a/src/main/java/de/schulung/quarkus/recipes/RecipesService.java b/src/main/java/de/schulung/quarkus/recipes/RecipesService.java index fb2a3a6..9d34ae5 100644 --- a/src/main/java/de/schulung/quarkus/recipes/RecipesService.java +++ b/src/main/java/de/schulung/quarkus/recipes/RecipesService.java @@ -1,38 +1,36 @@ package de.schulung.quarkus.recipes; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.transaction.Transactional; import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import java.time.OffsetDateTime; -import java.util.Map; import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; @ApplicationScoped +@RequiredArgsConstructor public class RecipesService { - private final Map recipes = new ConcurrentHashMap<>(); + private final RecipesRepository repository; public Stream findAll() { - return recipes - .values() + return repository + .listAll() .stream(); } public Optional findById(String id) { - return Optional - .ofNullable(recipes.get(id)); + return repository + .findByIdOptional(id); } + @Transactional public void create( @Valid Recipe recipe ) { - - final var id = UUID.randomUUID().toString(); - recipe.setId(id); recipe.setLastEdited(OffsetDateTime.now()); if (null == recipe.getImg()) { @@ -43,6 +41,6 @@ public void create( recipe.setDifficulty(Difficulty.medium); } - recipes.put(id, recipe); + repository.persist(recipe); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e701ad4..3677084 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,4 @@ quarkus.jackson.fail-on-unknown-properties=true +quarkus.datasource.jdbc.url=jdbc:h2:./.local-db/customers +quarkus.hibernate-orm.schema-management.strategy=update +quarkus.hibernate-orm.log.sql=true diff --git a/src/test/java/de/schulung/quarkus/recipes/RecipesResourceTests.java b/src/test/java/de/schulung/quarkus/recipes/RecipesResourceTests.java index b75a3f6..0ca83d0 100644 --- a/src/test/java/de/schulung/quarkus/recipes/RecipesResourceTests.java +++ b/src/test/java/de/schulung/quarkus/recipes/RecipesResourceTests.java @@ -1,5 +1,6 @@ package de.schulung.quarkus.recipes; +import io.quarkus.test.TestTransaction; import io.quarkus.test.junit.QuarkusTest; import io.restassured.http.ContentType; import org.junit.jupiter.api.Test; @@ -15,6 +16,7 @@ import static org.hamcrest.Matchers.*; @QuarkusTest +@TestTransaction class RecipesResourceTests { // --- Content Negotiation --- diff --git a/src/test/java/de/schulung/quarkus/recipes/RecipesServiceTests.java b/src/test/java/de/schulung/quarkus/recipes/RecipesServiceTests.java index f897add..ba92625 100644 --- a/src/test/java/de/schulung/quarkus/recipes/RecipesServiceTests.java +++ b/src/test/java/de/schulung/quarkus/recipes/RecipesServiceTests.java @@ -1,5 +1,6 @@ package de.schulung.quarkus.recipes; +import io.quarkus.test.TestTransaction; import io.quarkus.test.junit.QuarkusTest; import jakarta.inject.Inject; import org.junit.jupiter.api.Test; @@ -7,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; @QuarkusTest +@TestTransaction public class RecipesServiceTests { @Inject