Programming/Spring

Spring Data JPA - Web 확장 (도메인 클래스 컨버터, 페이징과 정렬)

잇(IT) 2023. 7. 26. 14:42
- 도메인 클래스 컨버터

 

- 도메인 클래스 컨버터 사용 전

@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();
    }

- 위와 같이 HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩하거나 엔티티 객체 자체를 바인딩 해서 찾을 수 있다.

- 단 컨버터로 엔티티를 파라미터로 받으면, 이 엔티티는 단순 조회용으로만 사용해야 한다. (트랜잭션이 없는 범위에서 엔티티를 조회했으므로, 엔티티를 변경해도 DB에 반영되지 않는다.)


- 페이징과 정렬
@GetMapping("/members")
public Page<Member> list(Pageable pageable) {
 	Page<Member> page = memberRepository.findAll(pageable);
 	return page;
}

- 파라미터로 Pageable 인터페이스를 받고 구현체로 PageRequest를 사용한다.

 

	Page<T> findAll(Pageable pageable);
}

- findAll 파라미터로 pageable을 받는 메서드가 작성되어 있다.

 

@PostConstruct
    public void init() {
        for (int i = 0; i < 100; i++) {
            memberRepository.save(new Member("user" + i, i));
        }
    }

- 위의 코드를 통해 임의의 Member 엔티티 100개를 생성했다고 가정한다.

 

{
    "content": [
        {
            "createdDate": "2023-07-26T14:36:04.609312",
            "lastModifiedDate": "2023-07-26T14:36:04.609312",
            "createdBy": "d44c3a91-4cb3-453d-b788-1b536a85e2bb",
            "lastModifiedBy": "d44c3a91-4cb3-453d-b788-1b536a85e2bb",
            "id": 1,
            "username": "user0",
            "age": 0,
            "team": null
        },
        {
            "createdDate": "2023-07-26T14:36:04.638226",
            "lastModifiedDate": "2023-07-26T14:36:04.638226",
            "createdBy": "f617d7e2-62ef-45c6-8c5a-517536af8cc8",
            "lastModifiedBy": "f617d7e2-62ef-45c6-8c5a-517536af8cc8",
            "id": 2,
            "username": "user1",
            "age": 1,
            "team": null
        },
        {
            "createdDate": "2023-07-26T14:36:04.64061",
            "lastModifiedDate": "2023-07-26T14:36:04.64061",
            "createdBy": "bbc56e86-96c1-4359-93de-d3883b2f7ee6",
            "lastModifiedBy": "bbc56e86-96c1-4359-93de-d3883b2f7ee6",
            "id": 3,
            "username": "user2",
            "age": 2,
            "team": null
        },
        ...
        ],
    "pageable": {
        "sort": {
            "empty": true,
            "sorted": false,
            "unsorted": true
        },
        "offset": 0,
        "pageSize": 5,
        "pageNumber": 0,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalPages": 20,
    "totalElements": 100,
    "size": 5,
    "number": 0,
    "sort": {
        "empty": true,
        "sorted": false,
        "unsorted": true
    },
    "first": true,
    "numberOfElements": 5,
    "empty": false
}

- pageable 객체를 통해 스프링 데이터 JPA가 페이징과 관련된 데이터를 전부 알아서 가져온다.

 

{
    "content": [
        {
            "createdDate": "2023-07-26T14:36:04.64912",
            "lastModifiedDate": "2023-07-26T14:36:04.64912",
            "createdBy": "0ad70158-c53d-4efe-a81d-aad234caae6e",
            "lastModifiedBy": "0ad70158-c53d-4efe-a81d-aad234caae6e",
            "id": 6,
            "username": "user5",
            "age": 5,
            "team": null
        },
        {
            "createdDate": "2023-07-26T14:36:04.651125",
            "lastModifiedDate": "2023-07-26T14:36:04.651125",
            "createdBy": "1972d971-2522-4d3a-86d2-428a9c4ac9d4",
            "lastModifiedBy": "1972d971-2522-4d3a-86d2-428a9c4ac9d4",
            "id": 7,
            "username": "user6",
            "age": 6,
            "team": null
        },
        ...
        "pageable": {
        "sort": {
            "empty": true,
            "sorted": false,
            "unsorted": true
        },
        "offset": 5,
        "pageSize": 5,
        "pageNumber": 1,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalPages": 20,
    "totalElements": 100,
    "size": 5,
    "number": 1,
    "sort": {
        "empty": true,
        "sorted": false,
        "unsorted": true
    },
    "first": false,
    "numberOfElements": 5,
    "empty": false
}

- /members?page=1 과 같은 요청을 받았을 때 Spring MVC는 'page' 등... 요청 파라미터를 Pageable 객체로 변환하여 list 메서드의 pageable 파라미터에 전달한다. 이렇게 함으로써 list 메서드 내에서 페이징과 정렬을 쉽게 처리할 수 있게 된다.

- 스프링 데이터 JPA의 기능을 스프링 부트가 알아서 Pagealbe 인터페스의 구현체로 pageRequest를 알아서 넣고, 데이터를 주입한다.


- application.yml (글로벌 설정)

  data:
    web:
      pageable:
        default-page-size: 10
        max-page-size: 2000

- 설정 파일을 통해 기본값을 변경할 수 있다.

 

- 개별 설정

@GetMapping("/members")
    public Page<MemberDto> list(@PageableDefault(size=5) Pageable pageable) {

- @PageableDefault(...)를 통해 개별 설정을 할 수도 있다.


- Page 내용을 DTO로 변환하기

1. 엔티티를 API로 노출하면 다양한 문제가 발생한다.

2. 그래서 엔티티를 꼭 DTO로 변환해서 반환해야 한다. Page는 map() 을 지원해서 내부 데이터를 다른 것으로 변경할 수 있다.

 

- MemberDto

@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;
    }
}

 

- Controller에 DTO 변환 코드 추가

@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;
    }

{
    "content": [
        {
            "id": 6,
            "username": "user5",
            "teamName": null
        },
        {
            "id": 7,
            "username": "user6",
            "teamName": null
        },
        {
            "id": 8,
            "username": "user7",
            "teamName": null
        },
        {
            "id": 9,
            "username": "user8",
            "teamName": null
        },
        {
            "id": 10,
            "username": "user9",
            "teamName": null
        }
        ...
         "pageable": {
        "sort": {
            "empty": true,
            "sorted": false,
            "unsorted": true
        },
        "offset": 5,
        "pageNumber": 1,
        "pageSize": 5,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalElements": 100,
    "totalPages": 20,
    "size": 5,
    "number": 1,
    "sort": {
        "empty": true,
        "sorted": false,
        "unsorted": true
    },
    "first": false,
    "numberOfElements": 5,
    "empty": false
}

- DTO를 사용하여 원하는 데이터만 가져오는 것이 가능해진다.


* 참고

 

- MemberDto

@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();
    }
}

 

- Controller

@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;
        
//        return memberRepository.findAll(pageable)
//                .map(member -> new MemberDto(member));

        return memberRepository.findAll(pageable)
                .map(MemberDto::new);
    }

- DTO로 변환하는 코드를 위와 같이 줄일 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

출처 : 인프런 - 김영한(실전! 스프링 데이터 JPA)

728x90