HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩하는 것
도메인 클래스 컨버터 사용x
@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();
}
}
도메인 클래스 컨버터 사용
@RestController
@RequiredArgsConstructor
public class MemberController {
private final MemberRepository memberRepository;
@GetMapping("/members/{id}")
public String findMember(@PathVariable("id") Member member) {
return member.getUsername();
}
}
주의: 도메인 클래스 컨버터로 엔티티를 파라미터로 받으면, 이 엔티티는 단순 조회용으로만 사용해야 한다. (트랜잭션이 없는 범위에서 엔티티를 조회했으므로, 엔티티를 변경해도 DB에 반영되지 않는다.)
스프링 데이터가 제공하는 페이징과 정렬 기능을 스프링 MVC에서 편리하게 사용 가능
@GetMapping("/members")
public Page<Member> list(Pageable pageable) {
Page<Member> page = memberRepository.findAll(pageable);
return page;
}
예시 : /members?page=0&size=3&sort=id,desc&sort=username,desc
page : 페이지, 0부터 시작
size : 한 페이지당 데이터 갯수
sort : 정렬 조건(asc, desc)
아무것도 쓰지않은 default 페이지 사이즈 값은 20이다.
만약 default 값을 바꾸고 싶다면?
글로벌 설정(application.yml 파일)
spring
data:
web:
pageable:
default-page-size: 10
개별 설정(@PageableDefault 어노테이션을 사용)
@RequestMapping(value = "/members_page", method = RequestMethod.GET)
public String list(@PageableDefault(size = 12, sort = “username”, direction = Sort.Direction.DESC) Pageable pageable) {
...
}
예시 : /members?member_page=0&order_page=1
다음 예시와 같이 페이징 정보가 둘 이상이면 @Qualifier 에 접두사명 추가하여 접두사로 구분이 가능하다
public String list(
@Qualifier("member") Pageable memberPageable,
@Qualifier("order") Pageable orderPageable,
@Data
public class MemberDto {
private Long id;
private String username;
public MemberDto(Member m) {
this.id = m.getId();
this.username = m.getUsername();
}
}
다음과 같이 MemberDto 클래스를 만들어 주고
@GetMapping("/members")
public Page<MemberDto> list(Pageable pageable) {
Page<Member> page = memberRepository.findAll(pageable);
Page<MemberDto> map = page.map(MemberDto::new);
return map;
}
DTO로 변환 해주기
@GetMapping("/members")
public Page<MemberDto> list(Pageable pageable) {
return memberRepository.findAll(pageable).map(MemberDto::new);
}
Ctrl + Alt + N(window) 를 사용하여 코드를 최적화 시킬 수 있음.
org.springframework.data.jpa.repository.support.SimpleJpaRepository 가 바로 스프링 데이터 JPA의 구현체이다
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> ...{
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
...
}
@Repository 적용 :
@Transactional 트랜잭션 적용 :
@Transactional(readOnly = true) :
중요!
save() 메서드
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
새로운 엔티티면
persist, 새로운 엔티티가 아니면merge를 호출 한다.
merge를 호출하면 DB에서 데이터를 꺼내서 파라미터에 있는 데이터로 교체를 한다.(merge를 update처럼 쓰면 안된다.)
그렇다면 뭐가 새로운 엔티티인지 아닌지 어떻게 구별하는거지?
Persistable 인터페이스를 구현해서 판단 로직 변경 가능참고: JPA 식별자 생성 전략이 @GenerateValue 면 save() 호출 시점에 식별자가 없으므로 새로운
엔티티로 인식해서 정상 동작
참고: 하지만 JPA 식별자 생성 전략이 @Id 만 사용해서 직접 할당이면 이미
식별자 값이 있는 상태로 save() 를 호출한다. 따라서 이 경우 merge() 가 호출된다. merge() 는 우선
DB를 호출해서 값을 확인하고, DB에 값이 없으면 새로운 엔티티로 인지하므로 매우 비효율 적이다(쿼리 select , insert 2번 날림)
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item implements Persistable<String> {
@Id
private String id; // 직접 Id 값을 할당해야 함
...
따라서 Persistable 를 사용해서 새로운 엔티티 확인 여부를 직접 구현할때
등록시간(@CreatedDate)을 조합해서 사용하면 새로운 엔티티 여부를 편리하게 확인할
수 있다.
@Entity
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item implements Persistable<String> {
@Id
private String id;
@CreatedDate
private LocalDateTime createdDate;
public Item(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public boolean isNew() {
return createdDate == null;
}
}