안드로이드용 Parse에서 1:N 및 N:N 관계 생성 및 조회
안드로이드의 관계
소개
Parse를 사용하면 데이터 객체를 저장하고 그들 간의 관계를 설정할 수 있습니다. 이러한 동작을 모델링하기 위해, 다른 ParseObject의 값으로 사용될 수 있습니다. 내부적으로 Parse 프레임워크는 일관성을 유지하기 위해 참조된 객체를 한 곳에만 저장합니다. 이는 복잡한 쿼리를 구축하고 실행할 때 추가적인 힘을 줄 수 있습니다. 세 가지 주요 관계 유형이 있습니다:
- 일대일, 두 객체 간의 직접적인 관계를 설정합니다;
- 일대다, 하나의 객체가 여러 다른 객체와 관련될 수 있습니다;
- 다대다, 여러 객체 간에 복잡한 관계를 생성할 수 있습니다.
Parse에서 일대다 관계를 생성하는 두 가지 방법이 있습니다:
- 첫 번째는 포인터를 사용하는 것입니다. 자식 클래스, 이는 생성 및 쿼리 시간에서 가장 빠릅니다.
- 두 번째는 배열을 사용하는 것입니다. 포인터를 부모 클래스에서 사용하는데, 이는 크기에 따라 느린 쿼리 시간으로 이어질 수 있습니다. 이러한 성능 문제로 인해 우리는 포인터 예제만 사용할 것입니다.
Parse에서 다대다 관계를 생성하는 세 가지 방법이 있습니다.
- 첫 번째는 Parse 관계, 이는 생성 및 쿼리 시간에서 가장 빠릅니다. 우리는 이 가이드에서 이를 사용할 것입니다.
- 두 번째는 배열을 사용하는 것입니다. 포인터를 사용하는데, 이는 크기에 따라 느린 쿼리 시간으로 이어질 수 있습니다.
- 세 번째는 조인 테이블을 사용하는 것입니다. 이는 고전 데이터베이스의 아이디어에서 비롯됩니다. 다대다 관계가 있을 때, 우리는 양쪽의 모든 objectId 또는 포인터를 결합하여 관계가 추적되는 새로운 별도의 테이블을 만듭니다.
이 튜토리얼은 Android Studio 4.1.1에서 생성된 기본 앱을 사용합니다. buildToolsVersion=30.0.2 , Compile SDK Version = 30.0.2 및 targetSdkVersion 30
언제든지 이 튜토리얼로 구축된 전체 Android 프로젝트에 접근할 수 있습니다. 우리의 Github 저장소에서 확인하세요.
목표
우리의 목표는 실용적인 도서 앱을 만들어 Parse 관계를 이해하는 것입니다.
우리가 달성할 목표의 미리보기입니다:

전제 조건
이 튜토리얼을 완료하기 위해 우리는 다음이 필요합니다:
- Back4App에서 생성된 앱.
- Back4App에 연결된 안드로이드 앱.
책 앱 이해하기
당신이 사용할 주요 객체 클래스는 Book 클래스이며, 각 책 항목을 등록에 저장합니다. 또한, 다음은 다른 세 개의 객체 클래스입니다:
- 출판사: 책 출판사 이름, 일대다 관계는Book
- 장르: 책 장르, 일대다 관계는Book. 이 예제에서는 책이 하나의 장르만 가질 수 있다고 가정합니다;
- 저자: 책 저자, 다대다 관계는Book, 책은 여러 저자를 가질 수 있고 저자는 여러 책을 가질 수 있습니다;
이 데이터 모델의 시각적 표현:

시작해봅시다!
다음 단계에 앞서, Back4App을(를) 우리 애플리케이션에 연결해야 합니다. appId와 clientKey를 Back4App에서 string.xml 파일에 저장한 후, Parse를 우리 App.java 또는 App.kt 파일에서 초기화하세요. 새 Parse 앱 튜토리얼을(를) 따라가세요. Parse를(을) 앱에 초기화하는 방법을 모른다면.
또는 위에 공유한 깃허브 링크에서 프로젝트를 다운로드하고 appId와 clientKey 부분만 수정할 수 있습니다.
1 - 책의 관련 객체 저장 및 목록화
이 단계에서는 장르, 출판사 및 저자 클래스가 책 클래스와 관련된 방법을 살펴보겠습니다.
1.1 - 장르 저장 및 목록화
다음 스니펫을 사용하여 장르를 등록할 수 있습니다.
1 private void addGenre(String name) {
2 //We are taking this name parameter from the input.
3 progressDialog.show();
4 ParseObject parseObject = new ParseObject("Genre");
5 parseObject.put("name", name);
6 parseObject.saveInBackground(e -> {
7 progressDialog.dismiss();
8 if (e == null) {
9 getGenres();
10 inputGenre.setText("");
11 Toast.makeText(this, "Genre saved successfully", Toast.LENGTH_SHORT).show();
12 } else {
13 Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
14 }
15 });
16 }우리는 다음 스니펫을 사용하여장르를 등록할 수 있습니다.
1 private void getGenres() {
2 progressDialog.show();
3 ParseQuery<ParseObject> query = new ParseQuery<>("Genre");
4 query.findInBackground((objects, e) -> {
5 progressDialog.dismiss();
6 List<ParseObjectModel> list = new ArrayList<>();
7 for (ParseObject parseObject : objects) {
8 list.add(new ParseObjectModel(parseObject));
9 }
10
11 GenreAdapter adapter = new GenreAdapter(list, this);
12 recyclerView.setLayoutManager(new LinearLayoutManager(this));
13 recyclerView.setAdapter(adapter);
14 });
15 }
1.2 - 퍼블리셔 저장 및 목록화
다음 스니펫을 사용하여퍼블리셔를 등록할 수 있습니다.
1 private void addPublisher(String name) {
2 //We are taking this name parameter from the input.
3 progressDialog.show();
4 ParseObject parseObject = new ParseObject("Publisher");
5 parseObject.put("name", name);
6 parseObject.saveInBackground(e -> {
7 progressDialog.dismiss();
8 if (e == null) {
9 getPublishers();
10 inputPublisher.setText("");
11 Toast.makeText(this, "Publisher saved successfully", Toast.LENGTH_SHORT).show();
12 } else {
13 Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
14 }
15 });
16 }다음 스니펫을 사용하여퍼블리셔를 등록할 수 있습니다.
1 private void getPublishers() {
2 progressDialog.show();
3 ParseQuery<ParseObject> query = new ParseQuery<>("Publisher");
4 query.findInBackground((objects, e) -> {
5 progressDialog.dismiss();
6 List<ParseObjectModel> list = new ArrayList<>();
7 for (ParseObject parseObject : objects) {
8 list.add(new ParseObjectModel(parseObject));
9 }
10
11 PublisherAdapter adapter = new PublisherAdapter(list, this);
12 recyclerView.setLayoutManager(new LinearLayoutManager(this));
13 recyclerView.setAdapter(adapter);
14 });
15 }
1.3 - 저자 저장 및 목록
다음 스니펫을 사용하여저자를 등록할 수 있습니다.
1 private void addAuthor(String name){
2 //We are taking this name parameter from the input.
3 progressDialog.show();
4 ParseObject parseObject = new ParseObject("Author");
5 parseObject.put("name", name);
6 parseObject.saveInBackground(e -> {
7 progressDialog.dismiss();
8 if (e == null) {
9 getAuthors();
10 inputAuthor.setText("");
11 Toast.makeText(this, "Author saved successfully", Toast.LENGTH_SHORT).show();
12 } else {
13 Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
14 }
15 });
16 }우리는 다음 코드 조각을 사용하여저자를 등록할 수 있습니다.
1 private void getAuthors() {
2 progressDialog.show();
3 ParseQuery<ParseObject> query = new ParseQuery<>("Author");
4 query.findInBackground((objects, e) -> {
5 progressDialog.dismiss();
6 List<ParseObjectModel> list = new ArrayList<>();
7 for (ParseObject parseObject : objects) {
8 list.add(new ParseObjectModel(parseObject));
9 }
10
11 AuthorAdapter adapter = new AuthorAdapter(list, this);
12 recyclerView.setLayoutManager(new LinearLayoutManager(this));
13 recyclerView.setAdapter(adapter);
14 });
15 }
이 부분에서는 ParseObjectModel이라는 모델 클래스를 사용합니다. 이 모델 클래스에서는 데이터를 읽을 수 있도록 ParseObject 변수를 가지고 있으며, 다음 단계에서 책을 저장하는 데 사용할 isChecked 변수가 있습니다. 우리는 isChecked 변수를 사용하여 선택된 객체를 쉽게 검색할 수 있습니다. 여기 우리의 ParseObjectModel 모델이 있습니다.
1 public class ParseObjectModel {
2 ParseObject object;
3 boolean isChecked = false;
4
5 public ParseObjectModel(ParseObject object) {
6 this.object = object;
7 }
8
9 public ParseObject getObject() {
10 return object;
11 }
12
13 public ParseObjectModel setObject(ParseObject object) {
14 this.object = object;
15 return this;
16 }
17
18 public boolean isChecked() {
19 return isChecked;
20 }
21
22 public ParseObjectModel setChecked(boolean checked) {
23 isChecked = checked;
24 return this;
25 }
26 }2 - 책 객체와 그 관계 저장하기
2.1 - 1:N 관계로 책 객체 저장하기
이 기능은 Back4app 데이터베이스에 1:N 관계를 가진 새로운 책을 생성합니다.
1 progressDialog.show();
2 book.put("genre", genre);
3 book.put("publisher", publisher);
4 book.put("title", title);
5 book.put("year", year);
6 book.saveInBackground(e -> {
7 progressDialog.hide();
8 if (e == null) {
9 Toast.makeText(AddBookActivity.this, "Book saved successfully", Toast.LENGTH_SHORT).show();
10 startActivity(new Intent(AddBookActivity.this, BookListActivity.class));
11 finish();
12 } else {
13 Toast.makeText(AddBookActivity.this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
14 }
15 });2.2 N:N 관계를 가진 책 객체 저장하기
이 기능은 Back4app 데이터베이스에 N:N 관계를 가진 새로운 책을 생성합니다. 저자 관계의 경우, 우리는 authorRecyclerView의 어댑터에서 선택된 저자를 찾고 그들을 Parse Relation으로 저장합니다.
1 progressDialog.show();
2 book.put("genre", genre);
3 book.put("publisher", publisher);
4 book.put("title", title);
5 book.put("year", year);
6
7 //Here we are setting book relation with getSelectedItem function of BookAuthorAdapter.
8 if (recyclerViewAuthors.getAdapter() != null) {
9 relation = ((BookAuthorAdapter) recyclerViewAuthors.getAdapter()).getSelectedItems(book);
10 if (relation == null) {
11 Toast.makeText(this, "Please select Author/s", Toast.LENGTH_SHORT).show();
12 return;
13 }
14 } else {
15 Toast.makeText(this, "Something went wrong!!", Toast.LENGTH_SHORT).show();
16 return;
17 }
18
19 book.saveInBackground(e -> {
20 progressDialog.hide();
21 if (e == null) {
22 Toast.makeText(AddBookActivity.this, "Book saved successfully", Toast.LENGTH_SHORT).show();
23 startActivity(new Intent(AddBookActivity.this, BookListActivity.class));
24 finish();
25 } else {
26 Toast.makeText(AddBookActivity.this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
27 }
28 });
29
30 //This is the function for save Author/s relation of Book object.This function in BookAuthorAdapter.
31 public ParseRelation<ParseObject> getSelectedItems(ParseObject parseObject) {
32 ParseRelation<ParseObject> relation = parseObject.getRelation("author_relation");
33 for (ParseObjectModel object : this.list) {
34 if (object.isChecked())
35 relation.add(object.getObject());
36 }
37 return relation;
38 } 3 - 책 세부정보를 관계와 함께 조회하기
이 기능을 사용하여 우리는 우리의 책을 그들의 출판사에 따라 나열할 것입니다. 먼저, 우리는 Publisher 클래스에 쿼리를 던집니다.
1 progressDialog.show();
2 ParseQuery<ParseObject> query = new ParseQuery<>("Publisher");
3 query.findInBackground((objects, e) -> {
4 progressDialog.hide();
5 if (e == null) {
6 BookListAdapter adapter = new BookListAdapter(objects, this);
7 recyclerView.setLayoutManager(new LinearLayoutManager(this));
8 recyclerView.setAdapter(adapter);
9 } else {
10 Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
11 }
12 });그리고 우리는 각 책이 관련된 출판사를 나열하기 위해 쿼리합니다.
1 ParseObject object = list.get(position);
2 holder.title.setText(object.getString("name"));
3 ParseQuery<ParseObject> query = new ParseQuery<>("Book");
4 query.whereEqualTo("publisher", object);
5 query.findInBackground((objects, e) -> {
6 if (e == null) {
7 BooksAdapter adapter = new BooksAdapter(objects, context);
8 holder.books.setLayoutManager(new LinearLayoutManager(context));
9 holder.books.setAdapter(adapter);
10 } else {
11 Toast.makeText(context, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
12 }
13 });
이제, 우리가 어떤 책 객체를 클릭하면, 우리는 이 책의 객체 ID를 해당 책의 세부 정보를 보여줄 페이지로 전송합니다. 그리고 우리는 이 객체 ID를 사용하여 데이터베이스에서 책 의 모든 세부 정보를 가져옵니다.
1 private void getBookWithDetails() {
2 progressDialog.show();
3 ParseQuery<ParseObject> query = new ParseQuery<>("Book");
4 query.getInBackground(getIntent().getStringExtra("objectId"), (object, e) -> {
5 if (e == null) {
6 bookTitle.setText("Title: " +object.getString("title"));
7 bookYear.setText("Year: " +object.getString("year"));
8 try {
9 bookGenre.setText("Genre: " +object.getParseObject("genre").fetchIfNeeded().getString("name"));
10 } catch (ParseException parseException) {
11 parseException.printStackTrace();
12 }
13 try {
14 bookPublisher.setText("Publisher: " + object.getParseObject("publisher").fetchIfNeeded().getString("name"));
15 } catch (ParseException parseException) {
16 parseException.printStackTrace();
17 }
18
19 object.getRelation("author_relation").getQuery().findInBackground((objects, e1) -> {
20 progressDialog.hide();
21 if (e1 == null) {
22 BookDetailAuthorAdapter adapter = new BookDetailAuthorAdapter(objects, this);
23 authorRecyclerView.setLayoutManager(new LinearLayoutManager(this));
24 authorRecyclerView.setAdapter(adapter);
25 } else {
26 Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
27 }
28 });
29 } else {
30 progressDialog.hide();
31 Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
32 }
33 });
34 }완료되었습니다!
이 시점에서 우리는 Parse 관계에 대해 배웠습니다 안드로이드.