リレーショナルモデル
API (GraphQL) には、has one、has many、belongs to などのモデル間の関係を処理する機能があります。Amplify GraphQL API では、これは GraphQL データモデリングドキュメントで定義されている @hasOne、@hasMany、@belongsTo ディレクティブを使用して行われます。
デフォルトでは、GraphQL API リクエストは深さが 0 の選択セットを生成します。接続されたリレーショナルモデルは初期リクエストでは返されませんが、追加の API リクエストで必要に応じて遅延ロードできます。選択セットをカスタマイズするメカニズムを提供しており、これにより接続されたリレーションシップを初期リクエストで積極的にロードできます。
前提条件
次の例には、以下の最小バージョン要件があります。
- Amplify CLI v12.7.0
- Amplify Android Library v2.14.0
- このガイドは、Amplify CLI によって生成された更新されたモデルタイプを使用します。このガイドに従うには、
{project-directory}/amplify/cli.jsonで"generatemodelsforlazyloadandcustomselectionset"を見つけて、値をtrueに設定します。
モデル間のリレーショナリップを含む 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 responsefun 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 approachfun 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 対多のリレーショナリップを作成します。
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)}カスタム選択セットを使用したクエリの深さのカスタマイズ
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 を積極的にロードします。