前回、前々回でRDS DBインスタンスを作成し、RDSにテーブルを構築した上で、各設定クラスを実装するところまで解説しました。続く今回は、いよいよアプリケーションの処理実装に進みます。

エンティティクラス、レポジトリクラスの実装

まず、前回作成した以下のテーブルに対応したエンティティクラスを実装します。

前回作成したテーブル構成

テーブルに対応したJavaクラスを作成し、JPA標準のパッケージjavax.persistenceのアノテーションを付与します。代表的なアノテーションの概要は以下の通りです。

アノテーション 設定 概要
@Entity クラス エンティティとして表現するクラスに付与する
@Table クラス エンティティとマッピングする物理テーブル名を指定する
@Embeddable クラス 主に複合IDとして表現する組み込みクラスに付与する
@Id フィールド メソッド エンティティクラスのプライマリキーのフィールドに指定する
@EmbeddedId フィールド メソッド エンティティクラスの複合IDプライマリーキーのフィールドに指定する
@Column フィールド メソッド フィールドとデータベースのカラム名のマッピングを定義する
@Basic フィールド メソッド LazyFetchオブションなど指定する
@JoinColumns フィールド メソッド @JoinColumnを複数同時に記述する場合に利用する
@JoinColumn フィールド メソッド 結合のための外部キーカラム及び外部参照制約オプションを指定する
@PraimaryKeyJoinColums フィールド メソッド @PrimaryKeyJoinColumnを複数同時に記述する場合に利用する
@PraimaryKeyJoinColumn フィールド メソッド ほかのテーブルと結合する場合に、外部キーとして扱われるカラム
@OneToOne フィールド メソッド OneToOneリレーションシップであることを示す場合に付与する
@OneToMany フィールド メソッド OneToManyリレーションシップであることを示す場合に付与する
@ManyToOne フィールド メソッド ManyToOneリレーションシップであることを示す場合に付与する
@AttributeOverrides フィールド メソッド @AttributeOverrideを複数記述する場合に利用する
@AttributeOverride フィールド メソッド マッピング情報をオーバーライドする
@Transient フィールド メソッド 永続化しないエンティティクラス、マップドスーパークラス、または組み込みクラスのフィールドであることを示す
@Temporal フィールド メソッド 時刻を示す型(java.util.Dateおよび、java.util.Calendar)を持つフィールドに付与する
@GeneratedValue フィールド メソッド プライマリーキーカラムにユニークな値を自動生成、付与する方法を指定する
@NamedQuery クラス JPQLの名前付きクエリを指定する

上記の表を参考に、各テーブルごとに対応したエンティティクラスを作成します。一例としてユーザエンティティクラスの実装例を一部抜粋して以下に掲載しますが、各テーブルごとのエンティティクラスの実装の詳細は、エンティティクラスのパッケージを参照してください。

package org.debugroom.mynavi.sample.aws.rds.domain.model.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Objects;

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name = "usr", schema = "public", catalog = "sample_database")
public class User {

    private long userId;
    private String firstName;
    private String familyName;
    private String loginId;
    private Boolean isLogin;
    private Integer ver;
    private Timestamp lastUpdatedAt;
    private Address addressByUserId;
    private Collection emailsByUserId;
    private Collection membershipsByUserId;

    // omit

    @Id
    @Column(name = "user_id", nullable = false)
    public long getUserId() {
        return userId;
    }

    @Basic
    @Column(name = "first_name", nullable = true, length = -1)
    public String getFirstName() {
        return firstName;
    }

    @Basic
    @Column(name = "family_name", nullable = true, length = -1)
    public String getFamilyName() {
        return familyName;
    }

    @Basic
    @Column(name = "login_id", nullable = true, length = -1)
    public String getLoginId() {
        return loginId;
    }

    @Basic
    @Column(name = "is_login", nullable = true)
    public Boolean getLogin() {
        return isLogin;
    }

    @Basic
    @Column(name = "ver", nullable = true)
    @Version
    public Integer getVer() {
        return ver;
    }

    @Basic
    @Column(name = "last_updated_at", nullable = true)
    public Timestamp getLastUpdatedAt() {
        return lastUpdatedAt;
    }

    @OneToOne(mappedBy = "usrByUserId", cascade=CascadeType.ALL)
    public Address getAddressByUserId() {
        return addressByUserId;
    }

    @OneToMany(mappedBy = "usrByUserId", cascade=CascadeType.ALL)
    public Collection getEmailsByUserId() {
        return emailsByUserId;
    }

    @OneToMany(mappedBy = "usrByUserId", cascade = CascadeType.ALL)
    public Collection getMembershipsByUserId() {
        return membershipsByUserId;
    }
 }

続いて、ユーザーテーブルへアクセスするコンポーネントであるインタフェースUserRepositoryは、以下のように実装しておく必要があります。

  • org.springframework.data.jpa.repository.JpaRepositoryを継承する。
  • テーブルをモデル化したクラスとキーをJpaRepositoryの型パラメータに設定する。

各テーブルごとのレポジトリクラスの実装については、レポジトリクラスのパッケージを参照してください。

package org.debugroom.mynavi.sample.aws.rds.domain.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.debugroom.mynavi.sample.aws.rds.domain.model.entity.User;

public interface UserRepository extends JpaRepository<User, Long> {
}

なお、特に実装クラスを作成せず、インタフェースだけでfindAllやsaveメソッドを実行できるのは、Spring Data JPAが、GenericDAOパターンに基づく実装クラスを提供しているためです。

GenericDAOパターンとは、Javaの型パラメータを利用した実装で、CRUD処理をJavaのジェネリクス機構を使って実装しておき、テーブルの種類に応じた型パラメータクラスを設定することで、汎用的なCRUD共通処理を実装したDAO(DatabaseAccessObject)を作成しておくというものです。

Spring Data JPAに限らず、「Spring Data XXX」など類似する一連のプロダクトでは、同様にインタフェースを作成するだけで、基本的なCRUDはほぼ実装せずにデータベースアクセス処理を行うことが可能になります。

Serviceクラスの実装

最後にトランザクション境界が設定されるServiceクラスです。@Transactionalアノテーションと@Serviceアノテーションを付与したService実装クラスを作成します。こちらも一部の抜粋になりますが、この処理では各テーブルにデータを保存する処理を記述しています。詳細については、サービスクラスを参照してください。

package org.debugroom.mynavi.sample.aws.rds.domain.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import org.debugroom.mynavi.sample.aws.rds.domain.model.entity.*;
import org.debugroom.mynavi.sample.aws.rds.domain.repository.*;

@Transactional
@Service
public class SampleServiceImpl implements SampleService{

    @Autowired
    UserRepository userRepository;

    @Autowired
    GroupRepository groupRepository;

    @Override
    public void init(){
        userRepository.deleteAll();
        groupRepository.deleteAll();
    }

    @Override
    public void setData() {
        init();
        // omit
        userRepository.saveAll(Arrays.asList(new User[]{user1, user2}));
    }

※ ユーザーとグループのエンティティクラスには、関連テーブルの属性に「CascadeType.ALL」を指定しているため、関連テーブルを含めてデータの追加/削除が行われます。

アプリケーション起動クラスを実行した後、PSQLクライアントからRDSにアクセスすると、各テーブルにデータが追加されていることを確認できます。


sample_database=> select * from usr;
 user_id | first_name | family_name |    login_id    | is_login | ver |     last_updated_at
---------+------------+-------------+----------------+----------+-----+-------------------------
       0 | taro       | mynavi      | taro-account   | f        |   0 | 2019-04-11 01:31:26.064
       1 | hanako     | mynavi      | hanako-account | f        |   0 | 2019-04-11 01:31:26.064
(2 rows)


sample_database=> select * from grp;
 group_id |  group_name   | ver |     last_updated_at
----------+---------------+-----+-------------------------
        0 | mynavi-group1 |   0 | 2019-04-11 01:31:25.996
        1 | mynavi-group2 |   0 | 2019-04-11 01:31:25.996
(2 rows)

sample_database=> select * from membership ;
 user_id | group_id | ver |     last_updated_at
---------+----------+-----+-------------------------
       0 |        0 |   0 | 2019-04-11 01:31:26.064
       0 |        1 |   0 | 2019-04-11 01:31:26.064
       1 |        0 |   0 | 2019-04-11 01:31:26.064
(3 rows)

sample_database=> select * from address;
 user_id | zip_code |     address     | ver |     last_updated_at
---------+----------+-----------------+-----+-------------------------
       0 | 100-0000 | Tokyo Chiyodaku |   0 | 2019-04-11 01:31:26.064
       1 | 100-0000 | Tokyo Chuouku   |   0 | 2019-04-11 01:31:26.064
(2 rows)

sample_database=> select * from email;
 user_id | email_no |     email      | ver |     last_updated_at
---------+----------+----------------+-----+-------------------------
       0 |        0 | test@test.com  |   0 | 2019-04-11 01:31:26.064
       0 |        1 | test1@test.com |   0 | 2019-04-11 01:31:26.064
       1 |        0 | test2@test.com |   0 | 2019-04-11 01:31:26.064
       1 |        1 | test3@test.com |   0 | 2019-04-11 01:31:26.064
(4 rows)

* * *

以上、3回にわたり、AWS RDSを構築し、Spring Data JPAおよびSpring Cloud AWSを使ってデータベース・テーブルにアクセスするSpringアプリケーションを構築してきました。RDSへSpringアプリケーションで接続する場合、各フレームワークが提供する機能やデータアクセスの仕組みを押さえた上で実装しておくことが重要です。

著者紹介


川畑 光平(KAWABATA Kohei)

某システムインテグレータにて、金融機関システム業務アプリケーション開発・システム基盤担当を経て、現在はソフトウェア開発自動化関連の研究開発・推進に従事。

Red Hat Certified Engineer、Pivotal Certified Spring Professional、AWS Certified Solutions Architect Professionalなどの資格を持ち、アプリケーション基盤・クラウドなどさまざまな開発プロジェクト支援にも携わる。

本連載の内容に対するご意見・ご質問は Facebook まで。