From be84fd1b620a8ed28396063422b9e78341ea4bbe Mon Sep 17 00:00:00 2001 From: Eugene Kovko <37694937+eukovko@users.noreply.github.com> Date: Wed, 27 Dec 2023 18:48:40 +0100 Subject: [PATCH] BAEL 7300: Return Map instead of List in Spring Data JPA (#15482) * BAEL-7300: Return Map instead of List in Spring Data JPA * BAEL-7300: Change the naming for the tests * BAEL-7300: Fix formatting --- .../spring/data/jpa/querymap/AppConfig.java | 8 ++ .../spring/data/jpa/querymap/User.java | 93 ++++++++++++++ .../data/jpa/querymap/UserRepository.java | 37 ++++++ .../spring/data/jpa/querymap/Users.java | 40 ++++++ .../UserRepositoryIntegrationTest.java | 114 ++++++++++++++++++ .../src/test/resources/user-map-query-dml.sql | 50 ++++++++ 6 files changed, 342 insertions(+) create mode 100644 persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/AppConfig.java create mode 100644 persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/User.java create mode 100644 persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/UserRepository.java create mode 100644 persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/Users.java create mode 100644 persistence-modules/spring-data-jpa-query-3/src/test/java/com/baeldung/spring/data/jpa/querymap/UserRepositoryIntegrationTest.java create mode 100644 persistence-modules/spring-data-jpa-query-3/src/test/resources/user-map-query-dml.sql diff --git a/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/AppConfig.java b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/AppConfig.java new file mode 100644 index 0000000000..abcf5f3690 --- /dev/null +++ b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/AppConfig.java @@ -0,0 +1,8 @@ +package com.baeldung.spring.data.jpa.querymap; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AppConfig { + +} diff --git a/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/User.java b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/User.java new file mode 100644 index 0000000000..1b9630dafc --- /dev/null +++ b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/User.java @@ -0,0 +1,93 @@ +package com.baeldung.spring.data.jpa.querymap; + +import java.util.Objects; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "users") +public class User { + + @Id + @GeneratedValue + private Long id; + private String firstName; + private String lastName; + + public User() { + } + + public User(final String firstName, final String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public User(final Long id, final String firstName, final String lastName) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + } + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(final String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(final String lastName) { + this.lastName = lastName; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final User user = (User) o; + + if (!Objects.equals(id, user.id)) { + return false; + } + if (!Objects.equals(firstName, user.firstName)) { + return false; + } + return Objects.equals(lastName, user.lastName); + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (firstName != null ? firstName.hashCode() : 0); + result = 31 * result + (lastName != null ? lastName.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + '}'; + } +} diff --git a/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/UserRepository.java b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/UserRepository.java new file mode 100644 index 0000000000..c4b23fa678 --- /dev/null +++ b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/UserRepository.java @@ -0,0 +1,37 @@ +package com.baeldung.spring.data.jpa.querymap; + +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.transaction.Transactional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.util.Streamable; + +public interface UserRepository extends JpaRepository { + + String FIND_ALL_USERS = "select u from User u"; + + @Query(FIND_ALL_USERS) + Streamable findAllAsStreamable(); + + @Query(FIND_ALL_USERS) + Stream findAllAsStream(); + + @Query(FIND_ALL_USERS) + Users findAllUsers(); + + default Map findAllAsMapUsingCollection() { + return findAll().stream().collect(Collectors.toMap(User::getId, Function.identity())); + } + + default Map findAllAsMapUsingStreamable() { + return findAllAsStreamable().stream().collect(Collectors.toMap(User::getId, Function.identity())); + } + + @Transactional + default Map findAllAsMapUsingStream() { + return findAllAsStream().collect(Collectors.toMap(User::getId, Function.identity())); + } +} diff --git a/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/Users.java b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/Users.java new file mode 100644 index 0000000000..acf05726b0 --- /dev/null +++ b/persistence-modules/spring-data-jpa-query-3/src/main/java/com/baeldung/spring/data/jpa/querymap/Users.java @@ -0,0 +1,40 @@ +package com.baeldung.spring.data.jpa.querymap; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.springframework.data.util.Streamable; + +public class Users implements Streamable { + + private final Streamable userStreamable; + + public Users(Streamable userStreamable) { + this.userStreamable = userStreamable; + } + + @Override + public Iterator iterator() { + return userStreamable.iterator(); + } + + public Map getUserIdToUserMap() { + return stream().collect(Collectors.toMap(User::getId, Function.identity())); + } + + public List getAllUsersWithShortNames(int maxNameLength) { + return stream() + .filter(s -> s.getFirstName().length() <= maxNameLength) + .collect(Collectors.toList()); + } + + public Map> groupUsersAlphabetically() { + return stream().collect(Collectors.groupingBy(s -> getFristCharacter(s.getFirstName()))); + } + + private Character getFristCharacter(final String string) { + return string.substring(0, 1).toUpperCase().charAt(0); + } +} diff --git a/persistence-modules/spring-data-jpa-query-3/src/test/java/com/baeldung/spring/data/jpa/querymap/UserRepositoryIntegrationTest.java b/persistence-modules/spring-data-jpa-query-3/src/test/java/com/baeldung/spring/data/jpa/querymap/UserRepositoryIntegrationTest.java new file mode 100644 index 0000000000..7f7c3899fe --- /dev/null +++ b/persistence-modules/spring-data-jpa-query-3/src/test/java/com/baeldung/spring/data/jpa/querymap/UserRepositoryIntegrationTest.java @@ -0,0 +1,114 @@ +package com.baeldung.spring.data.jpa.querymap; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; + +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest(classes = AppConfig.class, properties = { + "spring.jpa.defer-datasource-initialization=true", + "spring.sql.init.data-locations=classpath:user-map-query-dml.sql" +}) +@DisplayName("Given: ") +class UserRepositoryIntegrationTest { + + public static final int NUMBER_OF_USERS = 50; + + @Autowired + private UserRepository repository; + + @Test + @DisplayName("repository is present") + void whenContextStartsRepositoryIsPresent() { + assumeThat(repository).isNotNull(); + } + + @Test + @DisplayName("data is loaded") + void whenUserPersistedUserCanBeFetched() { + final List users = repository.findAll(); + assumeThat(users).hasSize(NUMBER_OF_USERS); + } + + @Nested + @DisplayName("When: ") + class When { + + @Test + @DisplayName("fetch all users in a Map with Collection all of them are present") + void fetchUsersInMapUsingCollectionsThenAllOfThemPresent() { + final Map users = repository.findAllAsMapUsingCollection(); + validateUserIdToUserMap(users); + } + + @Test + @DisplayName("fetch all users in a Map with Streamable all of them are present") + void fetchUsersInMapUsingStreamableThenAllOfThemPresent() { + final Map users = repository.findAllAsMapUsingStreamable(); + validateUserIdToUserMap(users); + } + + @Test + @DisplayName("fetch all users in a Map with Stream all of them are present") + void fetchUsersInMapUsingStreamThenAllOfThemPresent() { + final Map users = repository.findAllAsMapUsingStream(); + validateUserIdToUserMap(users); + } + + @Test + @DisplayName("fetch all users in a Map with Streamable wrapper all of them are present") + void fetchUsersInMapUsingStreamableWrapperThenAllOfThemPresent() { + final Users streamableWrapper = repository.findAllUsers(); + final Map users = streamableWrapper.getUserIdToUserMap(); + validateUserIdToUserMap(users); + } + + @Test + @DisplayName("fetch all users in a Map with Streamable wrapper " + + "with a filter, all of them are present") + void fetchUsersInMapUsingStreamableWrapperWithFilterThenAllOfThemPresent() { + final Users users = repository.findAllUsers(); + final int maxNameLength = 4; + final List actual = users.getAllUsersWithShortNames(maxNameLength); + User[] expected = { + new User(9L, "Moe", "Oddy"), + new User(25L, "Lane", "Endricci"), + new User(26L, "Doro", "Kinforth"), + new User(34L, "Otho", "Rowan"), + new User(39L, "Mel", "Moffet") + }; + assertThat(actual).containsExactly(expected); + } + + @Test + @DisplayName("fetch all users in a Map with Streamable wrapper " + + "and grouping all of them are present") + void fetchUsersInMapUsingStreamableWrapperAndGroupingThenAllOfThemPresent() { + final Users users = repository.findAllUsers(); + final Map> alphabeticalGrouping = users.groupUsersAlphabetically(); + final List actual = alphabeticalGrouping.get('A'); + User[] expected = { + new User(2L, "Auroora", "Oats"), + new User(4L, "Alika", "Capin"), + new User(20L, "Artus", "Rickards"), + new User(27L, "Antonina", "Vivian")}; + assertThat(actual).containsExactly(expected); + } + + } + + private static void validateUserIdToUserMap(final Map users) { + assertThat(users.values()).hasSize(NUMBER_OF_USERS); + users.forEach((id, user) -> { + assertThat(user.getId()).isEqualTo(id); + }); + } + +} \ No newline at end of file diff --git a/persistence-modules/spring-data-jpa-query-3/src/test/resources/user-map-query-dml.sql b/persistence-modules/spring-data-jpa-query-3/src/test/resources/user-map-query-dml.sql new file mode 100644 index 0000000000..a7e3d59362 --- /dev/null +++ b/persistence-modules/spring-data-jpa-query-3/src/test/resources/user-map-query-dml.sql @@ -0,0 +1,50 @@ +INSERT INTO users (id, first_name, last_name) VALUES (1, 'Findlay', 'MacCafferty'); +INSERT INTO users (id, first_name, last_name) VALUES (2, 'Auroora', 'Oats'); +INSERT INTO users (id, first_name, last_name) VALUES (3, 'Shaylah', 'Coleson'); +INSERT INTO users (id, first_name, last_name) VALUES (4, 'Alika', 'Capin'); +INSERT INTO users (id, first_name, last_name) VALUES (5, 'Caressa', 'Caldicot'); +INSERT INTO users (id, first_name, last_name) VALUES (6, 'Bonny', 'Kilday'); +INSERT INTO users (id, first_name, last_name) VALUES (7, 'Carmina', 'Stopp'); +INSERT INTO users (id, first_name, last_name) VALUES (8, 'Tiebout', 'Gamlyn'); +INSERT INTO users (id, first_name, last_name) VALUES (9, 'Moe', 'Oddy'); +INSERT INTO users (id, first_name, last_name) VALUES (10, 'Edita', 'Kenyon'); +INSERT INTO users (id, first_name, last_name) VALUES (11, 'Melany', 'Blaylock'); +INSERT INTO users (id, first_name, last_name) VALUES (12, 'Sigvard', 'Storrier'); +INSERT INTO users (id, first_name, last_name) VALUES (13, 'Ogdon', 'Labitt'); +INSERT INTO users (id, first_name, last_name) VALUES (14, 'Stacia', 'Waleworke'); +INSERT INTO users (id, first_name, last_name) VALUES (15, 'Tiffi', 'Pearsey'); +INSERT INTO users (id, first_name, last_name) VALUES (16, 'Nefen', 'Roddick'); +INSERT INTO users (id, first_name, last_name) VALUES (17, 'Danie', 'Silman'); +INSERT INTO users (id, first_name, last_name) VALUES (18, 'Giacobo', 'Slowey'); +INSERT INTO users (id, first_name, last_name) VALUES (19, 'Robbie', 'McGillicuddy'); +INSERT INTO users (id, first_name, last_name) VALUES (20, 'Artus', 'Rickards'); +INSERT INTO users (id, first_name, last_name) VALUES (21, 'Pauline', 'Furnival'); +INSERT INTO users (id, first_name, last_name) VALUES (22, 'Bella', 'Crittal'); +INSERT INTO users (id, first_name, last_name) VALUES (23, 'Melicent', 'Lowe'); +INSERT INTO users (id, first_name, last_name) VALUES (24, 'Delora', 'Leving'); +INSERT INTO users (id, first_name, last_name) VALUES (25, 'Lane', 'Endricci'); +INSERT INTO users (id, first_name, last_name) VALUES (26, 'Doro', 'Kinforth'); +INSERT INTO users (id, first_name, last_name) VALUES (27, 'Antonina', 'Vivian'); +INSERT INTO users (id, first_name, last_name) VALUES (28, 'Hanny', 'Challoner'); +INSERT INTO users (id, first_name, last_name) VALUES (29, 'Tarrance', 'Beretta'); +INSERT INTO users (id, first_name, last_name) VALUES (30, 'Emyle', 'Jaycox'); +INSERT INTO users (id, first_name, last_name) VALUES (31, 'Lelah', 'Vesque'); +INSERT INTO users (id, first_name, last_name) VALUES (32, 'Kitty', 'Pumphrey'); +INSERT INTO users (id, first_name, last_name) VALUES (33, 'Karmen', 'Flaubert'); +INSERT INTO users (id, first_name, last_name) VALUES (34, 'Otho', 'Rowan'); +INSERT INTO users (id, first_name, last_name) VALUES (35, 'Jacinta', 'Tatlow'); +INSERT INTO users (id, first_name, last_name) VALUES (36, 'Jennette', 'Wass'); +INSERT INTO users (id, first_name, last_name) VALUES (37, 'Randi', 'Hambridge'); +INSERT INTO users (id, first_name, last_name) VALUES (38, 'Filmer', 'Conibere'); +INSERT INTO users (id, first_name, last_name) VALUES (39, 'Mel', 'Moffet'); +INSERT INTO users (id, first_name, last_name) VALUES (40, 'Clement', 'MacGovern'); +INSERT INTO users (id, first_name, last_name) VALUES (41, 'Silvana', 'Couronne'); +INSERT INTO users (id, first_name, last_name) VALUES (42, 'Myles', 'Lambersen'); +INSERT INTO users (id, first_name, last_name) VALUES (43, 'Sisile', 'Jeskins'); +INSERT INTO users (id, first_name, last_name) VALUES (44, 'Jackie', 'Aulsford'); +INSERT INTO users (id, first_name, last_name) VALUES (45, 'Duncan', 'Stapford'); +INSERT INTO users (id, first_name, last_name) VALUES (46, 'Heddie', 'Wylder'); +INSERT INTO users (id, first_name, last_name) VALUES (47, 'Homerus', 'Lyddyard'); +INSERT INTO users (id, first_name, last_name) VALUES (48, 'Davis', 'Golson'); +INSERT INTO users (id, first_name, last_name) VALUES (49, 'Marjory', 'Wilcock'); +INSERT INTO users (id, first_name, last_name) VALUES (50, 'Sheffield', 'Pigford'); \ No newline at end of file