본문 바로가기

개발기술/ORM

JPA 기타기능 (Pageable, auditing, data.sql 기능)

 

1. Auditing functionality  : Persistency Layer에서 특정 이벤트 발생시에 자동적으로 데이터값을 생성해주는 기능을 말한다. 레코드 생성자, 생성일 등 데이터의 히스토리를 추적하는데 사용된다. Auditing 기능을 적용할 Entity 클래스에

@EntityListeners (AuditingEntityListener.class)를 추가하고, 필요한 필드에 @CreatedDate나 @LastModifiedDate와 같은 애너테이션을 붙입니다. 이 entitylistener들은 entity의 lifecycle을 파악하고 lifecycle의 특정부분에 지정된 코드를 실행하도록 한다.

@EntityListeners(AuditingEntityListener.class)
@Configuration
@EnableJpaAuditing
public class JpaAuditingConfiguration {
}

 

@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;

2. 데이터 직접입력하기 :

(1) Spring directory  - resource - 아래에 data.sql이라는 문서를 만들고 이곳에서 Insert query를 작성. 반드시 'data.sql이어야만 인식을 함.

(2) spring property 설정에 defer datasource initialization이라는 값을 설정하여, hibernate의 ddl-auto가 table을 생성을 완료한후에야 Insert문을 실행하도록 설정한다.

jpa:
  defer-datasource-initialization: true

 

페이징처리 : Page & Pagable 

 

Page Pageable은 대규모 데이터베이스 조회 시 페이징 처리를 돕는 중요한 인터페이스입니다. 이를 이용해 원하는 페이지의 데이터와 메타정보를 효율적으로 조회할 수 있습니다.

 

Page : Spring Data에서 제공하는 인터페이스로, 전체 데이터의 일부분을 나타내는 객체입니다. 현재 페이지의 데이터뿐 아니라, 전체 요소 수, 총 페이지 수, 현재 페이지 번호 등의 Pageable을 통해 설정된 정보로 구성됩니다.

  • Page의 구현체: PageImpl. PageImpl: Page 인터페이스의 기본 구현체로, 실제로 조회된 데이터를 포함합니다. PageImpl은 Spring Data가 페이징된 쿼리를 실행한 후 데이터와 메타정보를 담아서 반환할 때 사용됩니다.
public PageImpl(List<T> content, Pageable pageable, long total);
  • Page iterable : 페이지는 리스트와 동일하고 for loop의 요인으로 사용가능하다.
// Assume you have a Page<CommunityDto> from your repository
Page<CommunityDto> communityDtos = communityService.getPosts(pageable);

// Iterating directly with for-each (since Page implements Iterable)
for (CommunityDto communityDto : communityDtos) {
    // Process each communityDto
}

 

  • Page.map() : Page의 객체를 변환시킬때 Page인터페이스의 map을 사용해서 담겨있는 객체를 변환할 수 있음.
 
companyEntities.map(company -> new CompanyDto(
        company.getId(),
    company.getName(),
        company.getAddress(),
        company.getIndustry()
        ));

 

 

 

Pageable : Spring Data에서 제공하는 인터페이스로, 페이지 번호, 페이지 크기, 정렬 기준 등 페이징 정보를 캡슐화합니다. 페이지와 관련된 정보를 API 요청의 파라미터로 받아 처리하며, ?page=2&size=10&sort=name,asc와 같이 요청 시 파라미터를 설정하여 특정 페이지를 조회할 수 있습니다.

  • Pageable의 구현체: PageRequest : PageRequest를 사용하여 페이지 번호, 페이지 크기, 정렬 조건 등을 설정할 수 있습니다. PageRequest.of(page, size, sort)와 같은 형태로 쉽게 객체를 생성할 수 있습니다.
Pageable sortedByDate = PageRequest.of(
    pageable.getPageNumber(), // 요청된 페이지 번호 유지
    pageable.getPageSize(),   // 요청된 페이지 크기 유지
    Sort.by("createdDate").descending() // 최신순 정렬
);
{
      "content": [
      {
          "id":1,
          "name":"Community A",
          "address":"123 Street",
          "industry":"Tech"
      },
      {
          "id":2,
          "name":"Community B",
          "address":"456 Avenue",
          "industry":"Finance"
      },
      {
          "id":3,
          "name":"Community C",
          "address":"789 Boulevard",
          "industry":"Education"
      }
      // ... up to 10 items on this page
],
      "pageable":{
      "sort":{
          "sorted":true,
              "unsorted":false,
              "empty":false
      },
      "offset":10,
          "pageNumber":1,
          "pageSize":10,
          "paged":true,
          "unpaged":false
  },
      "totalPages":5,
      "totalElements":50,
      "last":false,
      "size":10,
      "number":1,
      "sort":{
      "sorted":true,
          "unsorted":false,
          "empty":false
  },
      "first":false,
      "numberOfElements":10,
      "empty":false
  }

 

 

  • content: 현재 페이지의 실제 데이터 목록 (예: CommunityDto 객체들). pageSize 개수만큼의 항목을 포함합니다.
  • pageable: 현재 요청의 페이징 설정 정보를 포함합니다.
    • sort: 정렬 설정 (특정 필드를 기준으로 정렬된 정보).
    • offset: 데이터셋에서 시작 위치를 나타냅니다 (예: pageNumber * pageSize).
    • pageNumber: 현재 페이지 번호 (0부터 시작).
    • pageSize: 페이지당 항목 수.
    • paged / unpaged: 페이징이 활성화되어 있는지 여부.
  • totalPages: 전체 데이터셋에서 페이지의 총 개수 (예: 전체 50개의 항목을 10개씩 나눌 경우 5페이지).
  • totalElements: 페이지 크기에 상관없이 전체 데이터셋의 총 항목 수 (예: 50).
  • last: 현재 페이지가 마지막 페이지인지 여부를 나타냅니다.
  • size: pageSize와 동일하게 페이지당 항목 수를 나타냅니다.
  • number: pageNumber와 동일하게 현재 페이지 인덱스를 나타냅니다 (0부터 시작).
  • sort: 이 페이지에 적용된 정렬 기준.
  • first: 현재 페이지가 첫 번째 페이지인지 여부를 나타냅니다.
  • numberOfElements: 현재 페이지에 포함된 항목 수 (마지막 페이지에서는 pageSize보다 적을 수 있음).
  • empty: content 목록이 비어 있는지 여부를 나타냅니다.

 

 

페이징 구현순서

 

1단계 Controller에  Pageable을 request로 받는 controller을 생성한다.

  • Pageable 객체를 컨트롤러 메서드의 파라미터로 두기만 해도 @RequestParam이나 @PathVariable이 없어도 자동으로 클라이언트의 쿼리 파라미터를 바인딩해준다. 그 이유는 Spring Data JPA에서 제공하는 HandlerMethodArgumentResolver 덕분입니다.
@RestController
@RequestMapping("/api/companies")
public class CompanyController {
    private final CompanyService companyService;

    public CompanyController(CompanyService companyService) {
        this.companyService = companyService;
    }

    @GetMapping
    public ResponseEntity<Page<CompanyDto>> getCompanies(Pageable pageable) {
        Page<CompanyDto> companies = companyService.getCompanies(pageable);
        return ResponseEntity.ok(companies);
    }
}

 

 

 

 

1단계 : Repository Layer에서 pageable을 입력받고 Page<Entity>를 반환하는 repository method를 정의함

Page<CommunityEntity> findAll( Pageable pageable);

 

2단계 : pagable을 Controller을 통해서 입력받고 Repository에서 받은 Page객체를 PagedDto로 변환하여 Controller에 반환한다. 이는 Page에 담긴 Dto 뿐만아니라 Page에 대한 메타정보를 몇가지 선택하여 같이 전송하기 위함이다. 그리하여 페이지 번호, 페이지 크기, 정렬 조건 등의 메타정보 등을 전송하기 위함이다.

  • Page객체를 컨트롤러의 Response로 바로 보내지 않고 DTO로 보내는 이유
    • 1. Tight Coupling with Spring Data: By exposing Page, you are tightly coupling the API to Spring Data’s specific implementation. If you ever switch from Spring Data or change your pagination mechanism, you’ll have to update the frontend as well, which is less maintainable.
    • 2. Unnecessary Metadata: The Page object includes backend-specific details (e.g., Pageable info) that the frontend may not need or use. 
//다건 조회
public PagedResponseDTO<ResponseCommunityDto> getPosts(Pageable pageable) {


    Page<CommunityDto> communityDtos = communityService.getPosts(pageable);

    Page<ResponseCommunityDto> responseCommunityDtos = communityDtos.map(e
        -> ResponseCommunityDto.fromEntity(e,
        communityFileService.getFiles(e.getCommunityId())));

    return ConvertPageToPagedResponse(
        responseCommunityDtos);
}

private static PagedResponseDTO<ResponseCommunityDto> ConvertPageToPagedResponse(
    Page<ResponseCommunityDto> responseCommunityDtos) {

    // PagedResponseDTO로 변환
    PagedResponseDTO<ResponseCommunityDto> pagedResponse
        = PagedResponseDTO.<ResponseCommunityDto>builder()
        .content(responseCommunityDtos.getContent())
        .pageNumber(responseCommunityDtos.getNumber())
        .pageSize(responseCommunityDtos.getSize())
        .totalElements(responseCommunityDtos.getTotalElements())
        .totalPages(responseCommunityDtos.getTotalPages())
        .last(responseCommunityDtos.isLast())
        .build();
    return pagedResponse;
}