Help us understand the problem. What is going on with this article?

SpringBoot入門ガイドやってみた【Accessing Data with JPA編】

目的

Spring Quickstart Guideを取り組み終えた方、SpringBootを学び始めた方、復習をしたい方に向けて、

公式が人気ガイドだからやってみて!と勧めてくれている、Accessing Data with JPAを実際に取り組み学んだことを共有します。

開発環境
OS: macOS Mojave バージョン10.14.6
テキストエディタ: Visual Studio Code(以下VSCode)
Java: 11.0.2

QuickstartGuideのおさらいはこちらから
Building a RESTful Web Service編のおさらいはこちらから
Consuming a RESTful Web Service編のおさらいはこちらから

1.SpringBoot projectを始めよう!

まずは、spring initializrにアクセスします。

1.ADD DEPENDENCIESボタンをクリックして、Spring Data JPAH2 Databaseを追加。
スクリーンショット 2020-07-06 13.52.15.png
スクリーンショット 2020-07-06 13.52.27.png

Spring Data JPA(Java Persistence API)とは、Javaのオブジェクトをデータベースに保存したり取り出したりするためのAPIです(O/Rマッピングしてくれるもの)。

H2 Databasetohaとは、Javaに標準で用意されているデータベース。ここで追加することによりMySQLなど別途サーバーを立てる必要がないので便利ですね。

2.Artifact, Nameは、accessing-data-jpaに変更。
3.Javaを11に変更。

そしてGENERATEボタンをクリックしてZipファイルをダウンロードします。

スクリーンショット 2020-07-06 13.55.11.png

ダウンロードしたZipファイルを展開したら準備完了です。

2.コードを追加しよう!

先ほどのフォルダをVSCodeで開きます。
拡張機能のJava Extension Packのインストールの推奨します。と言われるのでインストールしておきましょう。(未インスールの方のみ)

スクリーンショット 2020-06-30 10.08.25.png

Customer.javaを作成しよう!

src/main/java/com/example/accessingdatajpa/にCustomer.javaファイルを作成します。

スクリーンショット 2020-07-06 14.11.51.png

Customer.javaファイル内にコードを追加していきます。

Customer.java
package com.example.accessingdatajpa;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Customer {

  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  private Long id;
  private String firstName;
  private String lastName;

  protected Customer() {}

  public Customer(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  @Override
  public String toString() {
    return String.format(
        "Customer[id=%d, firstName='%s', lastName='%s']",
        id, firstName, lastName);
  }

  public Long getId() {
    return id;
  }

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }
}

Customer.javaファイルに追加したコードを深掘りしていきます。

@Entity

Customer.java
@Entity
public class Customer {
  // 省略
}

@Entityアノテーションは、DBのテーブルと結びつくクラスとなります(エンティティクラス)。
このエンティティクラスで定義された変数は、DBのカラムと結びつきます。

@Id@GeneratedValue(strategy=GenerationType.AUTO)、コンストラクタ

Customer.java
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;

protected Customer() {}

public Customer(String firstName, String lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

@Idアノテーションは、付与されている変数がテーブルの主キーになります。
また、@GeneratedValue(strategy=GenerationType.AUTO)アノテーションで、idが連番で自動的に生成してくれるようになります。

今回のCustomerテーブルは、主キーがid、その他のカラムはfirstNameとlastNameが存在するという事です。

エンティティクラスの要件として、引数のないコンストラクタが必要ですので、引数なしのコンストラクタを定義、
引数があるコンストラクタは、DBに保存するインスタンスを生成する時の為に定義しています。

③toStringメソッド、ゲッターメソッドの定義

Customer.java
@Override
public String toString() {
  return String.format(
     "Customer[id=%d, firstName='%s', lastName='%s']",
      id, firstName, lastName);
}

public Long getId() {
  return id;
}

public String getFirstName() {
  return firstName;
}

public String getLastName() {
  return lastName;
}

toStringメソッドは、java.lang.Objectクラスで定義されている文字列表現を返すメソッドです。
@Overrideアノテーションは、Objectクラスで定義されているtoStringメソッドをこのクラスでオーバーライドしています。と明示するためのアノテーションです。

正しくオーバーライドされていなければエラーになります。
よって、toStrign()のようにタイプミスをしてしまっていると、コンパイル時にエラーが出て教えてくれます。

今回は、idとfirstNameとlastNameを表示するための文字列を返すメソッドにオーバーライドしています。

String.formatというメソッドを使用する際に、第一引数に決められた書式を指定しなければいけないため、
%sは、文字列、%dは10進数整数と書式を指定しています。

後は、それぞれの変数を取得するためのゲッターメソッドを定義しています。

CustomerRepository.javaを作成しよう!

src/main/java/com/example/accessingdatajpa/にCustomerRepository.javaファイルを作成します。

スクリーンショット 2020-07-07 12.01.41.png

CustomerRepository.javaファイル内にコードを追加していきます。

CustomerRepository.java
package com.example.accessingdatajpa;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

public interface CustomerRepository extends CrudRepository<Customer, Long> {

  List<Customer> findByLastName(String lastName);

  Customer findById(long id);

}

CustomerRepository.javaファイルに追加したコードを深掘りしていきます。

①リポジトリインターフェースを作成する

CustomerRepository.java
public interface CustomerRepository extends CrudRepository<Customer, Long> {
  // 省略
}

CustomerRepositoryというインターフェースを作成しています。その時にCrudRepositoryというインターフェースを継承しています。
CrudRepositoryインターフェースは引数にエンティティの型であるCustomer、IDの型であるLongをジェネリックパラメーターとして指定しています。

CrudRepositoryインターフェースは、Create、Read、Update、Delete(CRUD)という操作が出来るインターフェースです。
よって、CustomerRepositoryインターフェースも上記操作が出来るようになります。

②メソッドの実装

CustomerRepository.java
List<Customer> findByLastName(String lastName);

Customer findById(long id);

Spring Data JPAでは、findBy〇〇と特定の命名規則を満たすメソッドはその内容からクエリメソッドとして定義されます。

今回は、findByLastName(String lastName)が該当します。引数で受け取ったlastNameと一致するデータを全て取得する事が可能なメソッドです。
findById(long id)は、特定のidと一致するデータを取得する事が可能なメソッドです。

AccessingDataJpaApplication.javaを編集しよう!

デフォルトの状態は以下のようになっていると思います。

AccessingDataJpaApplication.java
package com.example.accessingdatajpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AccessingDataJpaApplication {

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

}

公式を参考にしつつコードを追加します。

AccessingDataJpaApplication.java
package com.example.accessingdatajpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 追加したコード
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class AccessingDataJpaApplication {

  // 追加したコード
  private static final Logger log = LoggerFactory.getLogger(AccessingDataJpaApplication.class);

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

  // 追加したコード
  @Bean
  public CommandLineRunner demo(CustomerRepository repository) {
    return (args) -> {
      // save a few customers
      repository.save(new Customer("Jack", "Bauer"));
      repository.save(new Customer("Chloe", "O'Brian"));
      repository.save(new Customer("Kim", "Bauer"));
      repository.save(new Customer("David", "Palmer"));
      repository.save(new Customer("Michelle", "Dessler"));

      // fetch all customers
      log.info("Customers found with findAll():");
      log.info("-------------------------------");
      for (Customer customer : repository.findAll()) {
        log.info(customer.toString());
      }
      log.info("");

      // fetch an individual customer by ID
      Customer customer = repository.findById(1L);
      log.info("Customer found with findById(1L):");
      log.info("--------------------------------");
      log.info(customer.toString());
      log.info("");

      // fetch customers by last name
      log.info("Customer found with findByLastName('Bauer'):");
      log.info("--------------------------------------------");
      repository.findByLastName("Bauer").forEach(bauer -> {
        log.info(bauer.toString());
      });
      // for (Customer bauer : repository.findByLastName("Bauer")) {
      //  log.info(bauer.toString());
      // }
      log.info("");
    };
  }

}

AccessingDataJpaApplication.javaファイルに追加したコードを深掘りしていきます。

①ログ出力のためのlog

AccessingDataJpaApplication.java
// 追加したコード
private static final Logger log = LoggerFactory.getLogger(AccessingDataJpaApplication.class);

ターミナルでログを表示するためにLogger、LoggerFactoryを用いています。
LoggerFactory.getLogger()の引数にクラスを指定してログを取得することが出来ます。

②データの登録、取得

AccessingDataJpaApplication.java
// 追加したコード
@Bean
public CommandLineRunner demo(CustomerRepository repository) {
  return (args) -> {
    // save a few customers
    repository.save(new Customer("Jack", "Bauer"));
    repository.save(new Customer("Chloe", "O'Brian"));
    repository.save(new Customer("Kim", "Bauer"));
    repository.save(new Customer("David", "Palmer"));
    repository.save(new Customer("Michelle", "Dessler"));

    // fetch all customers
    log.info("Customers found with findAll():");
    log.info("-------------------------------");
    for (Customer customer : repository.findAll()) {
      log.info(customer.toString());
    }
    log.info("");

    // fetch an individual customer by ID
    Customer customer = repository.findById(1L);
    log.info("Customer found with findById(1L):");
    log.info("--------------------------------");
    log.info(customer.toString());
    log.info("");

    // fetch customers by last name
    log.info("Customer found with findByLastName('Bauer'):");
    log.info("--------------------------------------------");
    repository.findByLastName("Bauer").forEach(bauer -> {
      log.info(bauer.toString());
    });
    // for (Customer bauer : repository.findByLastName("Bauer")) {
    //  log.info(bauer.toString());
    // }
    log.info("");
  };
}

データの登録、取得を行なっている
CommandLineRunner demo(CustomerRepository repository)というメソッドは、SpringBootがアプリ実行後に呼び出してくれます。

その中でまずは、データの登録を行なっています。
Customerをインスタンス化する際にコンストラクタの引数に文字列(firstName、lastName)を渡しています。
そしてrepositoryのsaveメソッドでその生成されたJavaオブジェクトをCustomerテーブルに登録しています。

repository.save(new Customer("Jack", "Bauer"));

次に、findAll()で全てのデータを取得しています。
拡張for文でループを回して、取得した複数のデータを引数customerで受け取り1つずつ出力しています。
出力する際にオーバーライドしたtoString()を使用しています。

for (Customer customer : repository.findAll()) {
  log.info(customer.toString());
}

次に、findById(1L)でIDが1のデータを取得しています。
これは特定の1つのデータしか取得できないので、ループを回さずに出力しています。

// fetch an individual customer by ID
Customer customer = repository.findById(1L);
log.info(customer.toString());

最後に、findByLastName("Bauer")で、lastNameの値がBauerのデータを取得しています。
このデータは複数ある可能性があるので、List型で受け取るよう定義されていましたね。

取得した複数のデータをforEach文でループを回して、引数bauerで受け取り1つずつ出力しています。
コメントアウトされている拡張for文は、こちらの書き方でも良いですよという事だと思います。

// fetch customers by last name
repository.findByLastName("Bauer").forEach(bauer -> {
    log.info(bauer.toString());
});
// for (Customer bauer : repository.findByLastName("Bauer")) {
  //  log.info(bauer.toString());
// }

3.実行してみよう!

アプリケーション実行の準備が出来たので確認しましょう。

ターミナルで以下のコマンドを入力してEnterしてください。

ターミナル
$ ./mvnw spring-boot:run

しばらくすると、ターミナルに以下の文字が出てきます。

ターミナル
Customers found with findAll():
-------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=2, firstName='Chloe', lastName='O'Brian']
Customer[id=3, firstName='Kim', lastName='Bauer']
Customer[id=4, firstName='David', lastName='Palmer']
Customer[id=5, firstName='Michelle', lastName='Dessler']

Customer found with findById(1L):
--------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']

Customer found with findByLastName('Bauer'):
--------------------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=3, firstName='Kim', lastName='Bauer']

参考サイト

JPA関連アノテーションの基本として-その1-
エンティティークラスの要件
CrudRepository
【Spring Data JPA】自動実装されるメソッドの命名ルール

morioheisei
ふざけた名前ですが常に本気です。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away