在本教程中,我們將學習。
我們將重點關注 Spring Data REST 提供的倉庫的關聯資源,並考慮我們能夠定義的每種關係類型。
為了避免額外的設置,我們將使用
Let’s define two entity classes, Library and Address, having a one-to-one relationship by using the @OneToOne annotation. The association is owned by the Library end of the association: The @RestResource annotation is optional, and we can use it to customize the endpoint. . Otherwise, we’ll encounter a JsonMappingException with the message “Detected multiple association links with same relation type! Disambiguate association.” The association name defaults to the property name, and we can customize it using the rel attribute of the @RestResource annotation: If we were to add the secondaryAddress property above to the Library class, we’d have two resources named address, thus encountering a conflict. We can resolve this by specifying a different value for the rel attribute, or by omitting the @RestResource annotation so that the resource name defaults to secondaryAddress. In order to , we’ll create two repository interfaces for each of them by extending the CrudRepository interface: First, we’ll add a Library instance to work with: Then the API returns the JSON object: Note that if we’re using curl on Windows, we have to escape the double-quote character inside the String that represents the JSON body: We can see in the response body that an association resource has been exposed at the libraries/{libraryId}/address endpoint.
However, if we want to add an association, we must first create an Address instance: The result of the POST request is a JSON object containing the Address record: After persisting both instances, we can establish the relationship by using one of the association resources This is done using the HTTP method PUT, which supports a media type of text/uri-list, and a body containing the URI of the resource to bind to the association. Since the Library entity is the owner of the association, we’ll add an address to a library: If successful, it’ll return status 204. To verify this, we can check the library association resource of the address: It should return the Library JSON object with the name “My Library”.
我們定義一個一對多關係,使用@OneToMany和@ManyToOne註解。 還可以添加可選的@RestResource註解來自定義關聯資源。 為了演示一對多關係,我們將添加一個新的Book實體,它代表“many”端與Library實體之間的關係: 然後我們將關係添加到Library類中: 我們也需要創建一個BookRepository: 為了add a book to a library,我們需要首先使用/books集合資源創建Book實例: 並且這裏是POST請求的響應: 在響應體中,我們可以看到關聯端點/books/{bookId}/library,已經被創建。 現在我們通過向包含URI的關聯資源的PUT請求associate the book with the library我們之前部分中創建的圖書館: 我們可以verify the books in the library通過在圖書館的/books關聯資源上使用GET方法: 返回的JSON對象將包含books數組: 為了remove an association,我們可以使用在關聯資源上DELETE方法的:
@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
}
@OneToOne
@JoinColumn(name = "secondary_address_id")
@RestResource(path = "libraryAddress", rel="address")
private Address secondaryAddress;
public interface LibraryRepository extends CrudRepository<Library, Long> {}
public interface AddressRepository extends CrudRepository<Address, Long> {}
-d '{"name":"My Library"}' http://localhost:8080/libraries
{
"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"
}
}
}
-d "{\"name\":\"My Library\"}"
-d '{"location":"Main Street nr 5"}' http://localhost:8080/addresses
{
"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"
}
}
}
-d "http://localhost:8080/addresses/1" -H "Content-Type:text/uri-list" http://localhost:8080/libraries/1/libraryAddress
-X GET http://localhost:8080/addresses/1/library
-X DELETE http://localhost:8080/libraries/1/libraryAddress
3. One-to-Many Relationship
3.1. The Data Model
@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
}
public class Library {
//...
@OneToMany(mappedBy = "library")
private List<Book> books;
//...
}
3.2. The Repository
public interface BookRepository extends CrudRepository<Book, Long> { }
3.3. The Association Resources
curl -i -X POST -d "{\"title\":\"Book1\"}"
-H "Content-Type:application/json" http://localhost:8080/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"
}
}
}
curl -i -X PUT -H "Content-Type:text/uri-list"
-d "http://localhost:8080/libraries/1" http://localhost:8080/books/1/library
curl -i -X GET http://localhost:8080/libraries/1/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"
}
}
}
curl -i -X DELETE http://localhost:8080/books/1/library
We define a many-to-many relationship using the
4.1. The Data Model
To create an example of a many-to-many relationship, we’ll add a new model class,
@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
}
Then we’ll add the association in the
public class Book {
//...
@ManyToMany(mappedBy = "books")
private List<Author> authors;
//...
}
4.2. The Repository
Next, we’ll create a repository interface to manage the
public interface AuthorRepository extends CrudRepository<Author, Long> { }
4.3. The Association Resources
As in the previous sections, we must first
We’ll create an
curl -i -X POST -H "Content-Type:application/json"
-d "{\"name\":\"author1\"}" http://localhost:8080/authors
Next, we’ll add a second
curl -i -X POST -H "Content-Type:application/json"
-d "{\"title\":\"Book 2\"}" http://localhost:8080/books
Then we’ll execute a GET request on our
{
"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"
}
}
}
Now we can
To send multiple
curl -i -X PUT -H "Content-Type:text/uri-list"
--data-binary @uris.txt http://localhost:8080/authors/1/books
The
http://localhost:8080/books/1
http://localhost:8080/books/2
To
curl -i -X GET http://localhost:8080/authors/1/books
And we’ll receive this response:
{
"_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"
}
}
}
To
curl -i -X DELETE http://localhost:8080/authors/1/books/1
5. Testing the Endpoints With 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. Testing the One-to-One Relationship
我們將創建一個 @Test 方法,通過向集合資源發送 POST 請求來保存 Library 和 Address 對象:
然後,它通過向關聯資源的 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. Testing the One-to-Many Relationship
現在,我們將創建一個 @Test 方法,保存一個 Library 實例和兩個 Book 實例,通過向每個 Book 對象的 /library 關聯資源發送 PUT 請求,並驗證該關係是否已保存:
@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. Testing the Many-to-Many Relationship
對於測試 Book 和 Author 實體之間的多對多關係,我們將創建一個測試方法,保存一個 Author 記錄和兩個 Book 記錄。
然後,它通過向 /books 關聯資源發送 PUT 請求,攜帶兩個 Book 的 URI,並驗證該關係已建立:
@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 Data REST 的不同類型關係。