LoginSignup
5

More than 3 years have passed since last update.

Spring Boot + Spring Data JPA にて getOne, findById, クエリメソッドの挙動を確認する

Last updated at Posted at 2020-05-18

概要

  • Spring Boot + Spring Data JPA にて JpaRepository#getOne, CrudRepository#findById, クエリメソッド HogeRepository#findByFoo の挙動を確認する

メソッドの説明

JpaRepository#getOne

JpaRepository#getOne は遅延取得 (laze fetch) するメソッド。

JpaRepository (Spring Data JPA 2.2.7.RELEASE API) - Javadoc 日本語訳

T getOne(ID id)
指定された識別子を持つエンティティへの参照を返します。JPA 永続性プロバイダーの実装方法によっては、これは常にインスタンスを返し、最初のアクセスで EntityNotFoundException をスローする可能性が非常に高くなります。それらのいくつかは、無効な識別子をすぐに拒否します。

パラメーター :
id - null であってはなりません。

戻り値 :
指定された識別子を持つエンティティへの参照。

関連事項 :
for details on when an exception is thrown.

JpaRepository#getOne は EntityManager#getReference を呼び出している。

EntityManager (Java(TM) EE 8 Specification APIs)

T getReference(Class entityClass, Object primaryKey)
Get an instance, whose state may be lazily fetched.

CrudRepository#findById

CrudRepository (Spring Data Core 2.2.7.RELEASE API) - Javadoc 日本語訳

Optional findById(ID id)
ID でエンティティを取得します。

パラメーター :
id - null であってはなりません。

戻り値 :
指定された ID を持つエンティティ、または見つからない場合は Optional#empty()

例外 :
IllegalArgumentException - id が null の場合。

HogeRepository#findByFoo

今回作成する Hoge エンティティクラスの ID (主キー) である foo カラムの値を引数としてエンティティを取得するクエリメソッド。
クエリメソッドは Spring Data の命名規則に沿ったメソッド名を指定することで処理の実装が自動生成される仕組み。

Spring Data JPA 5.3. クエリメソッド - リファレンスドキュメント - 日本語訳

JPA モジュールは、クエリを文字列として手動で定義すること、またはメソッド名から派生させることをサポートしています。

6.3. データベースアクセス(JPA編) — TERASOLUNA Server Framework for Java (5.x) Development Guideline 5.5.1.RELEASE documentation

Spring Dataが定めた命名規約に則ったメソッド名にすることで実行するQuery(JPQL)を指定する。
Spring Data JPAの機能によってメソッド名からJPQLが生成される。
ただし、メソッド名からJPQLを作成できるのはSELECTのみで、UPDATEおよびDELETEのJPQLは生成できない。

サンプルコード

ソースコード一覧

├── build.gradle
├── settings.gradle
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           ├── Hoge.java
        │           ├── HogeController.java
        │           ├── HogeRepository.java
        │           └── HogeService.java
        └── resources
            ├── application.properties
            └── data.sql

build.gradle

plugins {
  id 'org.springframework.boot' version '2.2.7.RELEASE'
  id 'io.spring.dependency-management' version '1.0.9.RELEASE'
  id 'java'
}

group = 'com.example'
version = '0.0.1'
sourceCompatibility = '11'

repositories {
  mavenCentral()
}

dependencies {
  // Spring
  implementation 'org.springframework.boot:spring-boot-starter-web'
  implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  // Lombok
  compileOnly 'org.projectlombok:lombok'
  annotationProcessor 'org.projectlombok:lombok'
  // H2 Database
  runtimeOnly 'com.h2database:h2'
}

settings.gradle

rootProject.name = 'my-app'

src/main/java/com/example/Hoge.java

package com.example;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.Id;

/**
 * DB テーブルの1レコード分に相当。
 */
@Data // Lombok で getter setter など便利なメソッドを自動生成
@Entity // JPA エンティティとして扱う
public class Hoge {

  @Id // JPA にこの変数をオブジェクトの ID (主キー) だと認識させる
  private String foo;

  private String bar;
}

src/main/java/com/example/HogeRepository.java

package com.example;

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

/**
 * DB アクセス用リポジトリ。
 * Spring Data JPA が標準で提供するメソッドが自動生成される。
 */
@Repository
public interface HogeRepository extends JpaRepository<Hoge, String> { // エンティティと主キーの型を指定

  // Spring Data JPA の命名規則に沿ったクエリメソッドを定義
  // 中身が自動生成される
  public Hoge findByFoo(String foo);
}

src/main/java/com/example/HogeService.java

サービスクラス。リポジトリのメソッドをコールする。

package com.example;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@Service
@Transactional
@Slf4j // org.slf4j.Logger 型の static final 変数 log を自動生成
public class HogeService {

  @Autowired
  private HogeRepository repository;

  public Hoge getOne(String id) {
    // JpaRepository#getOne
    log.debug("Before: JpaRepository#getOne");
    Hoge hoge = repository.getOne(id);
    log.debug("After: JpaRepository#getOne");
    return hoge;
  }

  public Hoge findById(String id) {
    // CrudRepository#findById
    log.debug("Before: CrudRepository#findById");
    Optional<Hoge> opt = repository.findById(id);
    log.debug("After: CrudRepository#findById");
    return opt.orElseThrow();
  }

  public Hoge findByFoo(String foo) {
    // HogeRepository#findByFoo
    log.debug("Before: HogeRepository#findByFoo");
    Hoge hoge = repository.findByFoo(foo);
    log.debug("After: HogeRepository#findByFoo");
    return hoge;
  }
}

src/main/java/com/example/HogeController.java

コントローラークラス。サービスのメソッドをコールする。

package com.example;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@SpringBootApplication
@RestController
@Slf4j // org.slf4j.Logger 型の static final 変数 log を自動生成
public class HogeController {

  public static void main(String[] args) {
    SpringApplication.run(HogeController.class, args);
  }

  @Autowired
  private HogeService service;

  @GetMapping("/getOne/{foo}")
  public Map getOne(@PathVariable("foo") String foo) {

    // エンティティを取得
    log.debug("Before: HogeService#getOne");
    Hoge hoge = service.getOne(foo);
    log.debug("After: HogeService#getOne");

    // エンティティから値を取り出す
    log.debug("Before: Hoge#getFoo, Hoge#getBar");
    Map result = Map.of(hoge.getFoo(), hoge.getBar());
    log.debug("After: Hoge#getFoo, Hoge#getBar");
    return result;
  }

  @GetMapping("/findById/{foo}")
  public Map findById(@PathVariable("foo") String foo) {

    // エンティティを取得
    log.debug("Before: HogeService#findById");
    Hoge hoge = service.findById(foo);
    log.debug("After: HogeService#findById");

    // エンティティから値を取り出す
    log.debug("Before: Hoge#getFoo, Hoge#getBar");
    Map result = Map.of(hoge.getFoo(), hoge.getBar());
    log.debug("After: Hoge#getFoo, Hoge#getBar");
    return result;
  }

  @GetMapping("/findByFoo/{foo}")
  public Map findByFoo(@PathVariable("foo") String foo) {

    // エンティティを取得
    log.debug("Before: HogeService#findByFoo");
    Hoge hoge = service.findByFoo(foo);
    log.debug("After: HogeService#findByFoo");

    // エンティティから値を取り出す
    log.debug("Before: Hoge#getFoo, Hoge#getBar");
    Map result = Map.of(hoge.getFoo(), hoge.getBar());
    log.debug("After: Hoge#getFoo, Hoge#getBar");
    return result;
  }
}

src/main/resources/application.properties

application.properties
# Spring Framework と Hibernate ORM 等のログを出力するように指定
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql=TRACE
logging.level.org.springframework.orm.jpa=DEBUG
logging.level.com.example=DEBUG

# Open EntityManager in View パターンを使う
spring.jpa.open-in-view=true

src/main/resources/data.sql

-- 初期データを DB に追加
INSERT INTO hoge (foo, bar) VALUES ('myfoo', 'mybar');

Spring Boot アプリケーションを起動

Java 11 (AdoptOpenJDK 11.0.7+10) + Gradle 6.4.1 で Spring Boot アプリケーションを起動する。

$ gradle bootRun

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.7.RELEASE)

起動時のログを見る。

Spring Boot 2.2.7 + Spring 5.2.6 が使われている。

com.example.HogeController               : Running with Spring Boot v2.2.7.RELEASE, Spring v5.2.6.RELEASE

Hibernate ORM core version 5.4.15.Final が使われている。

o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.15.Final
o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect

エンティティクラスの定義から自動的にテーブルが作成される。

org.hibernate.SQL                        : create table hoge (foo varchar(255) not null, bar varchar(255), primary key (foo))
o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'

アクセスしてそれぞれのメソッドの挙動を見る

JpaRepository#getOne

curl でアクセスする。

$ curl http://localhost:8080/getOne/myfoo

Spring Boot のログを確認する。

JpaRepository#getOne で Hoge エンティティを取得する際にではなく、取得した Hoge エンティティのフィールドにアクセスする際に select 文が発行されている (lazy fetch による遅延取得)。

o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
com.example.HogeController               : Before: HogeService#getOne
o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(1004472706<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [com.example.HogeService.getOne]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@2c01e862]
com.example.HogeService                  : Before: JpaRepository#getOne
o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(1004472706<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager        : Participating in existing transaction
com.example.HogeService                  : After: JpaRepository#getOne
o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(1004472706<open>)]
o.s.orm.jpa.JpaTransactionManager        : Not closing pre-bound JPA EntityManager after transaction
com.example.HogeController               : After: HogeService#getOne
com.example.HogeController               : Before: Hoge#getFoo, Hoge#getBar
org.hibernate.SQL                        : select hoge0_.foo as foo1_0_0_, hoge0_.bar as bar2_0_0_ from hoge hoge0_ where hoge0_.foo=?
o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [myfoo]
o.h.type.descriptor.sql.BasicExtractor   : extracted value ([bar2_0_0_] : [VARCHAR]) - [mybar]
com.example.HogeController               : After: Hoge#getFoo, Hoge#getBar
o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor

CrudRepository#findById

curl でアクセスする。

$ curl http://localhost:8080/findById/myfoo

Spring Boot のログを確認する。

CrudRepository#findById で Hoge エンティティを取得する際に select 文が発行されている。

o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
com.example.HogeController               : Before: HogeService#findById
o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(794705340<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [com.example.HogeService.findById]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@2199c627]
com.example.HogeService                  : Before: CrudRepository#findById
o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(794705340<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager        : Participating in existing transaction
org.hibernate.SQL                        : select hoge0_.foo as foo1_0_0_, hoge0_.bar as bar2_0_0_ from hoge hoge0_ where hoge0_.foo=?
o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [myfoo]
o.h.type.descriptor.sql.BasicExtractor   : extracted value ([bar2_0_0_] : [VARCHAR]) - [mybar]
com.example.HogeService                  : After: CrudRepository#findById
o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(794705340<open>)]
o.s.orm.jpa.JpaTransactionManager        : Not closing pre-bound JPA EntityManager after transaction
com.example.HogeController               : After: HogeService#findById
com.example.HogeController               : Before: Hoge#getFoo, Hoge#getBar
com.example.HogeController               : After: Hoge#getFoo, Hoge#getBar
o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor

HogeRepository#findByFoo

curl でアクセスする。

$ curl http://localhost:8080/findByFoo/myfoo

Spring Boot のログを確認する。

HogeRepository#findByFoo で Hoge エンティティを取得する際に select 文が発行されている。

o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
com.example.HogeController               : Before: HogeService#findByFoo
o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(1242780251<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [com.example.HogeService.findByFoo]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@44d4b569]
com.example.HogeService                  : Before: HogeRepository#findByFoo
org.hibernate.SQL                        : select hoge0_.foo as foo1_0_, hoge0_.bar as bar2_0_ from hoge hoge0_ where hoge0_.foo=?
o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [myfoo]
o.h.type.descriptor.sql.BasicExtractor   : extracted value ([foo1_0_] : [VARCHAR]) - [myfoo]
o.h.type.descriptor.sql.BasicExtractor   : extracted value ([bar2_0_] : [VARCHAR]) - [mybar]
com.example.HogeService                  : After: HogeRepository#findByFoo
o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(1242780251<open>)]
o.s.orm.jpa.JpaTransactionManager        : Not closing pre-bound JPA EntityManager after transaction
com.example.HogeController               : After: HogeService#findByFoo
com.example.HogeController               : Before: Hoge#getFoo, Hoge#getBar
com.example.HogeController               : After: Hoge#getFoo, Hoge#getBar
o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor

参考資料

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