WEB/JPA

실전! Spring Data JPA 3(확장기능,Auditing ,Page)

Tony Lim 2021. 4. 13. 19:50
728x90

public interface MemberRepository extends JpaRepository<Member,Long> , MemberRepositoryCustom
public interface MemberRepositoryCustom
{
    List<Member> findMemberCustom();
}
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom
{
    private final EntityManager em;
    @Override
    public List<Member> findMemberCustom()
    {
       return em.createQuery("select m from Member m")
               .getResultList();
    }
}

 

기존 Repository 말고 상당히 복잡하고 화면 specifc 한 쿼리를 짜야한다면 따로 Repository를 분리해서 만드는것이 유지보수하기 편하다.

 

Auditing

엔티티를 생성, 변경할 때 변경한 사람과 시간을 추적하고 싶을 때 (운영을 위해서)

순수 JPA방법

@MappedSuperclass
@Getter
public class JpaBaseEntity
{
    @Column(updatable = false)
    private LocalDateTime createdDate;
    private LocalDateTime updatedDate;

    @PrePersist
    public void prePersist()
    {
        LocalDateTime now = LocalDateTime.now();
        createdDate = now;
        updatedDate = now;
    }

    @PreUpdate
    public void PreUpdate()
    {
        updatedDate = LocalDateTime.now();
    }
}
public class Member extends JpaBaseEntity

추적하고 싶은 엔티티에게 extends를 해주면 속성들만 Member table에 들어와서 쓸 수 있게 된다. 

 

Spring Data JPA 를 이용해서 Auditing 하는 방법

@EnableJpaAuditing
@SpringBootApplication
public class SpringDataJpaApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringDataJpaApplication.class, args);
	}

	@Bean
	public AuditorAware<String> auditorProvider()
	{
		return () -> Optional.of(UUID.randomUUID().toString());
	}
}

@EnableJpaAuditing 을 까먹으면 안된다.

AuditorAware에서 지금은 randomUUID를 return 하지만 실무에서 예를 들면 시큐리티를 쓴다면 해당 사람의 아이디를 return 해줘야한다. 

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity
{
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String lastModifiedBy;
}

@EntityLinsters가 추가되었고 이제 수정자와 생성자 (누가했는가?) 를 넣어줄 수 있게 되었다.

여기에 들어가는 값은 위에서 return 한 randomUUID이다.

시간과 사람을 분리해서 부모(시간) 자식(사람) 관계로 묶을 수 있다. 둘다 @MappedSuperclass를 써줘야한다. 

 

Web 확장 - 도메인 클래스 컨버터

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

알아서 Member를 given id로 조회하는데 이것은 간단한 조회할 떄만 쓰는것이다. 

난 햇갈리니 그냥 위의 방법을 쓰는게 명시적으로 안햇갈릴것같다. 아래는 원인모를 에러가 발생할 수 있다 했다.

 

Web 확장 - 페이징과 정렬

    @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));
        }
    }
spring:
 data:
    web:
      pageable:
        default-page-size: 10
        max-page-size: 2000

이것은 application.yml 에서 global 하게 페이지 사이즈를 setting 한 것이다.

    @GetMapping("/members")
    public Page<Member> list(@PageableDefault(size = 5, sort = "username") Pageable pageable)
    {
        Page<Member> page = memberRepository.findAll(pageable);
        return page;
    }

개인적으로 설정하고싶으면 @PageableDefault로 설정이 가능하다. 이것이 global 설정보다 우선권을 가지게 된다.

 

 

localhost:8083/members?page=0&size=3&sort=id,desc&sort=username,desc

 

postman에서 이런식으로 request를 날려주면

{
    "content": [
        {
            "id": 100,
            "username": "user99",
            "age": 99,
            "team": null
        },
        {
            "id": 99,
            "username": "user98",
            "age": 98,
            "team": null
        },
        {
            "id": 98,
            "username": "user97",
            "age": 97,
            "team": null
        }
    ],
    "pageable": {
        "sort": {
            "sorted": true,
            "unsorted": false,
            "empty": false
        },
        "offset": 0,
        "pageNumber": 0,
        "pageSize": 3,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalPages": 34,
    "totalElements": 100,
    "size": 3,
    "number": 0,
    "sort": {
        "sorted": true,
        "unsorted": false,
        "empty": false
    },
    "first": true,
    "numberOfElements": 3,
    "empty": false
}

이렇게 알아서 PageRequest를 주어진 request를 파싱해서 적절히 생성해주어서 Pageable pageable에 넣어준다.

    @GetMapping("/members")
    public Page<MemberDto> list(@PageableDefault(size = 5, sort = "username") Pageable pageable)
    {
        Page<Member> page = memberRepository.findAll(pageable);
        Page<MemberDto> result = page.map(member -> new MemberDto(member.getId(), member.getUsername(), null);
        return result;
    }

항상 Dto를 이용해서 return 해줘야한다.

 

접두사

public String list
(
	@Qualifer("member") Pageable memberPageable,
    @Qualifer("order") Pageable orderPageable
)
/members?member_page=0&order_page=1

이런식으로 한번에 여러게 Page를 받아와야할 떄는 @Qualifer에 접두사명을 추가해주면된다.

 

 

 

728x90