From e316c193a0340005037252fab7f9f0548860f151 Mon Sep 17 00:00:00 2001 From: Eugene Kovko <37694937+eukovko@users.noreply.github.com> Date: Fri, 29 Dec 2023 19:15:39 +0100 Subject: [PATCH] Bael 7281 (#15494) * BAEL-7281: When use getOne and findOne methods Spring Data JPA * BAEL-7281: Cleanup * BAEL-7281: Test rename * BAEL-7281: Fix not updated name --- .../findvsget/ApplicationConfig.java | 8 ++ .../persistence/findvsget/entity/User.java | 90 +++++++++++++ .../NewTransactionUserRepository.java | 19 +++ .../repository/SimpleUserRepository.java | 10 ++ .../NonTransactionalUserReferenceService.java | 33 +++++ .../findvsget/service/SimpleUserService.java | 28 ++++ .../TransactionalUserReferenceService.java | 36 +++++ ...abaseConfigurationBaseIntegrationTest.java | 43 ++++++ .../FindUserIntegrationIntegrationTest.java | 37 ++++++ ...etReferenceIntegrationIntegrationTest.java | 125 ++++++++++++++++++ .../persistence/findvsget/UserProvider.java | 30 +++++ 11 files changed, 459 insertions(+) create mode 100644 persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/ApplicationConfig.java create mode 100644 persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/entity/User.java create mode 100644 persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/repository/NewTransactionUserRepository.java create mode 100644 persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/repository/SimpleUserRepository.java create mode 100644 persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/service/NonTransactionalUserReferenceService.java create mode 100644 persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/service/SimpleUserService.java create mode 100644 persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/service/TransactionalUserReferenceService.java create mode 100644 persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/DatabaseConfigurationBaseIntegrationTest.java create mode 100644 persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/FindUserIntegrationIntegrationTest.java create mode 100644 persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/GetReferenceIntegrationIntegrationTest.java create mode 100644 persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/UserProvider.java diff --git a/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/ApplicationConfig.java b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/ApplicationConfig.java new file mode 100644 index 0000000000..043efc584a --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/ApplicationConfig.java @@ -0,0 +1,8 @@ +package com.baeldung.spring.data.persistence.findvsget; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ApplicationConfig { + +} diff --git a/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/entity/User.java b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/entity/User.java new file mode 100644 index 0000000000..264ea92a5c --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/entity/User.java @@ -0,0 +1,90 @@ +package com.baeldung.spring.data.persistence.findvsget.entity; + +import java.util.Objects; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "users") +public class User { + + @Id + @Column(name = "id") + private Long id; + @Column(name = "first_name") + private String firstName; + @Column(name = "second_name") + private String secondName; + + public User() { + } + + public User(final Long id, final String firstName, final String secondName) { + this.id = id; + this.firstName = firstName; + this.secondName = secondName; + } + + public void setId(final Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(final String firstName) { + this.firstName = firstName; + } + + public String getSecondName() { + return secondName; + } + + public void setSecondName(final String secondName) { + this.secondName = secondName; + } + + @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(secondName, user.secondName); + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (firstName != null ? firstName.hashCode() : 0); + result = 31 * result + (secondName != null ? secondName.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", firstName='" + firstName + '\'' + + ", secondName='" + secondName + '\'' + + '}'; + } +} diff --git a/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/repository/NewTransactionUserRepository.java b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/repository/NewTransactionUserRepository.java new file mode 100644 index 0000000000..6b0523b637 --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/repository/NewTransactionUserRepository.java @@ -0,0 +1,19 @@ +package com.baeldung.spring.data.persistence.findvsget.repository; + +import com.baeldung.spring.data.persistence.findvsget.entity.User; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Repository +public interface NewTransactionUserRepository extends JpaRepository { + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + User getReferenceById(Long id); + + @Override + Optional findById(Long id); +} diff --git a/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/repository/SimpleUserRepository.java b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/repository/SimpleUserRepository.java new file mode 100644 index 0000000000..e105f226a5 --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/repository/SimpleUserRepository.java @@ -0,0 +1,10 @@ +package com.baeldung.spring.data.persistence.findvsget.repository; + +import com.baeldung.spring.data.persistence.findvsget.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface SimpleUserRepository extends JpaRepository { + +} diff --git a/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/service/NonTransactionalUserReferenceService.java b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/service/NonTransactionalUserReferenceService.java new file mode 100644 index 0000000000..e79df3c91d --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/service/NonTransactionalUserReferenceService.java @@ -0,0 +1,33 @@ +package com.baeldung.spring.data.persistence.findvsget.service; + +import com.baeldung.spring.data.persistence.findvsget.entity.User; +import com.baeldung.spring.data.persistence.findvsget.repository.SimpleUserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class NonTransactionalUserReferenceService { + + private static final Logger log = LoggerFactory.getLogger(NonTransactionalUserReferenceService.class); + private SimpleUserRepository repository; + + public User findUserReference(final long id) { + log.info("Before requesting a user"); + final User user = repository.getReferenceById(id); + log.info("After requesting a user"); + return user; + } + + public User findAndUseUserReference(final long id) { + final User user = repository.getReferenceById(id); + log.info("Before accessing a username"); + final String firstName = user.getFirstName(); + log.info("This message shouldn't be displayed because of the thrown exception: {}", firstName); + return user; + } + + public void setRepository(final SimpleUserRepository repository) { + this.repository = repository; + } +} diff --git a/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/service/SimpleUserService.java b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/service/SimpleUserService.java new file mode 100644 index 0000000000..0ed7a72946 --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/service/SimpleUserService.java @@ -0,0 +1,28 @@ +package com.baeldung.spring.data.persistence.findvsget.service; + +import com.baeldung.spring.data.persistence.findvsget.entity.User; +import com.baeldung.spring.data.persistence.findvsget.repository.SimpleUserRepository; +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class SimpleUserService { + + private static final Logger log = LoggerFactory.getLogger(SimpleUserService.class); + private final SimpleUserRepository repository; + + public SimpleUserService(final SimpleUserRepository repository) { + this.repository = repository; + } + + public User findUser(final long id) { + log.info("Before requesting a user in a findUser method"); + final Optional optionalUser = repository.findById(id); + log.info("After requesting a user in a findUser method"); + final User user = optionalUser.orElse(null); + log.info("After unwrapping an optional in a findUser method"); + return user; + } +} diff --git a/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/service/TransactionalUserReferenceService.java b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/service/TransactionalUserReferenceService.java new file mode 100644 index 0000000000..56ba884fed --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-4/src/main/java/com/baeldung/spring/data/persistence/findvsget/service/TransactionalUserReferenceService.java @@ -0,0 +1,36 @@ +package com.baeldung.spring.data.persistence.findvsget.service; + +import com.baeldung.spring.data.persistence.findvsget.entity.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class TransactionalUserReferenceService { + + private static final Logger log = LoggerFactory.getLogger(TransactionalUserReferenceService.class); + private JpaRepository repository; + + @Transactional + public User findUserReference(final long id) { + log.info("Before requesting a user"); + final User user = repository.getReferenceById(id); + log.info("After requesting a user"); + return user; + } + + @Transactional + public User findAndUseUserReference(final long id) { + final User user = repository.getReferenceById(id); + log.info("Before accessing a username"); + final String firstName = user.getFirstName(); + log.info("After accessing a username: {}", firstName); + return user; + } + + public void setRepository(final JpaRepository repository) { + this.repository = repository; + } +} diff --git a/persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/DatabaseConfigurationBaseIntegrationTest.java b/persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/DatabaseConfigurationBaseIntegrationTest.java new file mode 100644 index 0000000000..3ac46c3a39 --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/DatabaseConfigurationBaseIntegrationTest.java @@ -0,0 +1,43 @@ +package com.baeldung.spring.data.persistence.findvsget; + +import static com.baeldung.spring.data.persistence.findvsget.UserProvider.userSource; +import static org.assertj.core.api.Assumptions.assumeThat; + +import com.baeldung.spring.data.persistence.findvsget.entity.User; +import com.baeldung.spring.data.persistence.findvsget.repository.SimpleUserRepository; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.provider.Arguments; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest(classes = ApplicationConfig.class, properties = { + "spring.jpa.generate-ddl=true", + "spring.jpa.show-sql=false" +}) +abstract class DatabaseConfigurationBaseIntegrationTest { + + private static final int NUMBER_OF_USERS = 10; + + @Autowired + private SimpleUserRepository repository; + + @BeforeEach + void populateDatabase() { + final List users = userSource() + .map(Arguments::get) + .map(s -> new User(((Long) s[0]), s[1].toString(), s[2].toString())) + .collect(Collectors.toList()); + repository.saveAll(users); + assumeThat(repository.findAll()).hasSize(NUMBER_OF_USERS); + } + + @AfterEach + void clearDatabase() { + repository.deleteAll(); + } + +} + diff --git a/persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/FindUserIntegrationIntegrationTest.java b/persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/FindUserIntegrationIntegrationTest.java new file mode 100644 index 0000000000..efe370b38a --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/FindUserIntegrationIntegrationTest.java @@ -0,0 +1,37 @@ +package com.baeldung.spring.data.persistence.findvsget; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.baeldung.spring.data.persistence.findvsget.entity.User; +import com.baeldung.spring.data.persistence.findvsget.service.SimpleUserService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; + +@DisplayName("findBy test:") +class FindUserIntegrationIntegrationTest extends DatabaseConfigurationBaseIntegrationTest { + + + @Autowired + private SimpleUserService service; + + @ParameterizedTest + @ArgumentsSource(UserProvider.class) + @DisplayName("when looking for a user by an existing ID returns a user") + void whenGettingUserByCorrectIdThenReturnUser(Long id, String firstName, String lastName) { + final User expected = new User(id, firstName, lastName); + final User actual = service.findUser(id); + assertThat(actual).isEqualTo(expected); + } + + @ParameterizedTest + @DisplayName("when looking for a user by a non-existing ID returns null") + @ValueSource(longs = {11, 12, 13}) + void whenGettingUserByIncorrectIdThenReturnNull(Long id) { + assertThat(service.findUser(id)).isNull(); + } + + +} diff --git a/persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/GetReferenceIntegrationIntegrationTest.java b/persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/GetReferenceIntegrationIntegrationTest.java new file mode 100644 index 0000000000..c4be5d1ccd --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/GetReferenceIntegrationIntegrationTest.java @@ -0,0 +1,125 @@ +package com.baeldung.spring.data.persistence.findvsget; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import com.baeldung.spring.data.persistence.findvsget.entity.User; +import com.baeldung.spring.data.persistence.findvsget.repository.NewTransactionUserRepository; +import com.baeldung.spring.data.persistence.findvsget.repository.SimpleUserRepository; +import com.baeldung.spring.data.persistence.findvsget.service.NonTransactionalUserReferenceService; +import com.baeldung.spring.data.persistence.findvsget.service.TransactionalUserReferenceService; +import org.hibernate.LazyInitializationException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.springframework.beans.factory.annotation.Autowired; + + +@DisplayName("getReferenceBy test:") +class GetReferenceIntegrationIntegrationTest extends DatabaseConfigurationBaseIntegrationTest { + + private static final long EXISTING_ID = 1L; + + @Nested + @DisplayName("given non-transactional service, even if user exists") + class GivenNonTransactionalService { + + @Autowired + private NonTransactionalUserReferenceService nonTransactionalService; + + @BeforeEach + void configureService(@Autowired SimpleUserRepository repository) { + nonTransactionalService.setRepository(repository); + } + + @Test + void whenFindUserReferenceUsingOutsideServiceThenThrowsException() { + final User user = nonTransactionalService.findUserReference(EXISTING_ID); + assertThatExceptionOfType(LazyInitializationException.class) + .isThrownBy(user::getFirstName); + } + + @Test + void whenFindUserReferenceNotUsingOutsideServiceThenDontThrowException() { + final User user = nonTransactionalService.findUserReference(EXISTING_ID); + assertThat(user).isNotNull(); + } + + @Test + void whenFindUserReferenceUsingInsideServiceThenThrowsException() { + assertThatExceptionOfType(LazyInitializationException.class) + .isThrownBy(() -> nonTransactionalService.findAndUseUserReference(EXISTING_ID)); + } + } + + @Nested + @DisplayName("given transactional service with simple repository, even if user exists") + class GivenTransactionalService { + + @Autowired + private TransactionalUserReferenceService transactionalService; + + @BeforeEach + void configureService(@Autowired SimpleUserRepository repository) { + transactionalService.setRepository(repository); + } + + @Test + void whenFindUserReferenceUsingOutsideServiceThenThrowsException() { + final User user = transactionalService.findUserReference(EXISTING_ID); + assertThatExceptionOfType(LazyInitializationException.class) + .isThrownBy(user::getFirstName); + } + + @Test + void whenFindUserReferenceNotUsingOutsideServiceThenDontThrowException() { + final User user = transactionalService.findUserReference(EXISTING_ID); + assertThat(user).isNotNull(); + } + + @ParameterizedTest + @ArgumentsSource(UserProvider.class) + void whenFindUserReferenceUsingInsideServiceThenReturnsUser(Long id, String firstName, String lastName) { + final User expected = new User(id, firstName, lastName); + final User actual = transactionalService.findAndUseUserReference(id); + assertThat(actual).isEqualTo(expected); + } + } + + @Nested + @DisplayName("given transactional service with new transaction repository, even if user exists") + class GivenTransactionalServiceWithNewTransactionRepository { + + @Autowired + private TransactionalUserReferenceService transactionalServiceWithNewTransactionRepository; + + @BeforeEach + void configureService(@Autowired NewTransactionUserRepository repository) { + transactionalServiceWithNewTransactionRepository.setRepository(repository); + } + + @Test + void whenFindUserReferenceUsingOutsideServiceThenThrowsException() { + final User user = transactionalServiceWithNewTransactionRepository + .findUserReference(EXISTING_ID); + assertThatExceptionOfType(LazyInitializationException.class) + .isThrownBy(user::getFirstName); + } + + @Test + void whenFindUserReferenceNotUsingOutsideServiceThenDontThrowException() { + final User user = transactionalServiceWithNewTransactionRepository.findUserReference(EXISTING_ID); + assertThat(user).isNotNull(); + } + + @Test + void whenFindUserReferenceUsingInsideServiceThenThrowsExceptionDueToSeparateTransactions() { + assertThatExceptionOfType(LazyInitializationException.class) + .isThrownBy(() -> transactionalServiceWithNewTransactionRepository + .findAndUseUserReference(EXISTING_ID)); + } + } +} diff --git a/persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/UserProvider.java b/persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/UserProvider.java new file mode 100644 index 0000000000..f0222dae50 --- /dev/null +++ b/persistence-modules/spring-data-jpa-repo-4/src/test/java/com/baeldung/spring/data/persistence/findvsget/UserProvider.java @@ -0,0 +1,30 @@ +package com.baeldung.spring.data.persistence.findvsget; + + +import java.util.stream.Stream; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; + +public class UserProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(final ExtensionContext context) { + return userSource(); + } + + static Stream userSource() { + return Stream.of( + Arguments.of(1L, "Saundra", "Krystek"), + Arguments.of(2L, "Korey", "Venners"), + Arguments.of(3L, "Lory", "Daffey"), + Arguments.of(4L, "Michail", "Spinella"), + Arguments.of(5L, "Emanuel", "Geertje"), + Arguments.of(6L, "Jervis", "Waugh"), + Arguments.of(7L, "Chantal", "Soldan"), + Arguments.of(8L, "Darnall", "Fanner"), + Arguments.of(9L, "Cordelia", "Hindge"), + Arguments.of(10L, "Lem", "Pitcock") + ); + } +}