Name:
interface
Value:
Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated Jul 2, 2024

Maintenance ModeYou are viewing Amplify Gen 1 documentation. Amplify Gen 1 has entered maintenance mode and will reach end of life on May 1, 2027. New project should use Amplify Gen 2. For existing Gen 1 projects, a migration guide and tooling are available to help you upgrade. Switch to the latest Gen 2 docs →

リレーショナルモデル

API (GraphQL) には、has onehas manybelongs to などのモデル間の関係を処理する機能があります。Amplify GraphQL API では、これは GraphQL データモデリングドキュメントで定義されている @hasOne@hasMany@belongsTo ディレクティブを使用して行われます。

デフォルトでは、GraphQL API リクエストは深さが 0 の選択セットを生成します。接続されたリレーショナルモデルは初期リクエストでは返されませんが、追加の API リクエストで必要に応じて遅延ロードできます。選択セットをカスタマイズするメカニズムを提供しており、これにより接続されたリレーションシップを初期リクエストで積極的にロードできます。

Amplify CLI @aws-amplify/cli@12.12.2 および API Category @aws-amplify/amplify-category-api@5.11.5 のバージョンでは、スキーマ内の関連モデルに異なる認可ルールが適用される場合のサブスクリプションでのリレーショナルフィールドデータの処理方法が改善されました。この改善により、リレーショナルフィールドの値が編集され、不正アクセスを防ぐために null または空として表示されます。

この編集は、子モデルが親モデルと同じアクセス許可で保護されることを確認できない場合に発生します。

サブスクリプションはミューテーションに関連付けられており、ミューテーション結果で提供される選択セットはサブスクリプションに渡されるため、ミューテーション結果のリレーショナルフィールドは編集される必要があります。

認可されたエンドユーザーが編集されたリレーショナルフィールドにアクセスする必要がある場合は、クエリを実行してリレーショナルデータを読み取る必要があります。

また、リレーショナルフィールドが必須として設定されている場合、サブスクリプションは関連する認可を継承します。リレーショナルデータをより適切に保護するために、スキーマを修正してオプションのリレーショナルフィールドを使用することを検討してください。

  • 遅延ロードと積極的ロード: ミューテーションとサブスクリプションでは、リレーションシップの遅延ロードと積極的ロードはサポートされなくなりました。ただし、クエリの遅延ロードと積極的ロードは引き続き実行できます。

  • サブスクリプションと関連モデル: サブスクリプションを実行して関連モデルを取得する必要がある場合は、サブスクリプションイベントからモデル識別子を使用して遅延ロードまたは積極的ロードされたクエリを実行して、関連データを取得してください。

アプリケーションのセキュリティ状況に基づいて、この改善が加えられる前のサブスクリプション動作に戻すことを選択できます。 そうするには、amplify/backend/cli.json ファイルの graphqltransformer 下の subscriptionsInheritPrimaryAuth 機能フラグを使用します。

  • 有効な場合、サブスクリプションはリレーショナルフィールドのプライマリモデル認可ルールを継承します。
  • 無効な場合、プライマリモデルと関連モデル間の認可ルールに違いがある場合、ミューテーション応答のリレーショナルフィールドは編集されます。

前提条件

次の例には、以下の最小バージョン要件があります。

  • Amplify CLI v12.7.0
  • Amplify Android Library v2.14.0
  • このガイドは、Amplify CLI によって生成された更新されたモデルタイプを使用します。このガイドに従うには、{project-directory}/amplify/cli.json"generatemodelsforlazyloadandcustomselectionset" を見つけて、値を true に設定します。

プロジェクトに既存のリレーショナルモデルがある場合は、機能フラグを更新した後、amplify codegen models を再実行する必要があります。モデルが更新されたら、リレーショナルフィールドが ModelList/ModelReference タイプでラップされるようになったため、破棄的変更に対処する必要があります。このページの残りのガイドに従って、新しい遅延ロードサポートモデルの使用方法について詳しく学びます。

モデル間のリレーショナリップを含む GraphQL スキーマを作成する

次の例では、スキーマに Post および Comment モデルを追加しましょう。

type Post @model {
id: ID!
title: String!
rating: Int!
comments: [Comment] @hasMany
}
type Comment @model {
id: ID!
content: String
post: Post @belongsTo
}

Amplify CLI を使用して、更新されたスキーマのモデルを生成します。

amplify codegen models

リレーショナリップの作成

接続されたモデルを作成するには、接続するモデルのインスタンスを作成し、それを Amplify.API.mutate に渡します。

Post post = Post.builder()
.title("My Post with comments")
.rating(10)
.build();
Comment comment = Comment.builder()
.post(post) // Directly pass in the post instance
.content("Loving Amplify API!")
.build();
Amplify.API.mutate(ModelMutation.create(post),
savedPost -> {
Log.i("MyAmplifyApp", "Post created.");
Amplify.API.mutate(ModelMutation.create(comment),
savedComment -> Log.i("MyAmplifyApp", "Comment created."),
failure -> Log.e("MyAmplifyApp", "Comment not created.", failure)
);
},
failure -> Log.e("MyAmplifyApp", "Post not created.", failure)
);
val post = Post.builder()
.title("My Post with comments")
.rating(10)
.build()
val comment = Comment.builder()
.post(post) // Directly pass in the post instance
.content("Loving Amplify API!")
.build()
Amplify.API.mutate(ModelMutation.create(post),
{
Log.i("MyAmplifyApp", "Post created")
Amplify.API.mutate(ModelMutation.create(comment),
{ Log.i("MyAmplifyApp", "Comment created") },
{ Log.e("MyAmplifyApp", "Comment not created", it) }
)
},
{ Log.e("MyAmplifyApp", "Post not created", it) }
)
val post = Post.builder()
.title("My Post with comments")
.rating(10)
.build()
val comment = Comment.builder()
.post(post) // Directly pass in the post instance
.content("Loving Amplify API!")
.build()
try {
Amplify.API.mutate(ModelMutation.create(post))
Log.i("MyAmplifyApp", "Post created.")
Amplify.API.mutate(ModelMutation.create(comment))
Log.i("MyAmplifyApp", "Comment created.")
} catch (error: ApiException) {
Log.e("MyAmplifyApp", "Create failed", error)
}

リレーショナリップのクエリ

次の例は、Post の初期ロード、その後の Post のコメントのページを読み込むための取得を示しています。

Amplify.API.query(
ModelQuery.get(Post.class, new Post.PostIdentifier("123")),
response -> {
Post post = response.getData();
ModelList<Comment> commentsModelList = post.getComments();
if (commentsModelList instanceof LoadedModelList) {
List<Comment> comments =
((LoadedModelList<Comment>) commentsModelList).getItems();
Log.i("MyAmplifyApp", "Loaded " + comments.size() + " comments.");
} else if (commentsModelList instanceof LazyModelList) {
((LazyModelList<Comment>) commentsModelList).fetchPage(
page -> {
List<Comment> comments = page.getItems();
Log.i("MyAmplifyApp", "Loaded " + comments.size() + " comments.");
},
failure -> Log.e("MyAmplifyApp, ", "Failed to fetch comments", failure)
);
}
},
failure -> Log.e("MyAmplifyApp", "Failed to query post.", failure)
);
Amplify.API.query(
ModelQuery[Post::class.java, Post.PostIdentifier("123")],
{ response ->
val post = response.data
when (val commentsModelList = post.comments) {
is LoadedModelList -> {
val comments = commentsModelList.items
Log.i("MyAmplifyApp", "Loaded ${comments.size} comments")
}
is LazyModelList -> {
commentsModelList.fetchPage(
{ page ->
val comments = page.items
Log.i("MyAmplifyApp", "Fetched ${comments.size} comments")
},
{ Log.e("MyAmplifyApp, ", "Failed to fetch comments", it) }
)
}
}
},
{ Log.e("MyAmplifyApp, ", "Failed to fetch post", it) }
)
try {
val response =
Amplify.API.query(ModelQuery[Post::class.java, Post.PostIdentifier("123")])
val post = response.data
val comments = when (val commentsModelList = post.comments) {
is LoadedModelList -> {
commentsModelList.items
}
is LazyModelList -> {
commentsModelList.fetchPage().items
}
}
Log.i("MyAmplifyApp", "Fetched ${comments.size} comments")
} catch (error: ApiException) {
Log.e("MyAmplifyApp", "Failed to fetch post and its comments", error)
}

生成されたコードモデルは、リレーショナリップを ModelReference および ModelList タイプでラップします。

public final class Post implements Model {
public ModelList<Comment> getComments()
}
public final class Comment implements Model {
public ModelReference<Post> getPost()
}

ModelReference および ModelList タイプは、Lazy(デフォルト)または Loaded です。接続されたリレーショナリップを積極的にロードする方法については、カスタム選択セットを使用したクエリの深さのカスタマイズを参照してください。

  • ModelReference<M>
    • LazyModelReference<M>
    • LoadedModelReference<M>
  • ModelList<M>
    • LazyModelList<M>
    • LoadedModelList<M>

ModelReference タイプを展開する

void getPostFromComment(Comment comment) {
ModelReference<Post> postReference = comment.getPost();
if (postReference instanceof LoadedModelReference) {
LoadedModelReference<Post> loadedPost = ((LoadedModelReference<Post>) postReference);
Post post = loadedPost.getValue();
Log.i("MyAmplifyApp", "Post: " + post);
} else if (postReference instanceof LazyModelReference) {
LazyModelReference<Post> lazyPost = ((LazyModelReference<Post>) postReference);
lazyPost.fetchModel(
post -> Log.i("MyAmplifyApp", "Post: $post"),
error -> Log.e("MyAmplifyApp", "Failed to fetch post", error)
);
}
}
fun getPostFromComment(comment: Comment) {
when (val postReference = comment.post) {
is LoadedModelReference -> {
val post = postReference.value
Log.i("MyAmplifyApp", "Post: $post")
}
is LazyModelReference -> {
postReference.fetchModel(
{ post -> Log.i("MyAmplifyApp", "Post: $post") },
{ Log.e("MyAmplifyApp", "Failed to fetch post", it) }
)
}
}
}
suspend fun getPostFromComment(comment: Comment) {
try {
val post = when (val postReference = comment.post) {
is LoadedModelReference -> {
postReference.value
}
is LazyModelReference -> {
postReference.fetchModel()
}
}
Log.i("MyAmplifyApp", "Post: $post")
} catch (error: ApiException) {
Log.e("MyAmplifyApp", "Failed to fetch post", error)
}
}

ModelList タイプを展開する

void getCommentsForPost(Post post) {
ModelList<Comment> commentsModelList = post.getComments();
if (commentsModelList instanceof LoadedModelList) {
LoadedModelList<Comment> loadedComments = ((LoadedModelList<Comment>) commentsModelList);
// Eager loading loads the 1st page only.
loadedComments.getItems();
} else if (commentsModelList instanceof LazyModelList) {
LazyModelList<Comment> lazyComments = ((LazyModelList<Comment>) commentsModelList);
fetchComments(lazyComments, null);
}
}
void fetchComments(LazyModelList<Comment> lazyComments, PaginationToken token) {
lazyComments.fetchPage(
token,
page -> {
List<Comment> comments = page.getItems();
Log.i("MyAmplifyApp", "Page of comments: " + comments);
if (page.getHasNextPage()) {
PaginationToken nextToken = page.getNextToken();
fetchComments(lazyComments, nextToken); // recursively fetch next page
}
},
error -> Log.e("MyAmplifyApp", "Failed to fetch comments page", error)
);
}
// Post comes from server response
fun getCommentsForPost(post: Post) {
when (val commentsModelList = post.comments) {
is LoadedModelList -> {
// Eager loading loads the 1st page only.
commentsModelList.items
}
is LazyModelList -> {
// Helper method to load all pages
fetchComments(commentsModelList)
}
}
}
// Helper method for callback approach
fun fetchComments(lazyComments: LazyModelList<Comment>, token: PaginationToken? = null) {
lazyComments.fetchPage(
token,
{ page ->
val comments = page.items
Log.i("MyAmplifyApp", "Page of comments: $comments")
if (page.hasNextPage) {
val nextToken = page.nextToken
fetchComments(lazyComments, nextToken) // recursively fetch next page
}
},
{ Log.e("MyAmplifyApp", "Failed to fetch comments page", it) }
)
}
suspend fun getCommentsForPost(post: Post) {
try {
val comments = when (val commentsModelList = post.comments) {
is LoadedModelList -> {
// Eager loading loads the 1st page only.
commentsModelList.items
}
is LazyModelList -> {
var page = commentsModelList.fetchPage()
var loadedComments = mutableListOf(page.items) // initial page of comments
// loop through all pages to fetch the full list of comments
while (page.hasNextPage) {
val nextToken = page.nextToken
page = commentsModelList.fetchPage(nextToken)
// add the page of comments to the comments variable
loadedComments += page.items
}
loadedComments
}
}
Log.i("MyAmplifyApp", "Comments: $comments")
} catch (error: ApiException) {
Log.e("MyAmplifyApp", "Failed to fetch comments", error)
}
}

リレーショナリップの削除

1 対多のリレーショナリップで親オブジェクトを削除すると、子は削除されません。孤立したデータを防ぐために、親を削除する前に子を削除します。

// Delete any comments associated with parent post.
Amplify.API.mutate(
ModelMutation.delete(comment),
commentResponse ->
// Once all comments for a post are deleted, the post can be deleted.
Amplify.API.mutate(
ModelMutation.delete(post),
postResponse -> Log.i("MyAmplifyApp", "Deleted comment and post"),
(error) -> Log.e("MyAmplifyApp", "Failed to delete post", error)
),
error -> Log.e("MyAmplifyApp", "Failed to delete comment", error)
);
Amplify.API.mutate(
// Delete any comments associated with parent post.
ModelMutation.delete(comment),
{
// Once all comments for a post are deleted, the post can be deleted.
Amplify.API.mutate(
ModelMutation.delete(post),
{ Log.i("MyAmplifyApp", "Deleted comment and post") },
{ Log.e("MyAmplifyApp", "Failed to delete post", it) }
)
},
{ Log.e("MyAmplifyApp", "Failed to delete comment", it) }
)
try {
// Delete any comments associated with parent post.
Amplify.API.mutate(ModelMutation.delete(comment))
// Once all comments for a post are deleted, the post can be deleted.
Amplify.API.mutate(ModelMutation.delete(post))
Log.i("MyAmplifyApp", "Deleted comment and post")
} catch (error: ApiException) {
Log.e("MyAmplifyApp", "Failed to delete comment and post", error)
}

多対多のリレーショナリップ

多対多のリレーショナリップの場合、@manyToMany ディレクティブを使用して relationName を指定できます。内部的には、Amplify はジョインテーブルと両方のモデルからの 1 対多のリレーショナリップを作成します。

ジョインテーブルレコードは、関連するレコードを削除する前に削除する必要があります。たとえば、PostTag 間の多対多のリレーショナリップの場合、Post または Tag を削除する前に PostTag ジョインレコードを削除してください。

type Post @model {
id: ID!
title: String!
rating: Int
editors: [User] @manyToMany(relationName: "PostEditor")
}
type User @model {
id: ID!
username: String!
posts: [Post] @manyToMany(relationName: "PostEditor")
}
Post post = Post.builder()
.title("My Post")
.rating(10)
.build();
User user = User.builder()
.username("User")
.build();
PostEditor postEditor = PostEditor.builder()
.post(post)
.user(user)
.build();
Amplify.API.mutate(ModelMutation.create(post),
createdPost -> {
Log.i("MyAmplifyApp", "Post created.");
Amplify.API.mutate(ModelMutation.create(user),
createdUser -> {
Log.i("MyAmplifyApp", "User created.");
Amplify.API.mutate(ModelMutation.create(postEditor),
createdPostEditor -> Log.i("MyAmplifyApp", "PostEditor created."),
failure -> Log.e("MyAmplifyApp", "PostEditor not created.", failure)
);
},
failure -> Log.e("MyAmplifyApp", "User not created.", failure)
);
},
failure -> Log.e("MyAmplifyApp", "Post not created.", failure)
);
val post = Post.builder()
.title("My Post")
.rating(10)
.build()
val user = User.builder()
.username("User")
.build()
val postEditor = PostEditor.builder()
.post(post)
.user(user)
.build()
Amplify.API.mutate(ModelMutation.create(post),
{
Log.i("MyAmplifyApp", "Post created")
Amplify.API.mutate(ModelMutation.create(user),
{
Log.i("MyAmplifyApp", "User created")
Amplify.API.mutate(
ModelMutation.create(postEditor),
{ Log.i("MyAmplifyApp", "PostEditor created") },
{ Log.e("MyAmplifyApp", " PostEditor not created", it) }
)
},
{ Log.e("MyAmplifyApp", " User not created", it) }
)
},
{ Log.e("MyAmplifyApp", "Post not created", it) }
)
val post = Post.builder()
.title("My Post")
.rating(10)
.build()
val user = User.builder()
.username("User")
.build()
val postEditor = PostEditor.builder()
.post(post)
.user(user)
.build()
try {
Amplify.API.mutate(ModelMutation.create(post))
Log.i("MyAmplifyApp", "Post created.")
Amplify.API.mutate(ModelMutation.create(user))
Log.i("MyAmplifyApp", "User created.")
Amplify.API.mutate(ModelMutation.create(postEditor))
Log.i("MyAmplifyApp", "PostEditor created.")
} catch (error: ApiException) {
Log.e("MyAmplifyApp", "Create failed", error)
}

この例は、複数の順序付けられた作成操作で作業する複雑さを示しています。ネストされたコールバックを削除するには、Amplify の Coroutines サポートの使用を検討してください。

カスタム選択セットを使用したクエリの深さのカスタマイズ

GraphQL リクエストのオプション includes パラメータを指定することで、ネットワークリクエストを介して接続されたモデルをネストされたクエリで実行できます。

Comment とそれが属する Post をクエリします。

Amplify.API.query(
ModelQuery.<Comment, CommentPath>get(
Comment.class,
new Comment.CommentIdentifier("c1"),
(commentPath -> includes(commentPath.getPost()))
),
response -> {
Comment comment = response.getData();
ModelReference<Post> postReference = comment.getPost();
if (postReference instanceof LoadedModelReference) {
Post post = ((LoadedModelReference<Post>) postReference).getValue();
Log.i("MyAmplifyApp", "Post: " + post);
}
},
failure -> Log.e("MyAmplifyApp", "Failed to fetch post", failure)
);

これにより、GraphQL ドキュメントのポストの選択セットが入力されます。これはGraphQL サービスに操作の一部としてポストモデルを取得することを示します。コメントが読み込まれると、ポストモデルはすぐにメモリで利用可能になり、追加のネットワークリクエストは不要です。

Amplify.API.query(
ModelQuery.get<Comment, CommentPath>(
Comment::class.java,
Comment.CommentIdentifier("c1")
) { commentPath ->
includes(commentPath.post)
},
{ response ->
val comment = response.data
val post = (comment.post as? LoadedModelReference)?.value
Log.i("MyAmplifyApp", "Post: $post")
},
{ Log.e("MyAmplifyApp", "Failed to fetch post", it) }
)

これにより、GraphQL ドキュメントのポストの選択セットが入力されます。これはGraphQL サービスに操作の一部としてポストモデルを取得することを示します。コメントが読み込まれると、ポストモデルはすぐにメモリで利用可能になり、追加のネットワークリクエストは不要です。

try {
val comment = Amplify.API.query(
ModelQuery.get<Comment, CommentPath>(
Comment::class.java,
Comment.CommentIdentifier("c1")
) { commentPath ->
includes(commentPath.post)
}
).data
val post = (comment.post as? LoadedModelReference)?.value
Log.i("MyAmplifyApp", "Post: $post")
} catch (error: ApiException) {
Log.e("MyAmplifyApp", "Failed to fetch post", error)
}

これにより、GraphQL ドキュメントのポストの選択セットが入力されます。これはGraphQL サービスに操作の一部としてポストモデルを取得することを示します。コメントが読み込まれると、ポストモデルはすぐにメモリで利用可能になり、追加のネットワークリクエストは不要です。

Post と Post のコメントの最初のページをクエリします。

Amplify.API.query(
ModelQuery.<Post, PostPath>get(
Post.class,
new Post.PostIdentifier("p1"),
(postPath -> includes(postPath.getComments()))
),
response -> {
Post post = response.getData();
ModelList<Comment> commentsModelList = post.getComments();
if (commentsModelList instanceof LoadedModelList) {
List<Comment> comments = ((LoadedModelList<Comment>) commentsModelList).getItems();
Log.i("MyAmplifyApp", "Comments: " + comments);
}
},
failure -> Log.e("MyAmplifyApp", "Failed to fetch post", failure)
);

Post のネットワークリクエストにコメントが含まれます。1 つのネットワーク呼び出しでコメントの最初のページを積極的にロードします。

Amplify.API.query(
ModelQuery.get<Post, PostPath>(
Post::class.java,
Post.PostIdentifier("p1")
) { postPath ->
includes(postPath.comments)
},
{ response ->
val post = response.data
val comments = (post.comments as? LoadedModelList)?.items
Log.i("MyAmplifyApp", "Comments: $comments")
},
{ Log.e("MyAmplifyApp", "Failed to fetch post", it) }
)

Post のネットワークリクエストにコメントが含まれます。1 つのネットワーク呼び出しでコメントの最初のページを積極的にロードします。

try {
val post = Amplify.API.query(
ModelQuery.get<Post, PostPath>(
Post::class.java,
Post.PostIdentifier("p1")
) { postPath ->
includes(postPath.comments)
}
).data
val comments = (post.comments as? LoadedModelList)?.items
Log.i("MyAmplifyApp", "Comments: $comments")
} catch (error: ApiException) {
Log.e("MyAmplifyApp", "Failed to fetch post", error)
}

Post のネットワークリクエストにコメントが含まれます。1 つのネットワーク呼び出しでコメントの最初のページを積極的にロードします。

includes パラメータを通じて複雑なネストされたクエリを生成できます。

ModelQuery.get<Comment, CommentPath>(Comment::class.java, "c1") { commentPath ->
includes(commentPath.post.comments)
}

このクエリは、コメントを取得し、親ポストおよび Post のコメントの最初のページを積極的にロードします。

ModelQuery.get<Comment, CommentPath>(
Comment::class.java,
"c1"
) { commentPath ->
includes(commentPath.post.comments)
}

このクエリは、コメントを取得し、親ポストおよび Post のコメントの最初のページを積極的にロードします。

ModelQuery.get<PostEditor, PostEditorPath>(PostEditor::class.java, "pe1") { postEditorPath ->
includes(postEditorPath.post, postEditorPath.user)
}

このクエリは、postEditor を取得し、その post と user を積極的にロードします。

ModelQuery.get<PostEditor, PostEditorPath>(
PostEditor::class.java,
"pe1"
) { postEditorPath ->
includes(postEditorPath.post, postEditorPath.user)
}

このクエリは、postEditor を取得し、その post と user を積極的にロードします。