Data Rest
Data Rest?
์คํ๋ง ๊ฐ๋ฐ ์ค, ๋จ์ ์กฐํ๋ฅผ ์ํด Service, Controller๋ฅผ ๋ง๋ค ๋ ๋ง์ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๋ ๋ถํธํจ์ ๊ฒฝํํ ์ ์ด ์์ ๊ฒ์ด๋ค. ์ด๋ฌํ ๋ถํธํจ์ ํด์ํ๊ธฐ ์ํด Spring Data REST๋ ๋ฆฌํฌ์งํ ๋ฆฌ ์ธํฐํ์ด์ค๋ฅผ ์๋์ผ๋ก RESTful API๋ก ๋ ธ์ถํ์ฌ ๊ฐ๋ฐ์์ ๋ถ๋ด์ ์ค์ธ๋ค. ๋ฆฌํฌ์งํ ๋ฆฌ ์ธํฐํ์ด์ค๋ง ์ ์ํ๋ฉด, Spring Data REST๋ ๊ธฐ๋ณธ์ ์ผ๋ก CRUD ๊ธฐ๋ฅ์ ๊ฐ์ถ RESTful ์๋ํฌ์ธํธ๋ฅผ ์๋์ผ๋ก ์์ฑํ๋ค.
Spring Data REST๋ ๋ํ HATEOAS ์์น์ ์ค์ํ์ฌ ๋ฆฌ์์ค ๊ฐ์ ํ์์ฑ์ ๋์ธ๋ค. ํด๋ผ์ด์ธํธ๋ ๋ฃจํธ URL์ GET ์์ฒญ์ ๋ณด๋ด์ด, ์๋ฒ๊ฐ ์ ๊ณตํ๋ ๋ชจ๋ ๋ฆฌ์์ค์ ๋ํ ๋งํฌ ์ ๋ณด๋ฅผ ํฌํจํ๋ JSON ์๋ต์ ๋ฐ์ ์ ์๋ค. ์ด๋ฅผ ํตํด ํด๋ผ์ด์ธํธ๋ ๋ณ๋์ ๋ฌธ์ํ ์์ด๋ API์ ํ์์ด ๊ฐ๋ฅํ๋ค.
์ด์ฒ๋ผ Spring Data REST๋ ๊ธฐ๋ณธ์ ์ธ CRUD ๊ธฐ๋ฅ์ ์๋์ผ๋ก ์ฒ๋ฆฌํด์ฃผ๋ฉฐ, ๊ฐ๋ฐ์๋ ๋น์ฆ๋์ค ๋ก์ง์ ๋์ฑ ์ง์คํ ์ ์๋ค. ๋ฐ๋ผ์, ๋จ์ํ ๋ฐ์ดํฐ ์กฐํ ๋ฐ ๊ด๋ฆฌ๋ฅผ ์ํด ๋ฐ๋ณต์ ์ธ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋์ , Spring Data REST๋ฅผ ํ์ฉํ์ฌ ๋์ฑ ํจ์จ์ ์ธ ๊ฐ๋ฐ์ ํ ์ ์์ ๊ฒ์ด๋ค.
Paging ?
Spring Data REST๋ ํ์ด์ง ๋ฐ ์ ๋ ฌ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ฌ ๋๊ท๋ชจ ๋ฐ์ดํฐ์ ์ ํจ์จ์ ์ผ๋ก ๋ค๋ฃฐ ์ ์๊ฒ ํด์ค๋๋ค. ์ด ๊ธฐ๋ฅ์ ํ์ฉํ๋ฉด ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ์ ๋ฐํํ์ง ์๊ณ , ํด๋ผ์ด์ธํธ๊ฐ ์ํ๋ ํฌ๊ธฐ๋ก ๋ฐ์ดํฐ๋ฅผ ๋๋ ์ ์ ๊ณตํ ์ ์์ต๋๋ค.
Paging
๊ธฐ๋ณธ์ ์ผ๋ก PagingAndSortingRepository<T, ID>๋ฅผ ์์๋ฐ๋ ๋ฆฌํฌ์งํ ๋ฆฌ๋ ์๋์ผ๋ก ํ์ด์ง ๊ธฐ๋ฅ์ ์ง์ํฉ๋๋ค. ํน์ URL ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํด ํ์ด์ง ํฌ๊ธฐ์ ์์ ํ์ด์ง ๋ฒํธ๋ฅผ ์ง์ ํ ์ ์์ต๋๋ค.
ํ์ด์ง์ ์ง์ ์ ์ํ ์ฟผ๋ฆฌ ๋ฉ์๋์ ์ ์ฉํ๋ ค๋ฉด, ๋ฉ์๋ ์๊ทธ๋์ฒ์ Pageable ํ๋ผ๋ฏธํฐ๋ฅผ ์ถ๊ฐํ๊ณ , ๋ฐํ ํ์
์ Page ๋๋ Slice๋ก ๋ณ๊ฒฝํด์ผ ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด Spring Data REST๊ฐ ์๋์ผ๋ก ํ์ด์ง ๋งํฌ๋ฅผ ์๋ต์ ์ถ๊ฐํ์ฌ, ํด๋ผ์ด์ธํธ๊ฐ ๋ค์ ํ์ด์ง ๋๋ ์ด์ ํ์ด์ง๋ก ์ฝ๊ฒ ์ด๋ํ ์ ์๊ฒ ๋ฉ๋๋ค.
์ด์ ๋ฐ ๋ค์ ๋งํฌ
๊ฐ ํ์ด์ง ์๋ต์๋ ํ์ฌ ํ์ด์ง์ ๋ฐ๋ผ ์ด์ ๋ฐ ๋ค์ ํ์ด์ง์ ๋งํฌ๊ฐ ํฌํจ๋ฉ๋๋ค. ์ฒซ ํ์ด์ง์ผ ๊ฒฝ์ฐ prev ๋งํฌ๋ ์์ฑ๋์ง ์์ผ๋ฉฐ, ๋ง์ง๋ง ํ์ด์ง์ผ ๊ฒฝ์ฐ next ๋งํฌ๋ ์์ฑ๋์ง ์์ต๋๋ค.
curl localhost:8080/people?size=5์๋ต์ ๋ค์๊ณผ ๊ฐ์ ํํ๋ฅผ ๊ฐ์ง๋๋ค:
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons{&sort,page,size}",
"templated" : true
},
"next" : {
"href" : "http://localhost:8080/persons?page=1&size=5{&sort}",
"templated" : true
}
},
"_embedded" : {
โฆ data โฆ
},
"page" : {
"size" : 5,
"totalElements" : 50,
"totalPages" : 10,
"number" : 0
}
}์ด์ฒ๋ผ ์๋ต์๋ ํ์ฌ ํ์ด์ง ์ ๋ณด์ ์ ์ฒด ํ์ด์ง ์, ์ด ์ํฐํฐ ์ ๋ฑ์ด ํฌํจ๋์ด ์์ด UI์์ ์ฌ์ฉ์์ ์์น๋ฅผ ๋ํ๋ด๋ ๋๊ตฌ๋ฅผ ์ฝ๊ฒ ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
Sorting
Spring Data REST๋ ๋ฆฌํฌ์งํ ๋ฆฌ์ ์ ๋ ฌ ๊ธฐ๋ฅ์ ์ฌ์ฉํด ๊ฒฐ๊ณผ๋ฅผ ์ ๋ ฌํ ์ ์๋ ํ๋ผ๋ฏธํฐ๋ฅผ ์ง์ํฉ๋๋ค. ํน์ ์์ฑ์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๋ฅผ ์ ๋ ฌํ๋ ค๋ฉด sort URL ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
curl -v "http://localhost:8080/people/search/nameStartsWith?name=K&sort=name,desc"์ฌ๋ฌ ์์ฑ์ ๊ธฐ์ค์ผ๋ก ์ ๋ ฌํ๋ ค๋ฉด sort=PROPERTY ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ๋ฌ ๊ฐ ์ถ๊ฐํ ์ ์์ต๋๋ค. ์์ฑ ๊ฒฝ๋ก ํ๊ธฐ๋ฒ์ ์ฌ์ฉํด ์ค์ฒฉ๋ ์์ฑ์ผ๋ก๋ ์ ๋ ฌํ ์ ์์ผ๋ฉฐ, ์ด ์์์ ๋ฐ๋ผ Pageable์ ์ ๋ ฌ ์ ๋ณด๊ฐ ์ถ๊ฐ๋ฉ๋๋ค.
Spring Data REST๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ฉ์ธ ๋ชจ๋ธ์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ด๋ณด๋ผ ์ ์์ต๋๋ค. ํ์ง๋ง ๋๋ก๋ ๋ค์ํ ์ด์ ๋ก ํด๋น ๋ชจ๋ธ์ ๋ทฐ๋ฅผ ๋ณ๊ฒฝํด์ผ ํ ํ์๊ฐ ์์ต๋๋ค. ์ด ์น์ ์์๋ ๋ฆฌ์์ค์ ๋ทฐ๋ฅผ ๋จ์ํํ๊ณ ์ถ์๋ ํํ๋ก ์ ๊ณตํ๊ธฐ ์ํด ํ๋ก์ ์ ๊ณผ ์์ํ์ธ ๋ฅผ ์ ์ํ๋ ๋ฐฉ๋ฒ์ ๋ค๋ฃน๋๋ค.
Projections?
Projections
ํด๋น ๋๋ฉ์ธ ๊ฐ์ฒด์ ๋ํ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ์๋์ ๊ฐ์ด ์์ฑํ๋ค๊ณ ๊ฐ์
interface PersonRepository extends CrudRepository<Person, Long> {}๊ธฐ๋ณธ์ ์ผ๋ก Spring Data REST๋ ์ด ๋๋ฉ์ธ ๊ฐ์ฒด๋ฅผ ๋ชจ๋ ์์ฑ์ ํฌํจํ ํํ๋ก ๋ด๋ณด๋
๋๋ค. firstName๊ณผ lastName์ ๋จ์ํ ๋ฐ์ดํฐ ๊ฐ์ฒด๋ก ๋ด๋ณด๋ด์ง๊ณ , address ์์ฑ์ ๋ํด์๋ ๋ ๊ฐ์ง ์ต์
์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ์ต์
์ Address ๊ฐ์ฒด์ ๋ํ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ์ ์ํ๋ ๊ฒ์
๋๋ค
interface AddressRepository extends CrudRepository<Address, Long> {}์ด ๊ฒฝ์ฐ, Person ๋ฆฌ์์ค๋ address ์์ฑ์ ํด๋น ์ฃผ์ ๋ฆฌ์์ค๋ก ์ฐ๊ฒฐ๋๋ URI๋ก ๋ ๋๋งํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์์คํ
์์ "Frodo"๋ฅผ ์กฐํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ HAL ๋ฌธ์๋ฅผ ๋ณผ ์ ์์ต๋๋ค:
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
},
"address" : {
"href" : "http://localhost:8080/persons/1/address"
}
}
}๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก Address ๋๋ฉ์ธ ๊ฐ์ฒด์ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ์ ์ํ์ง ์์ผ๋ฉด, Spring Data REST๋ Person ๋ฆฌ์์ค ๋ด์ ์ฃผ์ ๋ฐ์ดํฐ๋ฅผ ํฌํจํ์ฌ ๋ค์๊ณผ ๊ฐ์ด ๋ฐํํฉ๋๋ค:
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"address" : {
"street": "Bag End",
"state": "The Shire",
"country": "Middle Earth"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
}
}
}ํ์ง๋ง ์ฃผ์ ์ ๋ณด๋ฅผ ์ ํ ํฌํจํ์ง ์๊ณ ์ ํ ๊ฒฝ์ฐ, ๊ธฐ๋ณธ์ ์ผ๋ก Spring Data REST๋ id๋ฅผ ์ ์ธํ ๋ชจ๋ ์์ฑ์ ๋ด๋ณด๋
๋๋ค. ๋ค์์ ์ฃผ์๋ฅผ ํฌํจํ์ง ์๋ ํ๋ก์ ์
์ ์์
๋๋ค:
@Projection(name = "noAddresses", types = { Person.class })
interface NoAddresses {
String getFirstName();
String getLastName();
}@Projection ์ด๋
ธํ
์ด์
์ ์ด ์ธํฐํ์ด์ค๋ฅผ ํ๋ก์ ์
์ผ๋ก ํ์ํฉ๋๋ค. name ์์ฑ์ ํ๋ก์ ์
์ ์ด๋ฆ์ ์ง์ ํ๋ฉฐ, types ์์ฑ์ ์ด ํ๋ก์ ์
์ด Person ๊ฐ์ฒด์๋ง ์ ์ฉ๋๋๋ก ํฉ๋๋ค. ์ด ํ๋ก์ ์
์ firstName๊ณผ lastName๋ง์ ๋ด๋ณด๋ด๊ณ , address ์ ๋ณด๋ ์ ์ธ๋ฉ๋๋ค.
Excerpts
์์ํ์ธ ๋ ์ปฌ๋ ์
๋ฆฌ์์ค์ ์๋์ผ๋ก ์ ์ฉ๋๋ ํ๋ก์ ์
์ ์ผ์ข
์ผ๋ก, ๋ฆฌ์์ค ์ปฌ๋ ์
์ ๋จ์ํ๋ ํํ๋ก ์ ๊ณตํ ๋ ์ ์ฉํฉ๋๋ค. ์๋ฅผ ๋ค์ด, PersonRepository์ ์์ํ์ธ ํ๋ก์ ์
์ ์ ์ฉํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ํ ์ ์์ต๋๋ค:
@RepositoryRestResource(excerptProjection = NoAddresses.class)
interface PersonRepository extends CrudRepository<Person, Long> {}์ด๋ ๊ฒ ํ๋ฉด Person ๋ฆฌ์์ค๊ฐ ์ปฌ๋ ์
์ ํฌํจ๋ ๋ NoAddresses ํ๋ก์ ์
์ด ์๋์ผ๋ก ์ ์ฉ๋ฉ๋๋ค.
์์ํ์ธ ๋ ๊ฐ๋ณ ๋ฆฌ์์ค์๋ ์๋์ผ๋ก ์ ์ฉ๋์ง ์์ผ๋ฉฐ, ์ปฌ๋ ์ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ณธ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํํ๋ก ์ ๊ณตํ๋ ๋ฐ ์ค์ ์ ๋ก๋๋ค.
์ด๋ ๊ฒ ํ๋ก์ ์ ๊ณผ ์์ํ์ธ ๋ฅผ ์ฌ์ฉํ๋ฉด, ๋๋ฉ์ธ ๋ชจ๋ธ์ ๋ค์ํ ํํ๋ก ์ ๊ณตํ ์ ์์ผ๋ฉฐ, ํ์์ ๋ฐ๋ผ ๋ ธ์ถ๋๋ ๋ฐ์ดํฐ๋ฅผ ์กฐ์ ํ ์ ์์ต๋๋ค.
Last updated