BAEL-6581 - Skip Select Before Insert in Spring Data JPA
This commit is contained in:
parent
cc2d1bfb36
commit
6894e31e0d
|
@ -16,6 +16,11 @@
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.hypersistence</groupId>
|
||||||
|
<artifactId>hypersistence-utils-hibernate-55</artifactId>
|
||||||
|
<version>3.7.1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.baeldung.skipselectbeforeinsert.repository;
|
||||||
|
|
||||||
|
import com.baeldung.skipselectbeforeinsert.model.Task;
|
||||||
|
|
||||||
|
public interface TaskRepositoryExtension {
|
||||||
|
Task persistAndFlush(Task task);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
Loading…
Reference in New Issue