ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 4. 확장 기능
    Spring-Boot/실전! 스프링 데이터 JPA 2023. 2. 23. 17:35

    1. 사용자 정의 리포지토리 구현

    - 스프링 데이터 JPA 리포지토리는 인터페이스만 정의하고 구현체는 스프링이 자동 생성

    - 스프링 데이터 JPA가 제공하는 인터페이스를 직접 구현하면 구현해야 하는 기능이 너무 많음

    - 다양한 이유로 인터페이스의 메서드를 직접 구현하고 싶다면?

        ㆍJPA 직접 사용 (`EntityManager`)

        ㆍ스프링 JDBC Template 사용

        ㆍMyBatis 사용

        ㆍ데이터베이스 커넥션 직접 사용 등등..

        ㆍQueryDsl 사용

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

    * 사용자 정의 구현 클래스 *

    - 규칙 : 리포지토리 인터페이스 이름 + `Impl`

    - 스프링 데이터 JPA가 인식해서 스프링 빈으로 등록

     

    * Impl 대신 다른 이름으로 변경하고 싶으면? *

    *XML 설정*

    ```xml
    <repositories base-package="study.datajpa.repository"
    				repository-impl-postfix="Impl" />
    ```

    *JavaConfig 설정*

    ```java
    @EnableJpaRepositories(
    	basePackages = "study.datajpa.repository",
        	repositoryImplementationPostfix = "Impl"
    )
    ```

    * 참고 : 실무에서는 주로 QueryDSL 이나 SpringJdbcTemplate을 함께 사용할 때 사용자 정의 리포지토리 기능 자주 사용

    * 참고 : 항상 사용자 정의 리포지토리가 필요한 것은 아니다.

    - 그냥 임의의 리포지토리를 만들어도 된다.

    - 예를 들어 MemberQueryRepository를 인터페이스가 아닌 클래스로 만들고 스프링 빈으로 등록해서 그냥 직접 사용해도 된다.

    - 물론 이 경우 스프링 데이터 JPA와는 아무런 관계없이 별도로 동작한다.

     

    * 애플리케이션이 커질수록

    - command와 query를 분리

    - 핵심비즈니스로직과 화면단 데이터를 분리

     

    2. Auditing

    - 엔티티를 생성, 변경할 때 변경한 사람과 시간을 추적하고 싶으면?

        ㆍ등록일

        ㆍ수정일

        ㆍ등록자

        ㆍ수정자

     

    * JPA 주요 이벤트 어노테이션 *

    - @PrePersist, @PostPersist

    - @PreUpdate, @PostUpdate

     

    스프링 데이터 JPA 사용

    * 설정 *

    - `@EnableJpaAuditing` → 스프링 부트 설정 클래스에 적용해야함

    - `@EntityListeners(AuditingEntityListener.class)` → 엔티티에 적용

     

    * 사용 어노테이션 *

    - `@CreatedDate`

    - `@LastModifiedDate`

    - `@CreatedBy`

    - `@LastModifiedBy`

     

    전체적용

    - `@EntityListeners(AuditingEntityListener.class)`를 생략하고 스프링 데이터 JPA가 제공하는 이벤트를 엔티티 전체에 적용하려면 orm.xml에 다음과 같이 등록하면 된다.

    <?xml version=“1.0” encoding="UTF-8”?>
    <entity-mappings xmlns=“http://xmlns.jcp.org/xml/ns/persistence/orm”
            xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
            xsi:schemaLocation=“http://xmlns.jcp.org/xml/ns/persistence/
            orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_2.xsd”
            version=“2.2">
    <persistence-unit-metadata>
    <persistence-unit-defaults>
        <entity-listeners>
            <entity-listener
                    class="org.springframework.data.jpa.domain.support.AuditingEntityListener”/>
     </entity-listeners>
     </persistence-unit-defaults>
     </persistence-unit-metadata>
    
    </entity-mappings>

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

    - HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩

    @GetMapping("/members/{id}")
    public String findMember(@PathVariable("id") Long id) {
        Member member = memberRepository.findById(id).get();
        return member.getUsername();
    }
    
    @GetMapping("/members2/{id}")
    public String findMember2(@PathVariable("id") Member member) {
        return member.getUsername();
    }

    - HTTP 요청은 회원 `id` 를 받지만 도메인 클래스 컨버터가 중간에 동작해서 회원 엔티티 객체를 반환

    - 도메인 클래스 컨버터도 리파지토리를 사용해서 엔티티를 찾음

     

    * 주의 *

    - 도메인 클래스 컨버터로 엔티티를 파라미터로 받으면, 이 엔티티는 단순 조회용으로만 사용해야 한다.

    ( 트랜잭션이 없는 범위에서 엔티티를 조회했으므로, 엔티티를 변경해도 DB에 반영되지 않는다. )

     

    4. Web 확장 - 페이징과 정렬

    - 스프링 데이터가 제공하는 페이징과 정렬 기능을 스프링 MVC 에서 편리하게 사용할 수 있다.

     

    * 페이징과 정렬 예제 *

    ```java
    @GetMapping("/members")
    public Page<Member> list(Pageable pageable) {
        Page<Member> page = memberRepository.findAll(pageable);
        return page;
    }
    ```

    - 파라미터로 `Pageable`을 받을 수 있다.

    - `Pageable`은 인터페이스, 실제는 `org.springframework.data.domain.PageRequest` 객체 생성

     

    * 요청 파라미터 *

    - 예) `/members?page=0&size=3&sort=id,desc&sort=username,desc`

    - page : 현재 페이지, *0부터 시작한다.*

    - size : 한 페이지에 노출한 데이터 건수

    - sort : 정렬 조건을 정의한다.

        ㆍ예) 정렬 속성, 정렬 속성...(ASC | DESC), 정렬 방향을 변경하고 싶으면 'sort' 파라미터 추가 ('asc' 생략 가능)

     

    // 1. properties
    spring:
     data:
       web:
         pageable:
           default-page-size: 10
           max-page-size: 2000
    // 2.
    @GetMapping("/members")
    public Page<Member> list(@PageableDefault(size = 5, sort="username") Pageable pageable) {
        Page<Member> page = memberRepository.findAll(pageable);
        return page;
    }

     

     

    * 접두사 *

    - 페이징 정보가 둘 이상이면 접두사로 구분

    - `@Qualifier`에 접두사명 추가 "{접두사명}_xxx"

    - 예제 : `/members?member_page=0&order_page=1`

    ```java
    public String list {
        @Qualifier("member") Pageable memberPageable,
        @Qualifier("order") Pageable orderPageable
    }

     

    Page 내용을 DTO로 변환하기

    - 엔티티를 API로 노출하면 다양한 문제가 발생한다. 그래서 엔티티를 꼭 DTO로 변환해서 반환해야 한다.

    - Page는 `map()`을 지원해서 내부 데이터를 다른 것으로 변경할 수 있다.

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

     

    Page를 1부터 시작하기

    - 스프링 데이터는 Page를 0부터 시작한다.

    - 만약 1부터 시작하려면?

        ㆍ1. Pageable, Page를 파라미터와 응답 값으로 사용하지 않고, 직접 클래스를 만들어서 처리한다.

               그리고 직접 PageRequest(Pageable 구현체)를 생성해서 리포지토리에 넘긴다.

               물론 응답값도 Page 대신에 직접 만들어서 제공해야 한다.

        ㆍ2. `spring.data.web.pageable.one-indexed-parameters`를 `true`로 설정한다.

                그런데 이 방법은 web에서 `page`파라미터를 `-1` 처리 할 뿐이다. 

                따라서 응답값인 `Page`에 모두 0페이지 인덱스를 사용하는 한계가 있다.

    댓글

Designed by Tistory.