BAEL-6581 - Skip Select Before Insert in Spring Data JPA

This commit is contained in:
ICKostiantyn.Ivanov 2024-02-09 09:32:08 +01:00
parent cc2d1bfb36
commit 6894e31e0d
13 changed files with 335 additions and 0 deletions

View File

@ -16,6 +16,11 @@
</parent>
<dependencies>
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-55</artifactId>
<version>3.7.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

View File

@ -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);
}
}

View File

@ -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<Integer> {
@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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<PersistableTask, Integer> {
}

View File

@ -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<Task, Integer>, BaseJpaRepository<Task, Integer> {
}

View File

@ -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<Task, Integer>, TaskRepositoryExtension {
@Modifying
@Query(value = "insert into task(id, description) values(:#{#task.id}, :#{#task.description})", nativeQuery = true)
void insert(@Param("task") Task task);
}

View File

@ -0,0 +1,7 @@
package com.baeldung.skipselectbeforeinsert.repository;
import com.baeldung.skipselectbeforeinsert.model.Task;
public interface TaskRepositoryExtension {
Task persistAndFlush(Task task);
}

View File

@ -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;
}
}

View File

@ -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<TaskWithGeneratedId, Integer> {
}

View File

@ -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());
}
}

View File

@ -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