path variable 기반으로 코드를 작성해보자.
// MemberController Class
package study.datajpa.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import study.datajpa.entity.Member;
import study.datajpa.repository.MemberRepository;
import javax.annotation.PostConstruct;
@RestController
@RequiredArgsConstructor
public class MemberController {
private final MemberRepository memberRepository;
@GetMapping("/members/{id}")
public String findMember(@PathVariable("id") Long id) {
Member member = memberRepository.findById(id).get();
return member.getUsername();
}
@PostConstruct
public void init() {
memberRepository.save(new Member("userA"));
}
}
// DataJpaApplication
package study.datajpa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import java.util.Optional;
import java.util.UUID;
@EnableJpaAuditing
@SpringBootApplication
public class DataJpaApplication {
public static void main(String[] args) {
SpringApplication.run(DataJpaApplication.class, args);
}
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.of(UUID.randomUUID().toString());
}
}
결과는 아래와 같이 나온다.
insert into member (age, created_date, tema_id, username, member_id) values (?, ?, ?, ?, ?)
insert into member (age, created_date, tema_id, username, member_id) values (0, NULL, NULL, 'userA', 1);
8080번 포트로 들어가서 지정한 경로대로 들어가면 userA가 뜨게 된다.
MemberController Class에서 아래와 같이 지정을 해주고 확인을 해주면 userA가 나오게 된다.
@GetMapping("/members2/{id}")
public String findMember(@PathVariable("id") Member member) {
return member.getUsername();
}
// MemberController Class
package study.datajpa.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import study.datajpa.entity.Member;
import study.datajpa.repository.MemberRepository;
import javax.annotation.PostConstruct;
@RestController
@RequiredArgsConstructor
public class MemberController {
private final MemberRepository memberRepository;
@GetMapping("/members/{id}")
public String findMember(@PathVariable("id") Long id) {
Member member = memberRepository.findById(id).get();
return member.getUsername();
}
@GetMapping("/members2/{id}")
public String findMember(@PathVariable("id") Member member) {
return member.getUsername();
}
@PostConstruct
public void init() {
memberRepository.save(new Member("userA"));
}
}
- HTTP 요청은 회원 id를 받지만 도메인 클래스 컨버터가 중간에 동작해서 회원 엔티티 객체를 반환 하고
- 도메인 클래스 컨버터도 리파지토리를 사용해서 엔티티를 찾는다.
- 주의할 점은 도메인 클래스 컨버터로 엔티티를 파라미터로 받으면, 엔티티는 단순 조회용으로만 사용해야한다.
- => 트랜잭션이 없는 범위에서 엔티티를 조회했으므로, 엔티티를 변경해도 DB에 반영되지 않는다.
페이징과 정렬
아래와 같이 코드를 작성하고
// MemberController Class
package study.datajpa.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import study.datajpa.entity.Member;
import study.datajpa.repository.MemberRepository;
import javax.annotation.PostConstruct;
@RestController
@RequiredArgsConstructor
public class MemberController {
private final MemberRepository memberRepository;
@GetMapping("/members/{id}")
public String findMember(@PathVariable("id") Long id) {
Member member = memberRepository.findById(id).get();
return member.getUsername();
}
@GetMapping("/members2/{id}")
public String findMember(@PathVariable("id") Member member) {
return member.getUsername();
}
// 페이징과 정렬
@GetMapping("/members")
public Page<Member> list(Pageable pageable) {
Page<Member> page = memberRepository.findAll(pageable);
return page;
}
@PostConstruct
public void init() {
for (int i = 0; i < 100; i++) {
memberRepository.save(new Member("user" + i, i));
}
}
}
DataJpaApplication을 돌리면
// DataJpaApplication
package study.datajpa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import java.util.Optional;
import java.util.UUID;
@EnableJpaAuditing
@SpringBootApplication
public class DataJpaApplication {
public static void main(String[] args) {
SpringApplication.run(DataJpaApplication.class, args);
}
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.of(UUID.randomUUID().toString());
}
}
localhost 8080에서 아래와 같이 나오게 되고
postman에서도 확인을 할 수 있다.
sort도 넣어서 desc 내림차순으로 보여지게 할 수도 있다.
즉 요청 파라미터는 아래의 특징을 갖는데
- 예) /members?page=0&size=3&sort=id,desc&sort=username,desc
- page: 현재 페이지, 0부터 시작한다.
- size: 한 페이지에 노출할 데이터 컨수
- sort: 정렬 조건을 정의한다. 예) 정렬 속성,(ASC | DESC), 정렬 방향을 변경하고 싶으면 sort 파라미터 추가(asc 생략 가능)
이제 application.yml에서 global 설정을 해보자.
// application.yml
spring:
datasource:
url: jdbc:h2:tcp://localhost/~/datajpa
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
# dialect: org.hibernate.dialect.Oracle10gDialect
# show_sql: true
format_sql: true
# 여기부분
data:
web:
pageable:
default-page-size: 10
max-page-size: 2000
logging.level:
org.hibernate.SQL: debug
# org.hibernate.type: trace
그러면 postman에서 members 조회시 딱 10개만 나오게 된다.
그리고 memberControllers에만 특별한 설정을 해주고 싶으면 아래와 같이 해줄 수도 있다.
@GetMapping("/members")
public Page<Member> list(@PageableDefault(size = 5) Pageable pageable) {
Page<Member> page = memberRepository.findAll(pageable);
return page;
}
@PostConstruct
public void init() {
for (int i = 0; i < 100; i++) {
memberRepository.save(new Member("user" + i, i));
}
}
반환타입이 Page이므로 totalCountQuery도 나가게 된다.
그리고 엔티티(Member)를 외부에 그대로 노출시키면 안된다.
API를 갖다 쓰는 사람이 API의 스펙이 바뀌어 버리는 위험성을 가질 수있어서
API 스펙일 DTO로 변환을 해주면 좋다.
// MemberController Class
@GetMapping("/members")
public Page<MemberDto> list(@PageableDefault(size = 5) Pageable pageable) {
Page<Member> page = memberRepository.findAll(pageable);
Page<MemberDto> map = page.map(member -> new MemberDto(member.getId(), member.getUsername(), null));
return map;
}
@GetMapping("/members")
public Page<MemberDto> list(@PageableDefault(size = 5) Pageable pageable) {
return memberRepository.findAll(pageable)
.map(member -> new MemberDto(member.getId(), member.getUsername(), null));
}
아래와 같이 DTO가 반환되는 것을 볼 수 있다.
그리고 DTO는 엔티티를 봐도 괜찮기 때문에 MemberController에서 아래와 같이 설정을 해줄 수 있다.
// MemberDto
package study.datajpa.dto;
import lombok.Data;
import study.datajpa.entity.Member;
@Data
public class MemberDto {
private Long id;
private String username;
private String teamName;
public MemberDto(Long id, String username, String teamName) {
this.id = id;
this.username = username;
this.teamName = teamName;
}
public MemberDto(Member member) {
this.id = member.getId();
this.username = member.getUsername();
}
}
MemberController에서는 아래와 같이 설정을 해주고
// MemberController Class
@GetMapping("/members")
public Page<MemberDto> list(@PageableDefault(size = 5) Pageable pageable) {
return memberRepository.findAll(pageable)
.map(member -> new MemberDto((member));
}
이렇게 바꿔줄 수가 있다.
@GetMapping("/members")
public Page<MemberDto> list(@PageableDefault(size = 5) Pageable pageable) {
return memberRepository.findAll(pageable)
.map(MemberDto::new);
}
만약에 Page를 1부터 시작하게 할려면 두가지 방법이 있는데 두번째 방법을 적용해서 postman에서 확인을 해보면
page=0 과 1이 똑같이 1~5까지 실행이 되는 것이다.
spring:
datasource:
url: jdbc:h2:tcp://localhost/~/datajpa
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
# dialect: org.hibernate.dialect.Oracle10gDialect
# show_sql: true
format_sql: true
data:
web:
pageable:
default-page-size: 10
max-page-size: 2000
// one-indexed-parameters: true 설정을 해주고
one-indexed-parameters: true
postman에서 확인을 해보면
page=0 과 1이 똑같이 1~5까지 실행이 되는 것이다.
<출처 김영한: 실전! 스프링 데이터 JPA >
'Spring > SpringDataJPA' 카테고리의 다른 글
새로운 엔티티를 구별하는 방법 (0) | 2022.04.18 |
---|---|
스프링 데이터 JPA 구현체 분석 (0) | 2022.04.18 |
사용자 정의 리포지토리 구현 (0) | 2022.04.18 |
JPA Hint & Lock (0) | 2022.04.17 |
@EntityGraph (0) | 2022.04.17 |