【第13回】RDSへのテーブル構築とSpring Data JPAおよびSpring Cloud AWSを用いたRDSアクセスアプリケーションの実装(2)


クラウド上のリレーショナルデータベースとして、AWSで利用可能なAmazon RDS。今回はPostgreSQLをベースとするAmazon RDSへアクセスする Springアプリケーションの実装方法についてわかりやすく解説します。なお、データベースアクセスの実装にはSpring Data JPAおよび、 Spring Cloud AWSを用いて実装します。


  1. Amazon RDSの概要とデータベース構築
  2. RDSへのテーブル構築とSpring Data JPAおよびSpring Cloud AWSを用いたRDSアクセスアプリケーションの実装(1)
  3. RDSへのテーブル構築とSpring Data JPAおよびSpring Cloud AWSを用いたRDSアクセスアプリケーションの実装(2)


Spring Data JPAおよびSpring Cloud AWSを用いたRDSアクセスアプリケーションの実装(2)


前回に引き続き、アプリケーションの処理実装に進みます。まずは前回作成した以下のテーブルに対応したエンティティクラスを実装します。


../_images/sample_database.png


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


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<Email> emailsByUserId;
    private Collection<Membership> 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<Email> getEmailsByUserId() {
        return emailsByUserId;
    }

    @OneToMany(mappedBy = "usrByUserId", cascade = CascadeType.ALL)
    public Collection<Membership> 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クラスです。@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アプリケーションをSpring Data JPAおよびSpring Cloud AWSを使って構築してきました。 RDSへSpringアプリケーションで接続する場合、各フレームワークが提供する機能やデータアクセスの仕組みを押さえた上で実装しておくことが重要です。


著者紹介

川畑 光平(KAWABATA Kohei)

../_images/pic_image011.jpg

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

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

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