SpringDataRESTでのリレーションシップの操作
1. 概要
このチュートリアルでは、 Spring DataRESTでエンティティ間の関係を操作する方法を学習します。
定義できる各タイプの関係を考慮して、Spring DataRESTがリポジトリに対して公開するアソシエーションリソースに焦点を当てます。
余分な設定を避けるために、例ではH2組み込みデータベースを使用します。 必要な依存関係のリストは、 Introduction to Spring DataRESTの記事にあります。
2. 1対1の関係
2.1. データモデル
@OneToOne アノテーションを使用して、1対1の関係を持つLibraryと
@Entity
public class Library {
@Id
@GeneratedValue
private long id;
@Column
private String name;
@OneToOne
@JoinColumn(name = "address_id")
@RestResource(path = "libraryAddress", rel="address")
private Address address;
// standard constructor, getters, setters
}
@Entity
public class Address {
@Id
@GeneratedValue
private long id;
@Column(nullable = false)
private String location;
@OneToOne(mappedBy = "address")
private Library library;
// standard constructor, getters, setters
}
@RestResource アノテーションはオプションであり、これを使用してエンドポイントをカスタマイズできます。
アソシエーションリソースごとに異なる名前を付けることにも注意する必要があります。 そうでなければ、私たちは遭遇します JsonMappingException メッセージ付き
アソシエーション名はデフォルトでプロパティ名になり、@RestResourceアノテーションのrel属性を使用してカスタマイズできます。
@OneToOne
@JoinColumn(name = "secondary_address_id")
@RestResource(path = "libraryAddress", rel="address")
private Address secondaryAddress;
上記のsecondaryAddressプロパティをLibraryクラスに追加すると、 address という名前の2つのリソースが存在するため、競合が発生します。
これを解決するには、 rel 属性に別の値を指定するか、 RestResource アノテーションを省略して、リソース名がデフォルトでsecondsaryAddressになるようにします。
2.2. リポジトリ
これらのエンティティをリソースとして公開するために、 CrudRepository インターフェイスを拡張して、それぞれに2つのリポジトリインターフェイスを作成します。
public interface LibraryRepository extends CrudRepository<Library, Long> {}
public interface AddressRepository extends CrudRepository<Address, Long> {}
2.3. リソースの作成
まず、Libraryインスタンスを追加して操作します。
curl -i -X POST -H "Content-Type:application/json"
-d '{"name":"My Library"}' http://localhost:8080/libraries
次に、APIはJSONオブジェクトを返します。
{
"name" : "My Library",
"_links" : {
"self" : {
"href" : "http://localhost:8080/libraries/1"
},
"library" : {
"href" : "http://localhost:8080/libraries/1"
},
"address" : {
"href" : "http://localhost:8080/libraries/1/libraryAddress"
}
}
}
Windowsでcurlを使用している場合は、JSON本体を表すString内の二重引用符をエスケープする必要があることに注意してください。
-d "{\"name\":\"My Library\"}"
応答本文で、 libraries / {libraryId} /addressエンドポイントで関連付けリソースが公開されていることがわかります。
アソシエーションを作成する前に、このエンドポイントにGETリクエストを送信すると、空のオブジェクトが返されます。
ただし、関連付けを追加する場合は、最初にAddressインスタンスを作成する必要があります。
curl -i -X POST -H "Content-Type:application/json"
-d '{"location":"Main Street nr 5"}' http://localhost:8080/addresses
POSTリクエストの結果は、Addressレコードを含むJSONオブジェクトです。
{
"location" : "Main Street nr 5",
"_links" : {
"self" : {
"href" : "http://localhost:8080/addresses/1"
},
"address" : {
"href" : "http://localhost:8080/addresses/1"
},
"library" : {
"href" : "http://localhost:8080/addresses/1/library"
}
}
}
2.4. アソシエーションの作成
両方のインスタンスを永続化した後、関連付けリソースの1つを使用して関係を確立できます。
これは、 text / uri-list のメディアタイプをサポートするHTTPメソッドPUTと、関連付けにバインドするリソースのURIを含む本文を使用して行われます。
Library エンティティは関連付けの所有者であるため、ライブラリにアドレスを追加します。
curl -i -X PUT -d "http://localhost:8080/addresses/1"
-H "Content-Type:text/uri-list" http://localhost:8080/libraries/1/libraryAddress
成功すると、ステータス204が返されます。 これを確認するために、アドレスのライブラリアソシエーションリソースを確認できます。
curl -i -X GET http://localhost:8080/addresses/1/library
LibraryJSONオブジェクトを「MyLibrary」という名前で返す必要があります。
アソシエーションを削除するには、DELETEメソッドを使用してエンドポイントを呼び出すことができます。必ず、リレーションシップの所有者のアソシエーションリソースを使用してください。
curl -i -X DELETE http://localhost:8080/libraries/1/libraryAddress
3. 1対多の関係
@OneToManyおよび@ManyToOneアノテーションを使用して、1対多の関係を定義します。 オプションの@RestResourceアノテーションを追加して、関連付けリソースをカスタマイズすることもできます。
3.1. データモデル
1対多の関係を例示するために、新しい Book エンティティを追加します。これは、Libraryエンティティとの関係の「多」端を表します。
@Entity
public class Book {
@Id
@GeneratedValue
private long id;
@Column(nullable=false)
private String title;
@ManyToOne
@JoinColumn(name="library_id")
private Library library;
// standard constructor, getter, setter
}
次に、関係をLibraryクラスにも追加します。
public class Library {
//...
@OneToMany(mappedBy = "library")
private List<Book> books;
//...
}
3.2. リポジトリ
また、BookRepositoryを作成する必要があります。
public interface BookRepository extends CrudRepository<Book, Long> { }
3.3. 協会のリソース
本をライブラリに追加するには、最初に/ booksコレクションリソースを使用してBookインスタンスを作成する必要があります。
curl -i -X POST -d "{\"title\":\"Book1\"}"
-H "Content-Type:application/json" http://localhost:8080/books
そして、POSTリクエストからの応答は次のとおりです。
{
"title" : "Book1",
"_links" : {
"self" : {
"href" : "http://localhost:8080/books/1"
},
"book" : {
"href" : "http://localhost:8080/books/1"
},
"bookLibrary" : {
"href" : "http://localhost:8080/books/1/library"
}
}
}
応答本文では、関連付けエンドポイント / books / {bookId} / library、が作成されていることがわかります。
次に、ライブラリリソースの URI を含む関連付けリソースにPUTリクエストを送信して、前のセクションで作成したライブラリに本を関連付けます。
curl -i -X PUT -H "Content-Type:text/uri-list"
-d "http://localhost:8080/libraries/1" http://localhost:8080/books/1/library
ライブラリの/books アソシエーションリソースでGETメソッドを使用することにより、ライブラリ内のブックを検証できます。
curl -i -X GET http://localhost:8080/libraries/1/books
返されるJSONオブジェクトには、books配列が含まれます。
{
"_embedded" : {
"books" : [ {
"title" : "Book1",
"_links" : {
"self" : {
"href" : "http://localhost:8080/books/1"
},
"book" : {
"href" : "http://localhost:8080/books/1"
},
"bookLibrary" : {
"href" : "http://localhost:8080/books/1/library"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/libraries/1/books"
}
}
}
関連付けを削除するには、関連付けリソースでDELETEメソッドを使用できます。
curl -i -X DELETE http://localhost:8080/books/1/library
4. 多対多の関係
@ManyToMany アノテーションを使用して多対多の関係を定義し、それに@RestResourceを追加することもできます。
4.1. データモデル
多対多の関係の例を作成するために、 Bookエンティティと多対多の関係を持つ新しいモデルクラスAuthor、を追加します。
@Entity
public class Author {
@Id
@GeneratedValue
private long id;
@Column(nullable = false)
private String name;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "book_author",
joinColumns = @JoinColumn(name = "book_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "author_id",
referencedColumnName = "id"))
private List<Book> books;
//standard constructors, getters, setters
}
次に、Bookクラスにも関連付けを追加します。
public class Book {
//...
@ManyToMany(mappedBy = "books")
private List<Author> authors;
//...
}
4.2. リポジトリ
次に、Authorエンティティを管理するためのリポジトリインターフェイスを作成します。
public interface AuthorRepository extends CrudRepository<Author, Long> { }
4.3. 協会のリソース
前のセクションと同様に、関連付けを確立する前に、まずリソースを作成する必要があります。
/ authors コレクションリソースにPOSTリクエストを送信して、Authorインスタンスを作成します。
curl -i -X POST -H "Content-Type:application/json"
-d "{\"name\":\"author1\"}" http://localhost:8080/authors
次に、2番目のBookレコードをデータベースに追加します。
curl -i -X POST -H "Content-Type:application/json"
-d "{\"title\":\"Book 2\"}" http://localhost:8080/books
次に、 Author レコードに対してGETリクエストを実行して、関連付けURLを表示します。
{
"name" : "author1",
"_links" : {
"self" : {
"href" : "http://localhost:8080/authors/1"
},
"author" : {
"href" : "http://localhost:8080/authors/1"
},
"books" : {
"href" : "http://localhost:8080/authors/1/books"
}
}
}
これで、エンドポイント authors / 1 / books とPUTを使用して、2つのBookレコードとAuthorレコードの間に関連付けを作成できます。 text / uri-list のメディアタイプをサポートし、複数のURIを受信できるメソッド。
複数のURIを送信するには、改行で区切る必要があります。
curl -i -X PUT -H "Content-Type:text/uri-list"
--data-binary @uris.txt http://localhost:8080/authors/1/books
uris.txt ファイルには、本のURIがそれぞれ別々の行に含まれています。
http://localhost:8080/books/1
http://localhost:8080/books/2
両方の本が著者に関連付けられていることを確認するために、GETリクエストを関連付けエンドポイントに送信できます。
curl -i -X GET http://localhost:8080/authors/1/books
そして、私たちはこの応答を受け取ります:
{
"_embedded" : {
"books" : [ {
"title" : "Book 1",
"_links" : {
"self" : {
"href" : "http://localhost:8080/books/1"
}
//...
}
}, {
"title" : "Book 2",
"_links" : {
"self" : {
"href" : "http://localhost:8080/books/2"
}
//...
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/authors/1/books"
}
}
}
アソシエーションを削除するには、DELETEメソッドを使用してアソシエーションリソースのURLにリクエストを送信し、その後に{bookId}を送信できます。
curl -i -X DELETE http://localhost:8080/authors/1/books/1
5. TestRestTemplateを使用したエンドポイントのテスト
TestRestTemplate インスタンスを挿入し、使用する定数を定義するテストクラスを作成しましょう。
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringDataRestApplication.class,
webEnvironment = WebEnvironment.DEFINED_PORT)
public class SpringDataRelationshipsTest {
@Autowired
private TestRestTemplate template;
private static String BOOK_ENDPOINT = "http://localhost:8080/books/";
private static String AUTHOR_ENDPOINT = "http://localhost:8080/authors/";
private static String ADDRESS_ENDPOINT = "http://localhost:8080/addresses/";
private static String LIBRARY_ENDPOINT = "http://localhost:8080/libraries/";
private static String LIBRARY_NAME = "My Library";
private static String AUTHOR_NAME = "George Orwell";
}
5.1. 1対1の関係のテスト
コレクションリソースにPOSTリクエストを行うことにより、LibraryおよびAddressオブジェクトを保存する@Testメソッドを作成します。
次に、PUTリクエストとの関係をアソシエーションリソースに保存し、同じリソースへのGETリクエストで確立されていることを確認します。
@Test
public void whenSaveOneToOneRelationship_thenCorrect() {
Library library = new Library(LIBRARY_NAME);
template.postForEntity(LIBRARY_ENDPOINT, library, Library.class);
Address address = new Address("Main street, nr 1");
template.postForEntity(ADDRESS_ENDPOINT, address, Address.class);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Content-type", "text/uri-list");
HttpEntity<String> httpEntity
= new HttpEntity<>(ADDRESS_ENDPOINT + "/1", requestHeaders);
template.exchange(LIBRARY_ENDPOINT + "/1/libraryAddress",
HttpMethod.PUT, httpEntity, String.class);
ResponseEntity<Library> libraryGetResponse
= template.getForEntity(ADDRESS_ENDPOINT + "/1/library", Library.class);
assertEquals("library is incorrect",
libraryGetResponse.getBody().getName(), LIBRARY_NAME);
}
5.2. 1対多の関係のテスト
次に、Libraryインスタンスと2つのBookインスタンスを保存し、各BookにPUTリクエストを送信する@Testメソッドを作成します。オブジェクトの/library 関連付けリソース、および関係が保存されていることを確認します。
@Test
public void whenSaveOneToManyRelationship_thenCorrect() {
Library library = new Library(LIBRARY_NAME);
template.postForEntity(LIBRARY_ENDPOINT, library, Library.class);
Book book1 = new Book("Dune");
template.postForEntity(BOOK_ENDPOINT, book1, Book.class);
Book book2 = new Book("1984");
template.postForEntity(BOOK_ENDPOINT, book2, Book.class);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Content-Type", "text/uri-list");
HttpEntity<String> bookHttpEntity
= new HttpEntity<>(LIBRARY_ENDPOINT + "/1", requestHeaders);
template.exchange(BOOK_ENDPOINT + "/1/library",
HttpMethod.PUT, bookHttpEntity, String.class);
template.exchange(BOOK_ENDPOINT + "/2/library",
HttpMethod.PUT, bookHttpEntity, String.class);
ResponseEntity<Library> libraryGetResponse =
template.getForEntity(BOOK_ENDPOINT + "/1/library", Library.class);
assertEquals("library is incorrect",
libraryGetResponse.getBody().getName(), LIBRARY_NAME);
}
5.3. 多対多の関係のテスト
BookエンティティとAuthorエンティティ間の多対多の関係をテストするために、1つのAuthorレコードと2つのを保存するテストメソッドを作成します。 Bookレコード。
次に、2つの Books ‘URIを使用して/books アソシエーションリソースにPUTリクエストを送信し、関係が確立されていることを確認します。
@Test
public void whenSaveManyToManyRelationship_thenCorrect() {
Author author1 = new Author(AUTHOR_NAME);
template.postForEntity(AUTHOR_ENDPOINT, author1, Author.class);
Book book1 = new Book("Animal Farm");
template.postForEntity(BOOK_ENDPOINT, book1, Book.class);
Book book2 = new Book("1984");
template.postForEntity(BOOK_ENDPOINT, book2, Book.class);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Content-type", "text/uri-list");
HttpEntity<String> httpEntity = new HttpEntity<>(
BOOK_ENDPOINT + "/1\n" + BOOK_ENDPOINT + "/2", requestHeaders);
template.exchange(AUTHOR_ENDPOINT + "/1/books",
HttpMethod.PUT, httpEntity, String.class);
String jsonResponse = template
.getForObject(BOOK_ENDPOINT + "/1/authors", String.class);
JSONObject jsonObj = new JSONObject(jsonResponse).getJSONObject("_embedded");
JSONArray jsonArray = jsonObj.getJSONArray("authors");
assertEquals("author is incorrect",
jsonArray.getJSONObject(0).getString("name"), AUTHOR_NAME);
}
6. 結論
この記事では、Spring DataRESTとのさまざまなタイプの関係の使用方法を示しました。
例の完全なソースコードは、GitHubのにあります。