FireDrago

[Spring Data JPA] 확장기능 (사용자 리포지토리, Auditing, Web 확장) 본문

카테고리 없음

[Spring Data JPA] 확장기능 (사용자 리포지토리, Auditing, Web 확장)

화이용 2024. 4. 29. 14:14

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

스프링 데이터 JPA는 인터페이스만 구현하면 자동으로 구현체를 생성한다.

하지만 다양한 이유등으로 인터페이스의 메서드를 직접 구현하고 싶을 때가 있다.

(JPA직접 사용, 스프링 JDBC Template 사용, Mybatis 사용, Querydsl 사용 등등)

 

// 사용자 정의 인터페이스 생성
public interface MemberRepositoryCustom {

    List<Member> findMemberCustom();
}

먼저 구현하고 싶은 메서드를 가진 인터페이스를 정의한다. 

 

// 구현클래스를 직접 구현한다.  (인터페이스명 + Impl)
@RequiredArgsConstructor
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom{

    private final EntityManager em;

    @Override
    public List<Member> findMemberCustom() {
        return em.createQuery("select m from Member m")
                .getResultList();
    }
}

구현 클래스의 이름은 리포지토리 인터페이스 이름 + Impl 을 적용하면 된다.

 

public interface MemberRepository extends JpaRepository<Member, Long> , MemberRepositoryCustom{

... 기존 스프링 데이터 JPA 구현 ....

}

정의한 리포지토리를 사용할때는 기존 스프링 데이터 JPA 인터페이스에 상속으로 사용자정의 리포지토리 인터페이스를

설정해주면 된다.

 

 

Auditing

엔티티를 생성, 변경할때 생성,변경 시간과 사용자를 추적하고 싶을때 사용하는 방법이다.

 

설정

어노테이션 설명
@EnableJpaAuditing 스프링 부트 설정 클래스에 적용해야함 (Application 클래스)
@EntityListener(AuditingEntityListener.class) 엔티티에 적용 (Entity 클래스)

 

사용 어노테이션

어노테이션 설명
@CreatedDate 생성시간 (LocalDateTime 타입에 사용)
@CreatedBy 등록자
@LastModifiedDate 수정시간 LocalDateTime 타입에 사용
@LastModifiedBy 수정자

 

// Auditing 사용하기 위해 반드시 @EnableJpaAuditing 어노테이션을 사용해야한다.
@EnableJpaAuditing
@SpringBootApplication
public class DataJpaApplication {

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


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

 

Entity에 적용하기위해 별도의 클래스로 BaseEntity를 생성했다.

생성시간, 수정시간은 일부 엔티티에 사용할 수 있기때문에, 상속받도록 만들었다.

// Auditing 설정 어노테이션
@EntityListeners(AuditingEntityListener.class)
// 엔티티 상속으로 매핑정보만 제공하기 위해 사용하는 어노테이션
@MappedSuperclass
@Getter
public class BaseEntity extends BaseTimeEntity{

    @CreatedBy
    // 수정시에는 업데이트 되지 않도록 설정
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String lastModifiedBy;
}

 

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseTimeEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
}

테이블에는 생성자, 생성일, 수정자, 수정일이 모두 생성된 것을 확인할 수 있다.

em.persist() 를 통해 엔티티가 생성, 변경감지를 통해 수정되는 경우 자동적으로 테이블의 속성에 해당 값이 

insert 되는 것을 확인할 수 있다.

insert into 
    member (age,created_by,created_date,
            last_modified_by,
            last_modified_date,
            team_id,username,
            member_id) 
    values (0,'5c1a0324-c25d-4152-b2c0-c421b7f3f111',
            '2024-04-29T13:44:26.870+0900','5c1a0324-c25d-4152-b2c0-c421b7f3f111',
            '2024-04-29T13:44:26.870+0900',NULL,'member1',1);
=====================================================================================
update 
   member 
   set age=0,
       last_modified_by='aa546a74-a5ed-439c-bee8-78c710f309d7',
       last_modified_date='2024-04-29T13:44:27.000+0900',
       team_id=NULL,username='member2' 
   where member_id=1;
=====================================================================================

 

 

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

@RestController
@RequiredArgsConstructor
public class MemberController {

    private final MemberRepository memberRepository;
    
    @GetMapping("/members/{id}")
    // Member 엔티티를 바로 파라미터로 받을 수 있게된다.
    public String findMember(@PathVariable("id") Member member) {
        return member.getUsername();
    }
}

Member 엔티티를 바로 파라미터로 받았다. 

도메인 클래스 컨버터로 엔티티를 파라미터로 받으면,

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

엔티티는 단순 조회용으로만 사용해야 한다.

 

 

Web 확장 - 페이징과 정렬

앞서 배운 Pageable을 파라미터로 받을 수 있고, Page를 반환할수 있다.

이를 통해 Page객체에 페이징 정보를담아 전달 할 수 있다.

@GetMapping("/members")
public Page<MemberDto> list(
//  개별 페이지 설정
    @PageableDefault(size = 12, 
                     sort = "username",
                     direction = Sort.Direction.DESC) Pageable pageable) {
//  MemberDto로 변환하여 반환한다. 엔티티를 절대 노출하면 안된다.
    return memberRepository.findAll(pageable).map(MemberDto::new);
}

 

요청 파라미터

파라미터 설명
 /members?page=0&size=3&sort=id,desc&sort=username,desc
page 현재 페이지, 0부터 시작한다. (중요) 
size 한 페이지에 노출할 데이터 건수
sort 정렬 조건을 정의한다. 

 

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