最終更新日:2020/03/22 原本2019-07-01

Spring Bootでデータベースにアクセスしよう

Spring Bootで作るマイクロサービス 第5回

 前回、サンプルアプリケーションを通じて、Spring MVC(Webインターフェース)で利用する基本的なアノテーションについて紹介しました。今回も前回利用したサンプルアプリケーションを通じて、Spring Bootでのデータベースを扱う際の設定や基本的操作について紹介します。

サンプルアプリケーションの概要

 サンプルアプリケーションは、前回 と同様で、図1に示す簡単な住所録管理アプリケーションです。

図1:サンプルアプリケーションのイメージ
図1:サンプルアプリケーションのイメージ

 このアプリケーションでは、表1のAPIを提供します。

表1:提供するAPI
エンドポイント HTTPメソッド 概要    
/contact/add  POST 新規に連絡先データを登録する
/contact/list  GET 登録してあるデータ一覧を取得する
/contact/item/{id} GET 指定したidの連絡先データ情報を取得する
/contact/item/{id} POST 指定したidの連絡先データ情報を更新する
/contact/item/{id} DELETE 指定したidの連絡先データを削除する

Spring Bootでデータベースを扱う

 Spring Bootでデータベース操作をする場合には、表2に示すSpring JDBCもしくは、Spring DATA JPAがあります。

表2:Springで提供しているデータベース関連のプロジェクト
名称   利用するスターター   概要    
Spring JDBC  org.springframework.boot:spring-boot-starter-jdbc プログラム内にSQLを記述してデータベース操作を行う
Spring Data JPA  org.springframework.boot:spring-boot-starter-data-jpa ORMの仕組みを用いてデータベース操作を行う

 Spring JDBC、もしくはSpring Data JPAのどちらを選ぶかは、プロジェクトでの好みにもよりますが、JPAを使ったプログラムは、複雑なテーブル構造やバイナリデータなども含んだテーブルなどを扱う際や性能問題にぶつかった場合に問題解決が難しく、直接SQLを扱うSpring JDBCではプログラムが煩雑になりやすいといった問題があります。

 データベースを利用する上ではプログラムしやすいことだけではなく、運用も重要になるので、これらのプロジェクトを使わず、既に使い慣れたライブラリを使っても良いでしょう。

 今回のサンプルでは、Spring DATA JPAを用いた簡単な操作について説明します。また、データベースでは組み込みのH2データベースを用いるので、リスト1のようにGradleのビルドファイルに指定します。

[リスト1]Spring Data JPAを利用する場合のGradleビルドスクリプト(build.gradleの抜粋)
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // (1)Spring Data JPAを使う設定
    : (省略)
    runtimeOnly 'com.h2database:h2' // (2)H2データベースを使う設定
}

 (1)では、Spring Data JPAのスターターを指定します。このスターターを指定することで、依存しているHikariCPやHibernate ORMなどの関連するライブラリも含まれます。

 ただし、データベースのドライバに関するライブラリは含まれないため、(2)のように別にH2のライブラリを指定する必要があります。

接続するデータベースへの設定を行う

 Spring Bootではデータベースへの接続設定は設定ファイル(application.properties)を用います。リスト2はサンプルコードで使用している設定です。

[リスト2]データベースへの接続設定(src/main/resources/application.propertiesの抜粋)
spring.datasource.type=com.zaxxer.hikari.HikariDataSource // (1)使用するデータソースのクラス名(省略した場合と同じ設定)
spring.datasource.driver-class-name=org.h2.Driver         // (2)データベースドライバのクラス名
spring.datasource.url=jdbc:h2:mem:contacts                // (3)JDBCでの接続URL
spring.datasource.username=h2user                         // (4)データベースへの接続ユーザ
spring.datasource.password=                               // (5)データベースへの接続パスワード

 (1)では、使用するデータソースクラスを指定します。Spring Boot 2.xではHikariCPがデフォルトで使われますので、指定しない場合と同じ設定になっています。

 ただし、Spring Boot 1.xを使っている場合には、デフォルトで使われるデータソースが変更されているのでバージョンアップをする場合には注意が必要です。

 (2)はデータベースのドライバの設定をします。(3)ではJDBCでの接続URLを設定します。(4)(5)でデータベースへの接続ユーザとパスワードを設定します。

 これらの設定は、サンプルなのでh2のインメモリデータベースを使うようにしていますが、実際は外部のデータベースを使うことが多いと思います。

 また、今回設定している項目はJDBCとしてデータベースに接続するために必要な設定のみですが、実際には、図2のようにデータソースの実装としてコネクションプール機能があるので、それらの設定も行えます。

図2:コネクションプールと接続の関係
図2:コネクションプールと接続の関係

 最近のORマッパーなどのライブラリを利用していると、プログラマがデータベースの接続を意識せずに利用できるため、コネクションプールの機能についてあまり実感がない方もいると思います。

 コネクションプールは実際のデータベース接続を事前に行っておき、プログラム側が使う際にはそれらの接続を使いまわします。これによってデータベース接続にかかる時間やリソースの節約になります。

 ただし、実際の運用時にはリソースに応じた設定が必要になり、リスト3のように指定可能です。

[リスト3]HikariCPに関する設定(src/main/resources/application.propertiesの抜粋)
spring.datasource.hikari.connectionTimeout = 30000
spring.datasource.hikari.idleTimeout = 600000

 また、主に表3の設定が行えます。ここでは省略した設定の詳細はHikariCPのサイトにあるので、こちらもご参照ください。

表3:HikariCPを利用する際に設定できる項目
設定キー 概要    
connectionTimeout 接続時のタイムアウト。デフォルトは3万ミリ秒(30秒)
minimumIdle
アイドル状態での最低プールサイズ
maximumPoolSize 最大接続数
idleTimeout アイドル状態で生存できる時間。デフォルトは60万ミリ秒(10分)
maxLifetime 最大利用時間。デフォルトは180万ミリ秒(30分)
connectionTestQuery  接続しているかどうかの確認クエリの設定。ドライバがJDBC4をサポートしている場合には指定はいらない

データベースの初期化設定

 Spring Bootでは、データベースの初期化を起動時に行うことが可能です。リスト4は、初期化を行う際のサンプルコードです。

[リスト4]データベースの初期化処理指定(src/main/resources/application.propertiesの抜粋)
# (1)初期化を行うかの指定
spring.datasource.initialization-mode=always
# (2)初期化を行う際のDDLの実行モード
spring.jpa.hibernate.ddl-auto=update

 (1)は初期化を行うかの指定です。「always(初期化を行う)」「embedded(組み込みDB時に行う)」「never(行わない)」の指定が可能です。 省略も可能ですが、その場合には、その他の指定に従い自動的に初期化が行われるか、行われないかが決まります。そのため、初期化指定を行っているのに実行されない場合があるので、利用する場合には明示的に指定を行った方が良いでしょう。

 (2)では、DDLを実行する際のモードを指定します。指定できるのは表4の通りです。

表4:Entityに対応するテーブル初期化に対する指定
設定値 説明
none 何も行わない
validate  検証のみ。実際の操作は何も行わない
update 必要なEntityに相当するテーブルがない場合のみ作成する
create 必要なEntityに相当するテーブルを作成する。既にテーブルが存在する場合にはテーブルが削除された後、新規に作成する
create-drop 必要なEntityに相当するテーブルを作成する。セッション終了時にはそれらのテーブルは削除される

 例えば、リスト5は“create”に指定した場合に自動的に流れるSQLのログを示したものです。

[リスト5]"create"を指定した時に実行されるSQL(DDL)
Hibernate: drop table contact if exists
Hibernate: drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table contact (id integer not null, address varchar(255) not null, name varchar(255) not null, tel varchar(255) not null, zip varchar(255) not null, primary key (id))

 実際の運用でnoneやvalidate以外を使う場合には実行されるSQLを細かく指定することは難しく、フィールドの順番や細かいデフォルトの指定などがある場合にはDDLファイルを指定して実行した方が便利なケースもあります。 その場合には、表5のようなルールでファイルをクラスパス以下に置くここで、自動で実行されます。ただし、spring.datasource.initialization-modeの値が「always」に指定されている必要があります。

表5:データベース初期化で実行されるファイル名
ファイル名 説明   
schema.sql  スキーマを作成するためのファイル。クラスパス以下に保存(spring.datasource.schemaの設定でファイル名の変更が可能) 
data.sql  初期データなどがある場合に、insert分を実行するためのファイル。クラスパス以下に保存(spring.datasource.dataの設定でファイル名の変更が可能)

Spring Data JPAを使ってテーブルにアクセスする

 Spring Data JPAでは、図3のようにテーブルに相当するEntityクラスとそのテーブルにアクセスするためのRepositoryクラスを用いてデータの管理を行います。

 ただし、プログラマはRepositoryクラスの実装ではなくインターフェースの定義だけを行います。

図3:Spring Data JPAを使う場合のクラス・インターフェース・アノテーションの関係
図3:Spring Data JPAを使う場合のクラス・インターフェース・アノテーションの関係

Entityクラスを作成する

 テーブルに相当するEntityクラスを作成します。リスト6は、サンプルアプリケーションで住所録を管理するためのcontactテーブルに相当するクラスです。

[リスト6]Entityクラスの実装例(src/main/java/com/coltware/contacts/data/ContactModel.javaの抜粋)
import javax.persistence.*;

@Entity // (1)
@Table(name = "contact") // (2)テーブル指定
public class ContactModel {

    @Id // (3)IDを指定する
    @GeneratedValue // (4)自動生成される値
    private Integer id;

    // (5)各フィールドを定義する
    @Column(name = "zip", nullable = false)
    private String zip;

    @Column(name = "address", nullable = false)
    private String address;

    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "tel", nullable = false)
    private String tel;

    : (以下省略 setter/getterを定義する)
}

 (1)では、指定したクラスがEntityクラスであることを示すアノテーションを指定します。(2)では対応するテーブル名を指定します。クラス名と同じテーブル名であれば省略可能ですが、筆者は必ず明示的に指定するようにしています。

 (3)は、指定したフィールドが主キーであることを示します。(4)は、主キーの値が自動生成される値であることを示しています。

 また、(5)ではカラムであることを指定します。指定できる項目には主に表6のような項目があります。

表6:@Columnアノテーションに指定できる主な属性
属性キー 説明
name  カラム名を指定。省略した場合には変数名と同じと見なされる
unique ユニーク属性が必要な場合にはtrueを設定する。デフォルトはfalse
nullable  NULL値であることが許されるかどうかの指定。NULL値が許されない場合にはfalseにする。デフォルトはtrue
length 文字列の場合に、文字列長をいくつにするのかの指定。デフォルトは255
columnDefinition カラム定義を手動にて場合に指定する。この指定を利用するのであれば、前述したschema.sqlなどを使うことを検討した方が良いと思われる
insertable NSERT時にカラムとして含むかどうかの指定。デフォルトはtrue
updatable  UPDATE時にカラムとして含むかどうかの指定。デフォルトはtrue

 また、Javaで指定したフィールドの型はテーブル上の型に自動的にマッピングされます。そのマッピングはHibernate ORMのドキュメントを見ると詳しくわかります。

EntityクラスにアクセスするためのRepositoryインターフェースを定義する

 Entityクラスに相当するテーブルデータの取得や追加・保存・削除などは、JpaRepositoryインターフェースを継承することで行います。

 今回のサンプルアプリケーションでは基本的なCRUD(Create/Read/Update/Delete)操作のみのため、リスト7のように、具体的なコードは何も実装する必要はありません。

[リスト7]リポジトリクラスの実装例(src/main/java/com/coltware/contacts/data/ContactRepository.javaの抜粋)
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository // (1)
// (2) 扱うEntityクラスと主キーの型を指定
public interface ContactRepository extends JpaRepository<ContactModel,Integer> {
}

 (1)では、このインターフェースがJPAに置けるRepositoryであることを指定します。また、JpaRepositoryを継承する際には(2)のように扱うEntityクラスであるContactModelクラスとそのクラスの主キーの方であるIntegerを指定します。

データの取得

 作成したContactRepositoryを使ってデータを取得する場合にはfindById、findOneやfindAllといったメソッドを利用します。リスト8はこれらの利用例です。

[リスト8]データの取得例(src/main/java/com/coltware/contacts/controller/ContactController.javaの抜粋)
// (省略)
public class ContactController {
    // (省略)
    // (1)Repositoryインスタンスを取得する
    @Autowired
    ContactRepository contactRepository;

    // (省略)

    @GetMapping("/item/{id}")
    public Response itemAction(@PathVariable("id") String id){
        : (省略)
        // (2)主キーを指定した値の取得
        Optional<ContactModel> model = contactRepository.findById(rid);
        : (省略)
    }
    : (省略)
    @GetMapping("/list")
    public Response listAction(){
        // (3)全件データの取得
        List<ContactModel> list = contactRepository.findAll();
        return Response.createSuccessResponse(list);
    }
}

 (1)ではContactRepositoryを取得します。また、(2)で主キーを使った取得メソッドであるfindByIdを使ってデータを取得します。

 また、すべてのデータを取得するには、(3)のようにfindAllメソッドで取得できます。

データの追加・更新・削除

 データの登録と更新は、いずれもsaveもしくはsaveAndFlushメソッドにて行います。また、削除はdeleteもしくは、deleteByIdメソッドなどを利用します。

 リスト9はデータの登録と更新、削除を行うコード例です。

[リスト9]データの追加・更新・削除例(src/main/java/com/coltware/contacts/controller/ContactController.javaの抜粋)
public Response addAction(@RequestBody(required = false) ContactModel item){
    // (1)データの登録
    contactRepository.save(item);
    return Response.createSuccessResponse(item);
}
// : (省略)
public Response updateAction(@PathVariable("id") String id,@RequestBody(required = false) ContactModel item){

    // (2)更新対象の取得
    Optional<ContactModel> model = contactRepository.findById(rid);
    // : (省略)
    ContactModel updateModel = model.get();
    updateModel.setZip(item.getZip());
    updateModel.setAddress(item.getAddress());
    updateModel.setName(item.getName());
    updateModel.setTel(item.getTel());

    // (3)更新
    contactRepository.save(updateModel);
}
// : (省略)
public Response deleteAction(@PathVariable("id") String id){

    Optional<ContactModel> model = contactRepository.findById(rid);
    // : (省略)
    // (4)削除
    contactRepository.delete(model.get());
}

 (1)ではJSON形式から自動的にContactModelオブジェクトに変換したデータを用いてデータを登録しています。

 この際、IDが指定されていないことから、自動的にsaveメソッドがデータ登録を行います。また更新では、(2)のようにIDを指定しオブジェクトを指定し、それぞれの変更データを設定し、(3)のsaveメソッドで更新を行います。

 オブジェクトにいはIDが指定されていることから自動的にデータは更新されます。また、(4)のようにdeleteメソッドで削除可能です。

 ここで説明した以外にもデータ数を取得するcountメソッドやその他のメソッドもありますので、JpaRepositoryのJavaDocを参照してみてください。

最後に

 Spring Bootでのデータベース操作についても、前回同様、今回だけでは紹介しきれない内容がまだまだたくさんあります。

 プログラム部分ではSpring Data JPAを用いた簡単なデータ操作のみについて紹介しましたが、トランザクションを使った操作や、特定の条件を指定した場合のデータ取得など実際の利用時にまだ多くのことを理解する必要があります。

 ただし、これらは、Spring Bootというよりは個別のプロジェクトの内容になるため、本稿では詳しく紹介しません。JPAについて詳しく知りたい方は、その他のブログ、書籍やSpring Data JPAのリファレンスドキュメントなども併せてご覧ください。

参考資料