[Java] Make select-insert of save of Spring Data JPA only insert

1 minute read

Phenomenon

When id is explicitly specified in spring-data-jpa and ```save

is performed, select it as shown in the log below and then insert.



```java
Sample newEntity = new Sample(1L, "name");
repository.save(newEntity);
Hibernate: select sample0_.id as id1_0_0_, sample0_.name as name2_0_0_ from sample sample0_ where sample0_.id=?
Hibernate: insert into sample (name, id) values (?, ?)

It is jpa’s behavior to insert after selecting whether id has already been persisted. However, in some batch processing, it may be clear that the id does not exist as a specification. In this case, select is simply wasted and we want to prevent it. Below is how to prevent this pre-select.

Source code

build.gradle


plugins {
  id'org.springframework.boot' version '2.3.3.RELEASE'
  id'io.spring.dependency-management' version '1.0.10.RELEASE'
  id'java'
}
group ='com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
  compileOnly {
    extendsFrom annotationProcessor
  }
}
repositories {
  mavenCentral()
}
dependencies {
  implementation'org.springframework.boot:spring-boot-starter-data-jpa'
  compileOnly'org.projectlombok:lombok'
  runtimeOnly'com.h2database:h2'
  annotationProcessor'org.projectlombok:lombok'
  testImplementation('org.springframework.boot:spring-boot-starter-test') {
    exclude group:'org.junit.vintage', module:'junit-vintage-engine'
  }
}
test {
  useJUnitPlatform()
}

Add a property to check internally issued SQL.

src/main/resources/application.properties


spring.jpa.show-sql=true

Entity class. ```Persistable

is described later.



```java
import javax.persistence.Entity;
import javax.persistence.Id;

import org.springframework.data.domain.Persistable;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Sample implements Persistable<Long> {
@Id
Long id;

String name;

@Override
public Long getId() {
return id;
}

@Override
public boolean isNew() {
return true;
}
}
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface SampleRepository extends JpaRepository<Sample, Long> {

}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.Transactional;

@EnableJpaRepositories
@SpringBootApplication
public class JpaSample implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(JpaSample.class, args);
}

@Autowired
SampleRepository repository;
It's a sequel.
@Transactional
@Override
public void run(String... args) throws Exception {
Sample newEntity = new Sample(1L, "name");
repository.save(newEntity);
}

}

Execution log

When the above code is executed, the SQL log changes as below.

Hibernate: insert into sample (name, id) values (?, ?)

Commentary

The save of spring-data-jpa basically passes the following ```SimpleJpaRepository#save

and its implementation is as follows.




#### **`SimpleJpaRepository`**
```java

public <S extends T> S save(S entity) {

if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}

By default, isNew is false, so it becomes em.merge, resulting in select-insert. This behavior can be changed by implementing Persistable in the entity class. This time, we want to prevent pre-selection, that is, to let JPA recognize as a new entity, so isNew returns ```true

.

```