diff --git a/.gitignore b/.gitignore
index 7ed0d6b..e50227d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,9 @@ target/
!**/src/main/**/target/
!**/src/test/**/target/
+# DB
+.local-db
+
### STS ###
.apt_generated
.classpath
diff --git a/pom.xml b/pom.xml
index 33f27dd..a2f8a7e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,6 +39,15 @@
spring-boot-configuration-processor
true
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ com.h2database
+ h2
+ runtime
+
org.springframework.boot
spring-boot-devtools
diff --git a/src/main/java/de/sample/schulung/spring/blog/domain/BlogPostService.java b/src/main/java/de/sample/schulung/spring/blog/domain/BlogPostService.java
index b3b055f..7dfd920 100644
--- a/src/main/java/de/sample/schulung/spring/blog/domain/BlogPostService.java
+++ b/src/main/java/de/sample/schulung/spring/blog/domain/BlogPostService.java
@@ -7,6 +7,7 @@
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
+import java.time.LocalDateTime;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;
@@ -32,6 +33,7 @@ public Optional findById(UUID id) {
}
public void create(@Valid @NotNull BlogPost blogPost) {
+ blogPost.setTimestamp(LocalDateTime.now());
this.sink.create(blogPost);
this.eventPublisher.publishEvent(new BlogPostCreatedEvent(blogPost));
}
diff --git a/src/main/java/de/sample/schulung/spring/blog/domain/InMemoryBlogPostSink.java b/src/main/java/de/sample/schulung/spring/blog/domain/InMemoryBlogPostSink.java
index 5c16961..fa8bf8f 100644
--- a/src/main/java/de/sample/schulung/spring/blog/domain/InMemoryBlogPostSink.java
+++ b/src/main/java/de/sample/schulung/spring/blog/domain/InMemoryBlogPostSink.java
@@ -1,6 +1,5 @@
package de.sample.schulung.spring.blog.domain;
-import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@@ -20,7 +19,6 @@ public long count() {
public void create(BlogPost post) {
final var id = UUID.randomUUID();
post.setId(id);
- post.setTimestamp(LocalDateTime.now());
this.blogPosts.put(id, post);
}
diff --git a/src/main/java/de/sample/schulung/spring/blog/persistence/BlogPostEntity.java b/src/main/java/de/sample/schulung/spring/blog/persistence/BlogPostEntity.java
new file mode 100644
index 0000000..84a9d5a
--- /dev/null
+++ b/src/main/java/de/sample/schulung/spring/blog/persistence/BlogPostEntity.java
@@ -0,0 +1,37 @@
+package de.sample.schulung.spring.blog.persistence;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Getter
+@Setter
+@Entity(name = "BlogPost") // Name der Entity (JPQL)
+@Table(name = "BLOG_POSTS")
+public class BlogPostEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.UUID)
+ private UUID id;
+ @Size(min = 3)
+ @NotNull
+ private String title;
+ @Size(min = 10)
+ @NotNull
+ private String content;
+ @Column(name = "TIME_STAMP")
+ private LocalDateTime timestamp;
+
+ /*
+ @PrePersist
+ public void updateTimestamp() {
+ this.timestamp = LocalDateTime.now();
+ }
+ */
+
+}
diff --git a/src/main/java/de/sample/schulung/spring/blog/persistence/BlogPostEntityMapper.java b/src/main/java/de/sample/schulung/spring/blog/persistence/BlogPostEntityMapper.java
new file mode 100644
index 0000000..80aba78
--- /dev/null
+++ b/src/main/java/de/sample/schulung/spring/blog/persistence/BlogPostEntityMapper.java
@@ -0,0 +1,16 @@
+package de.sample.schulung.spring.blog.persistence;
+
+import de.sample.schulung.spring.blog.domain.BlogPost;
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingTarget;
+
+@Mapper(componentModel = "spring")
+public interface BlogPostEntityMapper {
+
+ BlogPost map(BlogPostEntity source);
+
+ BlogPostEntity map(BlogPost source);
+
+ void copy(BlogPostEntity source, @MappingTarget BlogPost target);
+
+}
diff --git a/src/main/java/de/sample/schulung/spring/blog/persistence/BlogPostRepository.java b/src/main/java/de/sample/schulung/spring/blog/persistence/BlogPostRepository.java
new file mode 100644
index 0000000..35580ea
--- /dev/null
+++ b/src/main/java/de/sample/schulung/spring/blog/persistence/BlogPostRepository.java
@@ -0,0 +1,21 @@
+package de.sample.schulung.spring.blog.persistence;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.UUID;
+import java.util.stream.Stream;
+
+@Repository
+public interface BlogPostRepository
+ extends JpaRepository {
+
+ // oder @Query("...")
+ Stream streamBlogPostEntitiesByTitleContainingIgnoreCaseOrderByTimestampDesc(String title);
+
+ @Query("select p from BlogPost p where p.title = :title")
+ Stream streamIrgendwie(@Param("title") String title);
+
+}
diff --git a/src/main/java/de/sample/schulung/spring/blog/persistence/JpaBlogPostSink.java b/src/main/java/de/sample/schulung/spring/blog/persistence/JpaBlogPostSink.java
new file mode 100644
index 0000000..80a315d
--- /dev/null
+++ b/src/main/java/de/sample/schulung/spring/blog/persistence/JpaBlogPostSink.java
@@ -0,0 +1,44 @@
+package de.sample.schulung.spring.blog.persistence;
+
+import de.sample.schulung.spring.blog.domain.BlogPost;
+import de.sample.schulung.spring.blog.domain.BlogPostSink;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+@RequiredArgsConstructor
+@Component
+public class JpaBlogPostSink implements BlogPostSink {
+
+ private final BlogPostRepository repo;
+ private final BlogPostEntityMapper mapper;
+
+ @Override
+ public long count() {
+ return repo.count();
+ }
+
+ @Override
+ public void create(BlogPost post) {
+ final var entity = mapper.map(post);
+ final var savedEntity = repo.save(entity);
+ //post.setId(savedEntity.getId());
+ mapper.copy(savedEntity, post);
+ }
+
+ @Override
+ public Stream findAll() {
+ return repo.findAll()
+ .stream()
+ .map(mapper::map);
+ }
+
+ @Override
+ public Optional findById(UUID id) {
+ return repo.findById(id)
+ .map(mapper::map);
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 2b99f98..3e5e670 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -10,3 +10,19 @@ application:
cors:
allow:
origins: ${CORS_ALLOW_ORIGINS:*}
+spring:
+ datasource:
+ url: jdbc:h2:./.local-db/data
+ h2:
+ console:
+ enabled: true
+ path: /h2-console # http://localhost:9080/h2-console (leave username and password empty)
+ jpa:
+ generate-ddl: true
+ show-sql: true
+ open-in-view: false
+ hibernate:
+ ddl-auto: update
+ properties:
+ hibernate:
+ dialect: org.hibernate.dialect.H2Dialect
diff --git a/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostApiTests.java b/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostApiTests.java
index 39f0a8a..a774edb 100644
--- a/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostApiTests.java
+++ b/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostApiTests.java
@@ -2,6 +2,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpHeaders;
@@ -15,6 +16,7 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
+@AutoConfigureTestDatabase
@AutoConfigureMockMvc
@ActiveProfiles("no-initialization")
class BlogPostApiTests {
diff --git a/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostApiWithMockedServiceTests.java b/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostApiWithMockedServiceTests.java
index 651cbbc..d5f9a28 100644
--- a/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostApiWithMockedServiceTests.java
+++ b/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostApiWithMockedServiceTests.java
@@ -5,8 +5,9 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
-import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
@@ -21,7 +22,8 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-@SpringBootTest
+@WebMvcTest
+@ComponentScan(basePackageClasses = BlogPostApiWithMockedServiceTests.class)
@AutoConfigureMockMvc
class BlogPostApiWithMockedServiceTests {
diff --git a/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostControllerIntegrationTests.java b/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostControllerIntegrationTests.java
index eed06f4..848087b 100644
--- a/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostControllerIntegrationTests.java
+++ b/src/test/java/de/sample/schulung/spring/blog/boundary/BlogPostControllerIntegrationTests.java
@@ -2,6 +2,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
@@ -9,6 +10,7 @@
// SO NICHT!
@SpringBootTest
+@AutoConfigureTestDatabase
public class BlogPostControllerIntegrationTests {
@Autowired
diff --git a/src/test/java/de/sample/schulung/spring/blog/domain/BlogPostEventsTests.java b/src/test/java/de/sample/schulung/spring/blog/domain/BlogPostEventsTests.java
index 389f5f9..9228b6f 100644
--- a/src/test/java/de/sample/schulung/spring/blog/domain/BlogPostEventsTests.java
+++ b/src/test/java/de/sample/schulung/spring/blog/domain/BlogPostEventsTests.java
@@ -3,6 +3,7 @@
import jakarta.validation.ConstraintViolationException;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.event.ApplicationEvents;
@@ -12,6 +13,7 @@
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@SpringBootTest
+@AutoConfigureTestDatabase // TODO: wie ohne Datenbank?
@RecordApplicationEvents
@ActiveProfiles("test-domain")
public class BlogPostEventsTests {
diff --git a/src/test/java/de/sample/schulung/spring/blog/domain/BlogPostInitializationTests.java b/src/test/java/de/sample/schulung/spring/blog/domain/BlogPostInitializationTests.java
index 2dd065a..ce0c4c1 100644
--- a/src/test/java/de/sample/schulung/spring/blog/domain/BlogPostInitializationTests.java
+++ b/src/test/java/de/sample/schulung/spring/blog/domain/BlogPostInitializationTests.java
@@ -2,6 +2,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
@@ -13,6 +14,7 @@
* THIS WILL LEAD TO A SEPARATE CONTEXT !!!
*/
@SpringBootTest
+@AutoConfigureTestDatabase
// komplette Anwendung -> Test der Konfiguration in application.yml
@TestPropertySource(properties = {
"application.initialization.enabled=true",
diff --git a/src/test/java/de/sample/schulung/spring/blog/domain/BlogPostServiceTests.java b/src/test/java/de/sample/schulung/spring/blog/domain/BlogPostServiceTests.java
index 42059cd..f12c5b5 100644
--- a/src/test/java/de/sample/schulung/spring/blog/domain/BlogPostServiceTests.java
+++ b/src/test/java/de/sample/schulung/spring/blog/domain/BlogPostServiceTests.java
@@ -2,11 +2,13 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
+@AutoConfigureTestDatabase
// komplette Anwendung -> Test der Konfiguration in application.yml
public class BlogPostServiceTests {