ํด๋น ๊ธ์ ๋ฐฐ์๋ณด์ Spring Data JPA ์๋ฆฌ์ฆ ์ ๋๋ค.
ํด๋น ์๋ฆฌ์ฆ์ ๋ด์ฉ์ด ์ด์ด์ง๋ ํํ์ด๋ฏ๋ก ๊ธ์ ๋ด์ฉ ์ค์ ์๋ต๋๋ ๋ง๋ค์ด ์์ ์ ์์ผ๋, ์์ธํ ์ฌํญ์ ์๋ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์!
- Spring Data JPA์ ๊ธฐ๋ณธ๊ณผ ํ๋ก์ ํธ ์์ฑ :: ์๋ฆฌ์ฆ ํ์ต ํ๊ฒฝ ์ค๋น
- JPA์ ๊ธฐ๋ณธ๊ณผ Spring Data JPA
- Springboot project ์์ JPA ์ค์ ํ๊ธฐ
- JPA์ ๊ธฐ๋ณธ ์ด๋
ธํ
์ด์
๋ค :: JPA์ ์์๊ณผ ๋์์ ๋
- ์ํฐํฐ์ ํ ์ด๋ธ ๋งคํ
- ํ๋์ ์ปฌ๋ผ ๋งคํ
- ๋งคํ ํ
์ด๋ธ๊ณผ ์ฐ๊ด๊ด๊ณ ๋งคํํ๊ธฐ :: RDB์ ๊ฝ, ์ฐ๊ด ๊ด๊ณ
- ์ฐ๊ด๊ด๊ณ๋?, ์ธ๋ ํค๋?, ๋งคํ ํ ์ด๋ธ์ด๋?
- ์ผ๋์ผ, ์ผ๋๋ค, ๋ค๋์ผ ์ฐ๊ด๊ด๊ณ
- ๊ณตํต ์ธํฐํ์ด์ค ๊ธฐ๋ฅ :: ์ด๋ป๊ฒ Data JPA ๋ ๋์ํ ๊น?
- ๋จ๊ฑด ์กฐํ ๋ฐํ ํ์
- ์ปฌ๋ ์ ์กฐํ ๋ฐํ ํ์
- ์ฌ์ฉ์ ์ ์ ์ฟผ๋ฆฌ ์ด์ฉํ๋ ๋ฐฉ๋ฒ :: ๋ด๊ฐ ์ํ๋ ์ฟผ๋ฆฌ๋ฅผ JPA ์์ ๋ง๋ค์ด๋ณด์!
- ๋ฉ์๋ ์ด๋ฆ์ผ๋ก ์ฟผ๋ฆฌ ์์ฑํ๊ธฐ
@Query
๋ฅผ ์ด์ฉํ์ฌ ๋ฉ์๋์ ์ ์ ์ฟผ๋ฆฌ ์์ฑํ๊ธฐ
- ํ์ด์ง๊ณผ ์ ๋ ฌ :: ๊ฒ์ํ๊ณผ ๊ฐ์ ํ์ด์ง๊ฐ ์๋ ์๋น์ค์์ ๋น์ ๋ฐํ๋ JPA ํ์ด์ง!
- Data JPA์ ํ์ด์ง๊ณผ ์ ๋ ฌ
- Web MVC ์์ JPA ํ์ด์ง๊ณผ ์ ๋ ฌ
- Auditing :: ๋ชจ๋ ์์ฒญ๊ณผ ์๋ต์
๋๊ฐ, ์ธ์
์ ๊ทผํ๋์ง ํ๋์ ์ํฐํฐ๋ก ๊ด๋ฆฌํ์.- ์์ JPA์ Auditing
- Spring Data JPA์ Auditing
- ๋ฐฐ์๋ณด์ Spring Data JPA ์๋ฆฌ์ฆ๋ฅผ ๋ง์น๋ฉฐ...
- ์๋ฆฌ์ฆ๋ฅผ ๋ง์น๋ฉฐ ๋๋์
- ๋ด๊ฐ ์ ๋ณด๋ฅผ ์ป์ ๊ณณ
- ํด๋น ์๋ฆฌ์ฆ๋ฅผ ์์ฃผํ์ จ๋์?
๊ฒ์ํ์ด๋ ๋๊ธ, ๋ธ๋ก๊ทธ๋ฅผ ๊ฐ๋ฐํ ๋ ํ์ด์ง์ ์์ฃผ ์ค์ํ ์ญํ ์ ํ๋ค.
ํ์ด์ง์ ๋ง์ ์ ๋ณด, ์ด๋ฅผํ ๋ฉด ๊ฒ์ํ์ ์กด์ฌํ๋ ์๋ฐฑ ์์ฒ๊ฐ์ ๊ฒ์๊ธ๊ณผ ๊ฐ์ ์ ๋ณด๋ค์ ํ์ด์ง๋ก ๋๋ ํจ๊ณผ์ ์ผ๋ก ์ ๋ณด๋ฅผ ์ ๊ณตํ๊ฒ ํ๋ ์ญํ ์ ํ๋ค.
์ด๋ฌํ ํ์ด์ง์ ๊ฐ๋ฐํ๊ธฐ ์ํด์๋ page ๊ด๋ จ ์ฟผ๋ฆฌ๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์์ ์ง์ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ด ์์์ง๋ง JPA ์์ ๋ Spring Data ํ๋ก์ ํธ์์๋ ํจ๊ณผ์ ์ผ๋ก ํ์ด์ง์ ์ฒ๋ฆฌํ ์ ์๊ฒ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค.
Spring Data JPA ์์์ ํ์ด์ง๊ณผ ์ ๋ ฌ
Spring Data JPA ์์๋ ์์ ๋ง ํ๋ฏ ํ์ด์ง๊ณผ ์ ๋ ฌ์ ์์ฃผ ๊ฐ๋ ฅํ ๋ฐฉ๋ฒ์ ์ ๊ณตํ๋ค.
์ง๋ ์๊ฐ์ ๋ดค๋ JpaRepository
์ธํฐํ์ด์ค์ ์์ ๋ค์ด์ด๊ทธ๋จ์ ์ดํด๋ณด์.
JpaRepository
์ ๋ถ๋ชจ ์ธํฐํ์ด์ค์ธ PagingAndSortingRepository ์์ ํ์ด์ง๊ณผ ์ํ
์ด๋ผ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
findAll()
๋ฉ์๋์ ๋ฐํ ํ์
๊ณผ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ฒ๋ค์ด ์กด์ฌํ๋ค.
org.springframework.data.domain.Pageable
- ํ์ด์ง์ ์ ๊ณตํ๋ ์ค์ํ ์ธํฐํ์ด์ค์ด๋ค.
org.springframework.data.domain.Page
- ํ์ด์ง์ findAll() ์ ๊ธฐ๋ณธ์ ์ธ ๋ฐํ ๋ฉ์๋๋ก ์ฌ๋ฌ ๋ฐํ ํ์ ์ค ํ๋์ด๋ค.
๊ฒฐ๊ตญ ์ฐ๋ฆฌ๋ JpaRepository<>
๋ฅผ ์ฌ์ฉํ ๋, findAll()
๋ฉ์๋๋ฅผ Pageable
์ธํฐํ์ด์ค๋ก ํ๋ผ๋ฏธํฐ๋ฅผ ๋๊ธฐ๋ฉด ํ์ด์ง์ ์ฌ์ฉํ ์ ์๊ฒ๋๋ค.
ํ ๋ฒ ์ฌ์ฉํด๋ณด์.
์ด๋ฒ์๋ controller ๋ฅผ ๋ง๋ค์ด์ ์ฌ์ฉํด๋ณด์.
@RestController
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping("/users")
public Page<User> getAllUsers() {
PageRequest pageRequest = PageRequest.of(0, 5);
return userRepository.findAll(pageRequest);
}
@PostConstruct
public void initializing() {
for (int i = 0; i < 100; i++) {
User user = User.builder()
.username("User " + i)
.address("Korea")
.age(i)
.build();
userRepository.save(user);
}
}
}
getAllUsers()
๋ฉ์๋์ ๋ณด๋ฉด PageRequest
๊ฐ์ฒด๊ฐ ์กด์ฌํ๋ค.
PageRequest
๊ฐ์ฒด๋ Pageable
์ธํฐํ์ด์ค๋ฅผ ์์๋ฐ๋๋ค.
์ฝ๊ฒ Paging ์ ์ํ ์ ๋ณด๋ฅผ ๋๊ธธ ์ ์๋๋ฐ, ์ด ์ ๋ณด์๋ ์ ๋ ฌ ์ ๋ณด, ํ์ด์ง offset, page์ ๊ฐ์ ์ ๋ณด๊ฐ ๋ด๊ฒจ์๋ค.
์๋๋ผ๋ฉด ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฐ์ดํฐ๊ฐ DB์ ์ ์ฅ๋์ด ์์ด์ผํ๋ค.
ํ์ง๋ง ์ฐ๋ฆฌ๋ hibernate ์ ๋ต์ create ๋ก ์ก์๊ธฐ ๋๋ฌธ์ DB์ ์ ์ฅํ๋ ๊ฒ์ด ํ๋ค๋ค.
๋ฌผ๋ก hibernate.ddl-auto ์ ๋ต์ update ๋ก ์ก๋๋ค๋ฉด DB์ ์ ์ฅํ ์ ์์ง๋ง ํ์ด์ง์ ํ ์คํธํ 100๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ค ๋ฃ๋ ๊ฒ ๋ณด๋ค Spring ์์ ์ ๊ณตํ๋ ์ด๊ธฐํ ๋ฉ์๋๋ฅผ ์ด์ฉํด์ ์ด๊ธฐํ๋ฅผ ํ ๊ฒ์ด๋ค.
initializing() ์ ์ด์ฉํด์ 100๋ช ์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํด์ฃผ๊ณ api ํธ์ถ์ ํด๋ณด์.
api ํธ์ถ์ ๋ธ๋ผ์ฐ์ ์์ ์ง์ url ๋ก ํด๋ ์ข๊ณ curl ์ ํด๋ ์ข์ง๋ง ๋๋ Postman์ ์ด์ฉํ ๊ฒ์ด๋ค.
๊ฒฐ๊ณผ๋ฅผ ๋ด๋ณด์.
"content":[
{
"content": [
{"id": 1, "username": "User 0", "address": "Korea", "age": 0},
// ์ค๊ฐ ์๋ต
{"id": 5, "username": "User 4", "address": "Korea", "age": 4}
],
"pageable": {
"sort": {
"sorted": false, // ์ ๋ ฌ ์ํ
"unsorted": true,
"empty": true
},
"pageSize": 5, // ํ ํ์ด์ง์์ ๋ํ๋ด๋ ์์์ ์ (๊ฒ์๊ธ ์)
"pageNumber": 0, // ํ์ด์ง ๋ฒํธ (0๋ฒ ๋ถํฐ ์์)
"offset": 0, // ํด๋น ํ์ด์ง์ ์ฒซ ๋ฒ์งธ ์์์ ์
"paged": true,
"unpaged": false
},
"totalPages": 20, // ํ์ด์ง๋ก ์ ๊ณต๋๋ ์ด ํ์ด์ง ์
"totalElements": 100, // ๋ชจ๋ ํ์ด์ง์ ์กด์ฌํ๋ ์ด ์์ ์
"last": false,
"number": 0,
"sort": {
"sorted": false,
"unsorted": true,
"empty": true
},
"size": 5,
"numberOfElements": 5,
"first": true,
"empty": false
}
์ด์ ๊ฐ์ด Paging ์ด ์ ์ฉ๋ ์๋ต์ ๋ฐ์ ์ ์๋ค.
content
์๋์ ์๋ ๋ฐ์ดํฐ๋ค์ด paging
๊ณผ ๊ด๋ จ๋ ์ ๋ณด๋ค์ด๋ค.
๋ฉ์ง๋ค.
ํ์ง๋ง ๋ ๋ฉ์ง ๊ฒ์ ์ฟผ๋ฆฌ ๋ฉ์๋ ๊ธฐ๋ฅ์์๋ ์ ๊ณตํ๋ค๋ ๊ฒ์ด๋ค.
์ฟผ๋ฆฌ ๋ฉ์๋์์ ํ์ด์ง ์ฌ์ฉํ๊ธฐ
public interface UserRepository extends JpaRepository<User, Long> {
Page<User> findByAddress(String address, Pageable pageable);
}
์ด๋ ๊ฒ ์ฌ์ฉ์์ ์ฃผ์๋ก ์กฐํํ๋ ์ฟผ๋ฆฌ ๋ฉ์๋๋ฅผ ๋ง๋ค๊ณ ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ๋ก Pageable
์ ๋๊ฒจ์ฃผ๋ฉด ๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ปจํธ๋กค๋ฌ์ ๊ฐ์ ๋ค์ PageRequest
๋ฅผ ๋ง๋ค์ด์ฃผ์.
์ด๋ฒ์๋ ๋ ๋์๊ฐ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ๋์ด์จ ๊ฐ์ ํ์ด์ง ์ ๋ณด๋ก ๋ง๋ค์ด ๋ณผ ๊ฒ์ด๋ค.
@RestController
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping("/users")
public Page<User> getAllUserWithPageByQueryMethod(@RequestParam("page") Integer page, @RequestParam("size") Integer size) {
PageRequest pageRequest = PageRequest.of(page, size);
return userRepository.findByAddress("Korea", pageRequest);
}
@PostConstruct
public void initializing() {
for (int i = 0; i < 100; i++) {
User user = User.builder()
.username("User " + i)
.address("Korea")
.age(i)
.build();
userRepository.save(user);
}
}
}
์ด๋ ๊ฒ ๋ง๋ค๊ณ http://localhost:8080/users/?page=3&size=4
๋ก ์์ฒญ์ ๋ณด๋ด๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ฒฐ๊ณผ๊ฐ ์ถ๋ ฅ๋๋ค.
{
"content": [
{ "id": 13, "username": "User 12", "address": "Korea", "age": 12 },
{ "id": 14, "username": "User 13", "address": "Korea", "age": 13 },
{ "id": 15, "username": "User 14", "address": "Korea", "age": 14 },
{ "id": 16, "username": "User 15", "address": "Korea", "age": 15 }
],
"pageable": {
"sort": { "sorted": false, "unsorted": true, "empty": true },
"pageNumber": 3,
"pageSize": 4,
"offset": 12,
"paged": true,
"unpaged": false
},
"totalPages": 25,
"totalElements": 100,
"last": false,
"numberOfElements": 4,
"number": 3,
"sort": { "sorted": false, "unsorted": true, "empty": true },
"size": 4,
"first": false,
"empty": false
}
๋ฐํ ํ์ ์ ๋ฐ๋ฅธ ํ์ด์ง ๊ฒฐ๊ณผ
Spring Data JPA ์๋ ๋ฐํ ํ์ ์ ๋ฐ๋ผ์ ๊ฐ๊ธฐ ๋ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํ๋ค.
Page<T>
ํ์Slice<T>
ํ์List<T>
ํ์
๊ฐ์ ๋ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํด์ค๋ค.
Page<T>
ํ์
Page<T>
ํ์
์ ๋ฐํ ํ์
์ผ๋ก ๋ฐ๊ฒ ๋๋ค๋ฉด offset๊ณผ totalPage ๋ฅผ ์ด์ฉํ์ฌ ์๋น์ค๋ฅผ ์ ๊ณตํ ์ ์๊ฒ๋๋ค.
Page<T>
๋ ์ผ๋ฐ์ ์ธ ๊ฒ์ํ ํํ์ ํ์ด์ง์์ ์ฌ์ฉ๋๋ค.
์ฌ๊ธฐ์ ์ค์ํ ์ ๋ณด๋ ์ด ํ์ด์ง ์ ์ด๋ค.
๊ทธ ์ ๋ณด๋ฅผ ํฌํจํ์ฌ ๋ฐํํ๋ค.
Page<T>
ํ์
์ count ์ฟผ๋ฆฌ๋ฅผ ํฌํจํ๋ ํ์ด์ง์ผ๋ก ์นด์ดํธ ์ฟผ๋ฆฌ๊ฐ ์๋์ผ๋ก ์์ฑ๋์ด ํจ๊ป ๋๊ฐ๋ค.
Slice<T>
ํ์
Slice<T>
ํ์
์ ๋ฐํ ํ์
์ผ๋ก ๋ฐ๊ฒ ๋๋ค๋ฉด ๋๋ณด๊ธฐ ํํ์ ํ์ด์ง์์ ์ฌ์ฉ๋๋ค.
์๋ต์ ์ดํด๋ณด์.
{
"content": [
{ "id": 13, "username": "User 12", "address": "Korea", "age": 12 },
{ "id": 14, "username": "User 13", "address": "Korea", "age": 13 },
{ "id": 15, "username": "User 14", "address": "Korea", "age": 14 },
{ "id": 16, "username": "User 15", "address": "Korea", "age": 15 }
],
"pageable": {
"sort": { "sorted": false, "unsorted": true, "empty": true },
"pageNumber": 3,
"pageSize": 4,
"offset": 12,
"paged": true,
"unpaged": false
},
"number": 3,
"numberOfElements": 4,
"first": false,
"last": false,
"size": 4,
"sort": { "sorted": false, "unsorted": true, "empty": true },
"empty": false
}
Page<T>
ํ์
์ ๋ฐํ์ ์๋ ๊ฒ๋ค์ด ์กด์ฌํ๋ค.
number๊ณผ numberOfElements ๊ทธ๋ฆฌ๊ณ Page<T>
์ ์กด์ฌํ๋ totalPages, totalElements ๊ฐ ์์ด์ก๋ค.
Slice<T>
ํ์
์ ์ถ๊ฐ count ์ฟผ๋ฆฌ ์์ด ๋ค์ ํ์ด์ง ํ์ธ ๊ฐ๋ฅํ๋ค. ๋ด๋ถ์ ์ผ๋ก limit + 1 ์กฐํ๋ฅผ ํด์ totalCount ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ์ง ์์์ ์ฑ๋ฅ์ ์กฐ๊ธ ์ด์ ์ ๋ณผ ์๋ ์๋ค.
List<T>
ํ์
@GetMapping("/users")
public List<User> getAllUsers(Pageable pageable) {
return userRepository.findAll(pageable);
}
List ๋ฐํ ํ์ ์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก count ์ฟผ๋ฆฌ ์์ด ๊ฒฐ๊ณผ๋ง ๋ฐํํ๋ค.
Spring Web MVC ์์ ๋ ํธํ๊ฒ ํ์ด์งํ๊ธฐ
Spring Data JPA์ ํ์ด์ง๊ณผ ์ ๋ ฌ ๊ธฐ๋ฅ๋ณด๋ค ํจ์ฌ ๊ฐํธํ๊ฒ MVC ์์ ์ฌ์ฉํ ์ ์๊ฒ ํ๋ค.
์ฆ, ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉ์๊ฐ ์ ์ํ ํ๋ผ๋ฏธํฐ์ ๋ฐ๋ผ์๋ ํ์ด์ง์ด ๊ฐ๋ฅํ๋ค๋ ์๋ฆฌ์ด๋ค.
@GetMapping("/users")
public Page<User> getAllUsers(Pageable pageable) {
return userRepository.findAll(pageable);
}
์ปจํธ๋กค๋ฌ์์ @GetMapping
์ ํ๋ฆฌ๋ฏธํฐ๋ก Pageable
์ ์ถ๊ฐํ๋ฉด ๋๋ค.
๊ทธ๋ผ ํ์ด์ง ๊ด๋ จ ์ฟผ๋ฆฌ๊ฐ ๋์จ๋ค.
Springboot ๋ด๋ถ์์ url ํ๋ผ๋ฏธํฐ๊ฐ ์ปจํธ๋กค๋ฌ์ ๋ฐ์ธ๋ฉ์ด ๋ ๋, Pageable์ด ์กด์ฌํ๋ฉด PageRequest ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
ํด๋น ๊ฐ์ฒด์์ ์ญ์ ์ ๋ ฌ๋ ์ ๊ณตํ๋๋ฐ, url์ ๋ค์๊ณผ ๊ฐ์ด ์น๋ฉด ์ ๋ ฌ๊ณผ ํ์ด์ง์ด ๋์์ ์ํ๋๊ฒ ํ ์ ์๋ค.
http://localhost:8080/members?page=0
- 0๋ฒ ํ์ด์ง ๋ถํฐ 20๊ฐ ์กฐํํ๋ค.
- default ๊ฐ 20๊ฐ๋ก default๋ฅผ ์์ ํ๋ ๋ฐฉ๋ฒ๋ ์กด์ฌํ๋ค.
- 0๋ฒ ํ์ด์ง ๋ถํฐ 20๊ฐ ์กฐํํ๋ค.
http://localhost:8080/members?page=0&size=5
- 0๋ฒ ํ์ด์ง๋ถํฐ 5๊ฐ ์กฐํํ๋ค.
http://localhost:8080/members?page=0&size=5&sort=id.desc
- 0๋ฒ ํ์ด์ง๋ถํฐ 5๊ฐ ์กฐํ ํ๋๋ฐ, id์ ์ญ์์ผ๋ก ์กฐํํ๋ค.
์ด๋ ๊ฒ ์ค๋์ JPA ํ์ด์ง์ ๋ํ ๊ธฐ๋ณธ์ ์์๋ณด์๋ค.
๋ด์ผ์ ์ฐ๋ฆฌ์ ๋ง์ง๋ง ์ฌ์ ์ธ JPA Auditing ์ ๋ํด์ ์์๋ณผ ๊ฒ์ด๋ค!
๋๊ธ