From 6894e31e0de902cfcf8e36cbae4aedd08f851f0b Mon Sep 17 00:00:00 2001 From: "ICKostiantyn.Ivanov" Date: Fri, 9 Feb 2024 09:32:08 +0100 Subject: [PATCH] BAEL-6581 - Skip Select Before Insert in Spring Data JPA --- .../spring-boot-data-3/pom.xml | 5 + .../SkipSelectBeforeInsertApplication.java | 18 +++ .../model/PersistableTask.java | 45 +++++++ .../skipselectbeforeinsert/model/Task.java | 30 +++++ .../model/TaskWithGeneratedId.java | 31 +++++ .../repository/PersistableTaskRepository.java | 12 ++ .../repository/TaskJpaRepository.java | 12 ++ .../repository/TaskRepository.java | 17 +++ .../repository/TaskRepositoryExtension.java | 7 ++ .../TaskRepositoryExtensionImpl.java | 21 ++++ .../TaskWithGeneratedIdRepository.java | 14 +++ ...SkipSelectBeforeInsertIntegrationTest.java | 111 ++++++++++++++++++ .../src/test/resources/application.properties | 12 ++ 13 files changed, 335 insertions(+) create mode 100644 spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/SkipSelectBeforeInsertApplication.java create mode 100644 spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/model/PersistableTask.java create mode 100644 spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/model/Task.java create mode 100644 spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/model/TaskWithGeneratedId.java create mode 100644 spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/PersistableTaskRepository.java create mode 100644 spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskJpaRepository.java create mode 100644 spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskRepository.java create mode 100644 spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskRepositoryExtension.java create mode 100644 spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskRepositoryExtensionImpl.java create mode 100644 spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskWithGeneratedIdRepository.java create mode 100644 spring-boot-modules/spring-boot-data-3/src/test/java/com/baeldung/skipselectbeforeinsert/SkipSelectBeforeInsertIntegrationTest.java create mode 100644 spring-boot-modules/spring-boot-data-3/src/test/resources/application.properties diff --git a/spring-boot-modules/spring-boot-data-3/pom.xml b/spring-boot-modules/spring-boot-data-3/pom.xml index 4afafe3c19..d73ffc8515 100644 --- a/spring-boot-modules/spring-boot-data-3/pom.xml +++ b/spring-boot-modules/spring-boot-data-3/pom.xml @@ -16,6 +16,11 @@ + + io.hypersistence + hypersistence-utils-hibernate-55 + 3.7.1 + org.springframework.boot spring-boot-starter-web diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/SkipSelectBeforeInsertApplication.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/SkipSelectBeforeInsertApplication.java new file mode 100644 index 0000000000..481d8f8aa3 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/SkipSelectBeforeInsertApplication.java @@ -0,0 +1,18 @@ +package com.baeldung.skipselectbeforeinsert; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import io.hypersistence.utils.spring.repository.BaseJpaRepositoryImpl; + +@SpringBootApplication +@EnableJpaRepositories( + value = "com.baeldung.skipselectbeforeinsert.repository", + repositoryBaseClass = BaseJpaRepositoryImpl.class +) +public class SkipSelectBeforeInsertApplication { + public static void main(String[] args) { + SpringApplication.run(SkipSelectBeforeInsertApplication.class, args); + } +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/model/PersistableTask.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/model/PersistableTask.java new file mode 100644 index 0000000000..949aa2935d --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/model/PersistableTask.java @@ -0,0 +1,45 @@ +package com.baeldung.skipselectbeforeinsert.model; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Transient; + +import org.springframework.data.domain.Persistable; + +@Entity +public class PersistableTask implements Persistable { + + @Id + private int id; + private String description; + + @Transient + private boolean isNew = true; + + public void setNew(boolean isNew) { + this.isNew = isNew; + } + + @Override + public Integer getId() { + return id; + } + + @Override + public boolean isNew() { + return isNew; + } + + public void setId(int id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/model/Task.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/model/Task.java new file mode 100644 index 0000000000..9b8eaa8f6b --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/model/Task.java @@ -0,0 +1,30 @@ +package com.baeldung.skipselectbeforeinsert.model; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Task { + + @Id + private Integer id; + private String description; + + public Integer getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/model/TaskWithGeneratedId.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/model/TaskWithGeneratedId.java new file mode 100644 index 0000000000..a3ea2a8ad0 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/model/TaskWithGeneratedId.java @@ -0,0 +1,31 @@ +package com.baeldung.skipselectbeforeinsert.model; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class TaskWithGeneratedId { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + private String description; + + public Integer getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/PersistableTaskRepository.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/PersistableTaskRepository.java new file mode 100644 index 0000000000..83e701b0c2 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/PersistableTaskRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.skipselectbeforeinsert.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.baeldung.skipselectbeforeinsert.model.PersistableTask; +import com.baeldung.skipselectbeforeinsert.model.Task; + +@Repository +public interface PersistableTaskRepository extends JpaRepository { + +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskJpaRepository.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskJpaRepository.java new file mode 100644 index 0000000000..a19089ec6e --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskJpaRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.skipselectbeforeinsert.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import com.baeldung.skipselectbeforeinsert.model.Task; + +import io.hypersistence.utils.spring.repository.BaseJpaRepository; + +@Repository +public interface TaskJpaRepository extends JpaRepository, BaseJpaRepository { +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskRepository.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskRepository.java new file mode 100644 index 0000000000..85b6aaf7bc --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskRepository.java @@ -0,0 +1,17 @@ +package com.baeldung.skipselectbeforeinsert.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import com.baeldung.skipselectbeforeinsert.model.Task; + +@Repository +public interface TaskRepository extends JpaRepository, TaskRepositoryExtension { + + @Modifying + @Query(value = "insert into task(id, description) values(:#{#task.id}, :#{#task.description})", nativeQuery = true) + void insert(@Param("task") Task task); +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskRepositoryExtension.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskRepositoryExtension.java new file mode 100644 index 0000000000..be37cdb056 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskRepositoryExtension.java @@ -0,0 +1,7 @@ +package com.baeldung.skipselectbeforeinsert.repository; + +import com.baeldung.skipselectbeforeinsert.model.Task; + +public interface TaskRepositoryExtension { + Task persistAndFlush(Task task); +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskRepositoryExtensionImpl.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskRepositoryExtensionImpl.java new file mode 100644 index 0000000000..86ff10a521 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskRepositoryExtensionImpl.java @@ -0,0 +1,21 @@ +package com.baeldung.skipselectbeforeinsert.repository; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.springframework.stereotype.Component; + +import com.baeldung.skipselectbeforeinsert.model.Task; + +@Component +public class TaskRepositoryExtensionImpl implements TaskRepositoryExtension { + @PersistenceContext + private EntityManager entityManager; + + @Override + public Task persistAndFlush(Task task) { + entityManager.persist(task); + entityManager.flush(); + return task; + } +} diff --git a/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskWithGeneratedIdRepository.java b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskWithGeneratedIdRepository.java new file mode 100644 index 0000000000..956deb0a1b --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/main/java/com/baeldung/skipselectbeforeinsert/repository/TaskWithGeneratedIdRepository.java @@ -0,0 +1,14 @@ +package com.baeldung.skipselectbeforeinsert.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import com.baeldung.skipselectbeforeinsert.model.Task; +import com.baeldung.skipselectbeforeinsert.model.TaskWithGeneratedId; + +@Repository +public interface TaskWithGeneratedIdRepository extends JpaRepository { +} diff --git a/spring-boot-modules/spring-boot-data-3/src/test/java/com/baeldung/skipselectbeforeinsert/SkipSelectBeforeInsertIntegrationTest.java b/spring-boot-modules/spring-boot-data-3/src/test/java/com/baeldung/skipselectbeforeinsert/SkipSelectBeforeInsertIntegrationTest.java new file mode 100644 index 0000000000..10250b8725 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/test/java/com/baeldung/skipselectbeforeinsert/SkipSelectBeforeInsertIntegrationTest.java @@ -0,0 +1,111 @@ +package com.baeldung.skipselectbeforeinsert; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.dao.DataIntegrityViolationException; + +import com.baeldung.skipselectbeforeinsert.model.PersistableTask; +import com.baeldung.skipselectbeforeinsert.model.Task; +import com.baeldung.skipselectbeforeinsert.model.TaskWithGeneratedId; +import com.baeldung.skipselectbeforeinsert.repository.PersistableTaskRepository; +import com.baeldung.skipselectbeforeinsert.repository.TaskJpaRepository; +import com.baeldung.skipselectbeforeinsert.repository.TaskRepository; +import com.baeldung.skipselectbeforeinsert.repository.TaskWithGeneratedIdRepository; + +@DataJpaTest +public class SkipSelectBeforeInsertIntegrationTest { + + @Autowired + private TaskRepository taskRepository; + @Autowired + private TaskWithGeneratedIdRepository taskWithGeneratedIdRepository; + @Autowired + private PersistableTaskRepository persistableTaskRepository; + + @Autowired + private TaskJpaRepository taskJpaRepository; + + @Test + public void givenRepository_whenSaveNewTaskWithPopulatedId_thenExtraSelectIsExpected() { + Task task = new Task(); + task.setId(1); + taskRepository.saveAndFlush(task); + } + + @Test + public void givenRepository_whenSaveNewTaskWithGeneratedId_thenNoExtraSelectIsExpected() { + TaskWithGeneratedId task = new TaskWithGeneratedId(); + TaskWithGeneratedId saved = taskWithGeneratedIdRepository.saveAndFlush(task); + assertNotNull(saved.getId()); + } + + @Test + public void givenRepository_whenSaveNewPersistableTask_thenNoExtraSelectIsExpected() { + PersistableTask persistableTask = new PersistableTask(); + persistableTask.setId(2); + persistableTask.setNew(true); + PersistableTask saved = persistableTaskRepository.saveAndFlush(persistableTask); + assertEquals(2, saved.getId()); + } + + @Test + public void givenRepository_whenSaveNewPersistableTasksWithSameId_thenExceptionIsExpected() { + PersistableTask persistableTask = new PersistableTask(); + persistableTask.setId(3); + persistableTask.setNew(true); + persistableTaskRepository.saveAndFlush(persistableTask); + + PersistableTask duplicateTask = new PersistableTask(); + duplicateTask.setId(3); + duplicateTask.setNew(true); + + assertThrows(DataIntegrityViolationException.class, + () -> persistableTaskRepository.saveAndFlush(duplicateTask)); + } + + @Test + public void givenRepository_whenPersistNewTaskUsingCustomPersistMethod_thenNoExtraSelectIsExpected() { + Task task = new Task(); + task.setId(4); + Task saved = taskRepository.persistAndFlush(task); + + assertEquals(4, saved.getId()); + } + + @Test + public void givenRepository_whenPersistNewTaskUsingPersist_thenNoExtraSelectIsExpected() { + Task task = new Task(); + task.setId(5); + Task saved = taskJpaRepository.persistAndFlush(task); + + assertEquals(5, saved.getId()); + } + + @Test + public void givenRepository_whenPersistTaskWithTheSameId_thenExceptionIsExpected() { + Task task = new Task(); + task.setId(5); + taskJpaRepository.persistAndFlush(task); + + Task secondTask = new Task(); + secondTask.setId(5); + + assertThrows(DataIntegrityViolationException.class, + () -> taskJpaRepository.persistAndFlush(secondTask)); + } + + @Test + public void givenRepository_whenPersistNewTaskUsingNativeQuery_thenNoExtraSelectIsExpected() { + Task task = new Task(); + task.setId(6); + taskRepository.insert(task); + + assertTrue(taskRepository.findById(6).isPresent()); + } +} diff --git a/spring-boot-modules/spring-boot-data-3/src/test/resources/application.properties b/spring-boot-modules/spring-boot-data-3/src/test/resources/application.properties new file mode 100644 index 0000000000..5593af4735 --- /dev/null +++ b/spring-boot-modules/spring-boot-data-3/src/test/resources/application.properties @@ -0,0 +1,12 @@ +# spring.datasource.x +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1 +spring.datasource.username=sa +spring.datasource.password=sa + +# hibernate.X +spring.jpa.hibernate.dialect=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.hibernate.show_sql=true +spring.jpa.hibernate.hbm2ddl.auto=create-drop +spring.jpa.defer-datasource-initialization=true \ No newline at end of file