前回、前々回で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 まで。