Spring Data JPAのsaveのselect-insertをinsertだけにする

# 現象
spring-data-jpaでidを明示的に指定して```save```する場合、以下のログのようにselectしてからinsertになる。

```java
Sample newEntity = new Sample(1L, "name");
repository.save(newEntity);
```

```sql
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 (?, ?)
```

idがすでに永続化済みかどうかをselectしてからinsertするのはjpaの挙動である。しかし、ある種のバッチ処理などでは、仕様としてそのidが存在しない事が明らかな場合がある。この場合はselectは単に無駄となるので防ぎたい。以下はこの事前selectを防ぐ方法について。

# ソースコード

```gradle: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()
}

```

内部的に発行されるSQLを確認するプロパティを追加する。

```properties:src/main/resources/application.properties
spring.jpa.show-sql=true
```

エンティティクラス。```Persistable```は後述。

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

```java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

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

}
```

```java
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;
	
	@Transactional
	@Override
	public void run(String... args) throws Exception {
		Sample newEntity = new Sample(1L, "name");
		repository.save(newEntity);
	}

}

```

# 実行ログ
上記コードを実行すると以下のようなSQLログに変化する。

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

# 解説
spring-data-jpaの```save```は基本的には以下の```SimpleJpaRepository#save```を通過し、その実装は以下となっている。

```java:SimpleJpaRepository
	public <S extends T> S save(S entity) {

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

デフォルトでは```isNew```は```false```なので```em.merge```となり、結果としてselect-insertになる。この挙動はエンティティクラスに```Persistable```を実装することで変えられる。今回は事前selectを防ぐ、つまりJPAに新規エンティティとして認識させたいので、```isNew```を```true```を返すようにしている。