diff --git a/boot-ultimate-redis/src/test/java/com/example/ultimateredis/controller/RedisControllerTest.java b/boot-ultimate-redis/src/test/java/com/example/ultimateredis/controller/RedisControllerTest.java index da5d7bebe..138a3e789 100644 --- a/boot-ultimate-redis/src/test/java/com/example/ultimateredis/controller/RedisControllerTest.java +++ b/boot-ultimate-redis/src/test/java/com/example/ultimateredis/controller/RedisControllerTest.java @@ -28,6 +28,7 @@ void addRedisKeyValue() throws Exception { .content(objectMapper.writeValueAsString(addRedisRequest)) .assertThat() .hasStatus(HttpStatus.CREATED) + .hasContentType(MediaType.APPLICATION_JSON) .bodyJson() .convertTo(GenericResponse.class) .satisfies(response -> assertThat(response.response()).isEqualTo(true)); @@ -42,6 +43,7 @@ void getFromCache() { .param("key", "junit") .assertThat() .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) .bodyJson() .convertTo(GenericResponse.class) .satisfies(response -> assertThat(response.response()).isEqualTo("JunitValue")); @@ -61,6 +63,7 @@ void expireFromCache() { .param("key", "junit") .assertThat() .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) .bodyJson() .convertTo(GenericResponse.class) .satisfies( diff --git a/jpa/boot-read-replica-postgresql/pom.xml b/jpa/boot-read-replica-postgresql/pom.xml index 97fd825bb..3fd43f429 100644 --- a/jpa/boot-read-replica-postgresql/pom.xml +++ b/jpa/boot-read-replica-postgresql/pom.xml @@ -9,9 +9,9 @@ com.example.demo - boot-jpa-read-replica-postgresql + boot-read-replica-postgresql 0.0.1-SNAPSHOT - boot-jpa-read-replica-postgresql + boot-read-replica-postgresql Demo project for Spring Boot Read Replica @@ -109,7 +109,7 @@ - 1.24.0 + 1.25.2 diff --git a/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/controller/ArticleController.java b/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/controller/ArticleController.java index 24e44e806..60a7700a5 100644 --- a/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/controller/ArticleController.java +++ b/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/controller/ArticleController.java @@ -4,12 +4,14 @@ import com.example.demo.readreplica.service.ArticleService; import java.net.URI; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; @RestController @RequestMapping("/articles") @@ -22,7 +24,7 @@ class ArticleController { } @GetMapping("/{id}") - ResponseEntity findArticleById(@PathVariable Integer id) { + ResponseEntity findArticleById(@PathVariable Long id) { return this.articleService .findArticleById(id) .map(ResponseEntity::ok) @@ -32,6 +34,23 @@ ResponseEntity findArticleById(@PathVariable Integer id) { @PostMapping("/") ResponseEntity saveArticle(@RequestBody ArticleDTO articleDTO) { Long articleId = this.articleService.saveArticle(articleDTO); - return ResponseEntity.created(URI.create("/articles/" + articleId)).build(); + URI location = + ServletUriComponentsBuilder.fromCurrentRequest() + .path("{id}") + .buildAndExpand(articleId) + .toUri(); + return ResponseEntity.created(location).build(); + } + + @DeleteMapping("/{id}") + ResponseEntity deleteArticle(@PathVariable Long id) { + return this.articleService + .findById(id) + .map( + article -> { + articleService.deleteById(article.getId()); + return ResponseEntity.accepted().build(); + }) + .orElseGet(() -> ResponseEntity.notFound().build()); } } diff --git a/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/domain/ArticleDTO.java b/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/domain/ArticleDTO.java index 73796a871..4c480bf5f 100644 --- a/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/domain/ArticleDTO.java +++ b/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/domain/ArticleDTO.java @@ -1,5 +1,9 @@ package com.example.demo.readreplica.domain; +import static com.example.demo.readreplica.domain.CommentDTO.convertToComment; + +import com.example.demo.readreplica.entities.Article; +import com.example.demo.readreplica.entities.Comment; import java.time.LocalDateTime; import java.util.List; @@ -7,4 +11,16 @@ public record ArticleDTO( String title, LocalDateTime authored, LocalDateTime published, - List commentDTOs) {} + List commentDTOs) { + + public Article convertToArticle() { + Article article = + new Article().setAuthored(authored).setTitle(title).setPublished(published); + commentDTOs.forEach( + commentDTO -> { + Comment comment = convertToComment(commentDTO); + article.addComment(comment); + }); + return article; + } +} diff --git a/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/domain/CommentDTO.java b/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/domain/CommentDTO.java index 5a34dccc5..e55001748 100644 --- a/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/domain/CommentDTO.java +++ b/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/domain/CommentDTO.java @@ -1,3 +1,10 @@ package com.example.demo.readreplica.domain; -public record CommentDTO(String comment) {} +import com.example.demo.readreplica.entities.Comment; + +public record CommentDTO(String comment) { + + static Comment convertToComment(CommentDTO commentDTO) { + return new Comment().setComment(commentDTO.comment()); + } +} diff --git a/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/repository/ArticleRepository.java b/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/repository/ArticleRepository.java index 12e384944..0b735b2df 100644 --- a/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/repository/ArticleRepository.java +++ b/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/repository/ArticleRepository.java @@ -11,5 +11,5 @@ public interface ArticleRepository extends JpaRepository { @Transactional(readOnly = true) @Query("select a from Article a left join fetch a.comments where a.id = :articleId ") - Optional
findByArticleId(@Param("articleId") Integer articleId); + Optional
findByArticleId(@Param("articleId") Long articleId); } diff --git a/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/service/ArticleService.java b/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/service/ArticleService.java index 9665ed99e..fa4886afe 100644 --- a/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/service/ArticleService.java +++ b/jpa/boot-read-replica-postgresql/src/main/java/com/example/demo/readreplica/service/ArticleService.java @@ -3,14 +3,13 @@ import com.example.demo.readreplica.domain.ArticleDTO; import com.example.demo.readreplica.domain.CommentDTO; import com.example.demo.readreplica.entities.Article; -import com.example.demo.readreplica.entities.Comment; import com.example.demo.readreplica.repository.ArticleRepository; -import java.util.List; import java.util.Optional; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service +@Transactional(readOnly = true) public class ArticleService { private final ArticleRepository articleRepository; @@ -19,17 +18,26 @@ public class ArticleService { this.articleRepository = articleRepository; } - public Optional findArticleById(Integer id) { + public Optional findArticleById(Long id) { return this.articleRepository.findByArticleId(id).map(this::convertToArticleDTO); } @Transactional public Long saveArticle(ArticleDTO articleDTO) { - Article article = convertToArticle(articleDTO); + Article article = articleDTO.convertToArticle(); Article savedArticle = this.articleRepository.save(article); return savedArticle.getId(); } + public Optional
findById(Long id) { + return articleRepository.findById(id); + } + + @Transactional + public void deleteById(Long id) { + articleRepository.deleteById(id); + } + private ArticleDTO convertToArticleDTO(Article articleEntity) { return new ArticleDTO( articleEntity.getTitle(), @@ -39,24 +47,4 @@ private ArticleDTO convertToArticleDTO(Article articleEntity) { .map(comment -> new CommentDTO(comment.getComment())) .toList()); } - - private Article convertToArticle(ArticleDTO articleDTO) { - Article article = new Article(); - article.setAuthored(articleDTO.authored()); - article.setTitle(articleDTO.title()); - article.setPublished(articleDTO.published()); - convertToComment(articleDTO.commentDTOs()).forEach(article::addComment); - return article; - } - - private List convertToComment(List commentDTOs) { - return commentDTOs.stream() - .map( - commentDTO -> { - Comment comment = new Comment(); - comment.setComment(commentDTO.comment()); - return comment; - }) - .toList(); - } } diff --git a/jpa/boot-read-replica-postgresql/src/test/java/com/example/demo/readreplica/controller/ArticleControllerIntTest.java b/jpa/boot-read-replica-postgresql/src/test/java/com/example/demo/readreplica/controller/ArticleControllerIntTest.java new file mode 100644 index 000000000..0b1fcb806 --- /dev/null +++ b/jpa/boot-read-replica-postgresql/src/test/java/com/example/demo/readreplica/controller/ArticleControllerIntTest.java @@ -0,0 +1,104 @@ +package com.example.demo.readreplica.controller; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.example.demo.readreplica.domain.ArticleDTO; +import com.example.demo.readreplica.domain.CommentDTO; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +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.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.assertj.MockMvcTester; + +@SpringBootTest +@AutoConfigureMockMvc +class ArticleControllerIntTest { + + @Autowired private MockMvcTester mvcTester; + + @Autowired private ObjectMapper objectMapper; + + @Test + void findArticleById() { + + mvcTester + .get() + .uri("/articles/1") + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(ArticleDTO.class) + .satisfies( + articleDTO -> { + assertThat(articleDTO.title()) + .isNotNull() + .isEqualTo("Waiter! There is a bug in my JSoup!"); + assertThat(articleDTO.authored()) + .isNotNull() + .isInstanceOf(LocalDateTime.class); + assertThat(articleDTO.published()) + .isNotNull() + .isInstanceOf(LocalDateTime.class); + assertThat(articleDTO.commentDTOs()) + .isNotNull() + .hasSize(2) + .hasOnlyElementsOfType(CommentDTO.class); + }); + } + + @Test + void saveArticleRetriveAndDelete() throws JsonProcessingException { + ArticleDTO articleDTO = + new ArticleDTO( + "junitTitle", + LocalDateTime.now().minusDays(1), + LocalDateTime.now(), + List.of(new CommentDTO("junitComment"))); + AtomicReference location = new AtomicReference<>(); + mvcTester + .post() + .uri("/articles/") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(articleDTO)) + .assertThat() + .hasStatus(HttpStatus.CREATED) + .matches( + result -> { + location.set(result.getResponse().getHeader("Location")); + assertThat(location.get()).isNotBlank().contains("/articles/"); + }); + + mvcTester + .get() + .uri(location.get()) + .assertThat() + .hasStatusOk() + .hasContentType(MediaType.APPLICATION_JSON) + .bodyJson() + .convertTo(ArticleDTO.class) + .satisfies( + response -> { + assertThat(response.title()).isNotNull().isEqualTo("junitTitle"); + assertThat(response.authored()) + .isNotNull() + .isInstanceOf(LocalDateTime.class); + assertThat(response.published()) + .isNotNull() + .isInstanceOf(LocalDateTime.class); + assertThat(response.commentDTOs()) + .isNotNull() + .hasSize(1) + .hasOnlyElementsOfType(CommentDTO.class); + }); + + mvcTester.delete().uri(location.get()).assertThat().hasStatus(HttpStatus.ACCEPTED); + } +}