LoginSignup
5
6

More than 3 years have passed since last update.

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

Posted at

現象

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

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

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

ソースコード

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を確認するプロパティを追加する。

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

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

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;

    @Transactional
    @Override
    public void run(String... args) throws Exception {
        Sample newEntity = new Sample(1L, "name");
        repository.save(newEntity);
    }

}

実行ログ

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

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

解説

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

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

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

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

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6