From 914a58feb9e8e3d211a8b027f7d7ce1cb8346a2e Mon Sep 17 00:00:00 2001 From: Andrei Punko Date: Wed, 27 Dec 2023 18:45:57 +0300 Subject: [PATCH] Replace Page<> with Slice<> for pagination operations According to: https://www.youtube.com/watch?v=wi6h9ox1wwM --- .../controllers/ArticleController.java | 9 +-- .../persistence/dao/ArticleRepository.java | 7 +- .../persistence/dao/LoggingRepository.java | 3 +- .../by/andd3dfx/services/IArticleService.java | 4 +- .../services/impl/ArticleService.java | 9 ++- .../andd3dfx/services/impl/AuthorService.java | 2 +- .../services/impl/LoggingService.java | 4 +- .../dao/ArticleRepositoryCustomImplTest.java | 3 +- .../dao/ArticleRepositoryTest.java | 66 +++++++++++++++++++ .../andd3dfx/services/ArticleServiceTest.java | 9 ++- .../java/by/andd3dfx/util/MemoryAppender.java | 19 +++--- 11 files changed, 99 insertions(+), 36 deletions(-) create mode 100644 src/test/java/by/andd3dfx/persistence/dao/ArticleRepositoryTest.java diff --git a/src/main/java/by/andd3dfx/controllers/ArticleController.java b/src/main/java/by/andd3dfx/controllers/ArticleController.java index 91e2249..db21c1f 100644 --- a/src/main/java/by/andd3dfx/controllers/ArticleController.java +++ b/src/main/java/by/andd3dfx/controllers/ArticleController.java @@ -9,12 +9,10 @@ import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; -import java.util.List; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; import org.springframework.data.web.SortDefault; @@ -27,7 +25,6 @@ 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.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -102,7 +99,7 @@ public void deleteArticle( articleService.delete(id); } - @ApiOperation(value = "Read articles paged", response = Page.class) + @ApiOperation(value = "Read articles paged", response = Slice.class) @ApiResponses(value = { @ApiResponse(code = 200, message = "Articles successfully retrieved"), @ApiResponse(code = 401, message = "You are not authorized to view the resource") @@ -120,7 +117,7 @@ public void deleteArticle( "Multiple sort criteria are supported.", defaultValue = "title,ASC") }) - public Page readArticlesPaged( + public Slice readArticlesPaged( @PageableDefault(page = 0, size = 50) @SortDefault.SortDefaults({ @SortDefault(sort = "title", direction = Sort.Direction.ASC) diff --git a/src/main/java/by/andd3dfx/persistence/dao/ArticleRepository.java b/src/main/java/by/andd3dfx/persistence/dao/ArticleRepository.java index 5df85f5..bd23457 100644 --- a/src/main/java/by/andd3dfx/persistence/dao/ArticleRepository.java +++ b/src/main/java/by/andd3dfx/persistence/dao/ArticleRepository.java @@ -1,12 +1,13 @@ package by.andd3dfx.persistence.dao; import by.andd3dfx.persistence.entities.Article; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.stereotype.Repository; @Repository -public interface ArticleRepository extends PagingAndSortingRepository, - CrudRepository, ArticleRepositoryCustom { +public interface ArticleRepository extends CrudRepository, ArticleRepositoryCustom { + Slice
findAll(Pageable pageable); } diff --git a/src/main/java/by/andd3dfx/persistence/dao/LoggingRepository.java b/src/main/java/by/andd3dfx/persistence/dao/LoggingRepository.java index 22a15fe..69e25d3 100644 --- a/src/main/java/by/andd3dfx/persistence/dao/LoggingRepository.java +++ b/src/main/java/by/andd3dfx/persistence/dao/LoggingRepository.java @@ -2,11 +2,10 @@ import by.andd3dfx.persistence.entities.LoggedRecord; import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.stereotype.Repository; @Repository -public interface LoggingRepository extends PagingAndSortingRepository, +public interface LoggingRepository extends CrudRepository, LoggingRepositoryCustom { } diff --git a/src/main/java/by/andd3dfx/services/IArticleService.java b/src/main/java/by/andd3dfx/services/IArticleService.java index 1124170..eb6d8ca 100644 --- a/src/main/java/by/andd3dfx/services/IArticleService.java +++ b/src/main/java/by/andd3dfx/services/IArticleService.java @@ -3,8 +3,8 @@ import by.andd3dfx.dto.ArticleDto; import by.andd3dfx.dto.ArticleUpdateDto; import java.util.List; -import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; public interface IArticleService { @@ -18,5 +18,5 @@ public interface IArticleService { List getAll(Integer pageNo, Integer pageSize, String sortBy); - Page getAll(Pageable pageable); + Slice getAll(Pageable pageable); } diff --git a/src/main/java/by/andd3dfx/services/impl/ArticleService.java b/src/main/java/by/andd3dfx/services/impl/ArticleService.java index a670e12..be66d3d 100644 --- a/src/main/java/by/andd3dfx/services/impl/ArticleService.java +++ b/src/main/java/by/andd3dfx/services/impl/ArticleService.java @@ -9,10 +9,9 @@ import by.andd3dfx.persistence.entities.Article; import by.andd3dfx.services.IArticleService; import lombok.RequiredArgsConstructor; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -69,14 +68,14 @@ public void delete(Long id) { @Override public List getAll(Integer pageNo, Integer pageSize, String sortBy) { Pageable pageRequest = PageRequest.of(pageNo, pageSize, Sort.by(sortBy)); - Page
pagedResult = articleRepository.findAll(pageRequest); + Slice
pagedResult = articleRepository.findAll(pageRequest); return articleMapper.toArticleDtoList(pagedResult.getContent()); } @Transactional(readOnly = true) @Override - public Page getAll(Pageable pageable) { - final Page
pagedResult = articleRepository.findAll(pageable); + public Slice getAll(Pageable pageable) { + Slice
pagedResult = articleRepository.findAll(pageable); return pagedResult.map(articleMapper::toArticleDto); } } diff --git a/src/main/java/by/andd3dfx/services/impl/AuthorService.java b/src/main/java/by/andd3dfx/services/impl/AuthorService.java index abf236a..fd715c3 100644 --- a/src/main/java/by/andd3dfx/services/impl/AuthorService.java +++ b/src/main/java/by/andd3dfx/services/impl/AuthorService.java @@ -32,6 +32,6 @@ public AuthorDto get(Long id) { public List getAll() { return StreamSupport.stream(authorRepository.findAll().spliterator(), false) .map(authorMapper::toAuthorDto) - .collect(Collectors.toList()); + .toList(); } } diff --git a/src/main/java/by/andd3dfx/services/impl/LoggingService.java b/src/main/java/by/andd3dfx/services/impl/LoggingService.java index 2012ab2..8ab01b9 100644 --- a/src/main/java/by/andd3dfx/services/impl/LoggingService.java +++ b/src/main/java/by/andd3dfx/services/impl/LoggingService.java @@ -45,7 +45,9 @@ public void logMethodCall(Method method, Object[] args, Object result, boolean i } private MethodCallRecord buildMethodCallRecord(Method method, Object[] args, Object result, boolean isSucceed) { - List paramNames = Arrays.stream(method.getParameters()).map(Parameter::getName).collect(Collectors.toList()); + List paramNames = Arrays.stream(method.getParameters()) + .map(Parameter::getName) + .toList(); if (paramNames.size() != args.length) { throw new IllegalArgumentException("Parameter names amount differ from args amount!"); } diff --git a/src/test/java/by/andd3dfx/persistence/dao/ArticleRepositoryCustomImplTest.java b/src/test/java/by/andd3dfx/persistence/dao/ArticleRepositoryCustomImplTest.java index 12d8bc7..3461626 100644 --- a/src/test/java/by/andd3dfx/persistence/dao/ArticleRepositoryCustomImplTest.java +++ b/src/test/java/by/andd3dfx/persistence/dao/ArticleRepositoryCustomImplTest.java @@ -18,6 +18,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.domain.Pageable; @ExtendWith(MockitoExtension.class) @DataJpaTest @@ -153,7 +154,7 @@ public void deleteByCriteriaForEmptyCriteria() { assertTrue(repository.findByCriteria(new ArticleSearchCriteria()).isEmpty()); } - private Article buildArticle(String title, String summary, LocalDateTime timestamp) { + public static Article buildArticle(String title, String summary, LocalDateTime timestamp) { Article article = new Article(); article.setTitle(title); article.setSummary(summary); diff --git a/src/test/java/by/andd3dfx/persistence/dao/ArticleRepositoryTest.java b/src/test/java/by/andd3dfx/persistence/dao/ArticleRepositoryTest.java new file mode 100644 index 0000000..debbab3 --- /dev/null +++ b/src/test/java/by/andd3dfx/persistence/dao/ArticleRepositoryTest.java @@ -0,0 +1,66 @@ +package by.andd3dfx.persistence.dao; + +import by.andd3dfx.persistence.entities.Article; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +import static by.andd3dfx.persistence.dao.ArticleRepositoryCustomImplTest.buildArticle; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(MockitoExtension.class) +@DataJpaTest +class ArticleRepositoryTest { + + @Autowired + private ArticleRepository repository; + + private Article entity; + private Article entity2; + private Article entity3; + + @BeforeEach + public void setup() { + repository.deleteAll(); + entity = buildArticle("Ivan", "HD", LocalDateTime.parse("2010-12-03T10:15:30")); + entity2 = buildArticle("Vasily", "HD", LocalDateTime.parse("2011-12-03T10:15:30")); + entity3 = buildArticle("Ivan", "4K", LocalDateTime.parse("2012-12-03T10:15:30")); + repository.saveAll(Arrays.asList(entity, entity2, entity3)); + } + + @AfterEach + public void tearDown() { + repository.deleteAll(); + } + + @Test + public void findAll() { + var result = repository.findAll(Pageable.ofSize(10)); + + assertThat("Wrong records amount", result.getNumberOfElements(), is(3)); + assertTrue(result.getContent().containsAll(List.of(entity, entity2, entity3))); + } + + @Test + public void findAll_withPageNSizeNSorting() { + var result = repository.findAll(PageRequest.of(0, 2, Sort.by("title", "summary"))); + + assertThat("Wrong records amount", result.getNumberOfElements(), is(2)); + var articles = result.getContent(); + assertThat(articles.get(0), is(entity3)); + assertThat(articles.get(1), is(entity)); + } +} diff --git a/src/test/java/by/andd3dfx/services/ArticleServiceTest.java b/src/test/java/by/andd3dfx/services/ArticleServiceTest.java index 5866708..f10e108 100644 --- a/src/test/java/by/andd3dfx/services/ArticleServiceTest.java +++ b/src/test/java/by/andd3dfx/services/ArticleServiceTest.java @@ -25,11 +25,10 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; @ExtendWith(MockitoExtension.class) @@ -176,7 +175,7 @@ void getAll() { final Pageable pageRequest = PageRequest.of(pageNo, pageSize, Sort.by(sortBy)); final List
articles = Arrays.asList(new Article()); - final Page
pagedResult = new PageImpl<>(articles, pageRequest, articles.size()); + final Slice
pagedResult = new PageImpl<>(articles, pageRequest, articles.size()); final List articleDtoList = Arrays.asList(new ArticleDto()); Mockito.doReturn(pagedResult).when(articleRepositoryMock).findAll(pageRequest); @@ -197,13 +196,13 @@ void getAllPaged() { final Pageable pageRequest = PageRequest.of(pageNo, pageSize, Sort.by(sortBy)); final List
articles = Arrays.asList(new Article()); - final Page
pagedResult = new PageImpl<>(articles, pageRequest, articles.size()); + final Slice
pagedResult = new PageImpl<>(articles, pageRequest, articles.size()); final List articleDtoList = Arrays.asList(new ArticleDto()); Mockito.doReturn(pagedResult).when(articleRepositoryMock).findAll(pageRequest); Mockito.doReturn(articleDtoList.get(0)).when(articleMapperMock).toArticleDto(articles.get(0)); - Page result = articleService.getAll(pageRequest); + Slice result = articleService.getAll(pageRequest); Mockito.verify(articleRepositoryMock).findAll(pageRequest); Mockito.verify(articleMapperMock).toArticleDto(articles.get(0)); diff --git a/src/test/java/by/andd3dfx/util/MemoryAppender.java b/src/test/java/by/andd3dfx/util/MemoryAppender.java index e6140fe..fe01d83 100644 --- a/src/test/java/by/andd3dfx/util/MemoryAppender.java +++ b/src/test/java/by/andd3dfx/util/MemoryAppender.java @@ -5,46 +5,45 @@ import ch.qos.logback.core.read.ListAppender; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; /** * Class for testing purposes to catch logs. */ public class MemoryAppender extends ListAppender { public void reset() { - this.list.clear(); + list.clear(); } public boolean contains(String string, Level level) { - return this.list.stream() + return list.stream() .anyMatch(event -> event.getMessage().toString().contains(string) && event.getLevel().equals(level)); } public int countEventsForLogger(String loggerName) { - return (int) this.list.stream() + return (int) list.stream() .filter(event -> event.getLoggerName().contains(loggerName)) .count(); } public List search(String string) { - return this.list.stream() + return list.stream() .filter(event -> event.getMessage().toString().contains(string)) - .collect(Collectors.toList()); + .toList(); } public List search(String string, Level level) { - return this.list.stream() + return list.stream() .filter(event -> event.getMessage().toString().contains(string) && event.getLevel().equals(level)) - .collect(Collectors.toList()); + .toList(); } public int getSize() { - return this.list.size(); + return list.size(); } public List getLoggedEvents() { - return Collections.unmodifiableList(this.list); + return Collections.unmodifiableList(list); } }