본문 바로가기
개발/Field-Passer 프로젝트

[Field-Passer 프로젝트] Spring Data JPA에서 NativeQuery로 기간 검색하고 DTO로 값 받기

by 코코의 주인 2023. 2. 5.

 오늘 하고 싶었던 건 아주 사소했습니다. 회원의 가입 일자를 기준으로 기간 검색을 해서 특정 기간에 신규 가입한 회원이 몇 명인지 알아내고 싶었어요. 정말 사소한 소원이었습니다. 근데 이게 이렇게 오래 걸릴 줄은 몰랐어요.

 

1. Member Entity

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@Entity
@Table(name = "MEMBER")
public class Member {

    .
    .
    .
    
    @Column(name = "SIGNUP_DATE")
    private LocalDateTime signUpDate;
    
    .
    .
    .
}

 

2. Controller

@GetMapping("/admin/membes/new")
public PeriodMemberVO checknewMember(PeriodRequestDTO period) {
    return adminService.checkNewMember(period);
}

 

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Builder
public class PeriodRequestDTO {

    private String startDate;
    private String endDate;
}

컨트롤러에서 PeriodRequestDTO를 통해 시작 일자(startDate)와 종료 일자(endDate)를 받아옵니다.

DTO를 통해서 값을 받아오려면 setter()를 통해서 받아오더라고요. 그래서 이 경우에만 특별히 setter를 추가했습니다.

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
public class PeriodMemberVO {
    private String resultCode;
    private int resultDataNum;
    private List<PeriodResponceDTO> resultData;
}

반환은 VO에 담아서 했습니다.

 

3.AdminService

@Service
@RequiredArgsConstructor
public class AdminServiceImpl implements AdminService {

    private final MemberService memberService;
    
    .
    .
    .
    
    /**
     * 신규 가입자 기간 검색
     * @param period
     * @return
     */
    @Override
    public PeriodMemberVO checkNewMember(PeriodRequestDTO period) {
        try {
            List<PeriodResponceDTO> newMember = memberService.checkNewMember(period.getStartDate(), period.getEndDate());
            return PeriodMemberVO.builder()
                    .resultCode("success")
                    .resultDataNum(newMember.size())
                    .resultData(newMember)
                    .build();
        } catch (NullPointerException e) {
            return PeriodMemberVO.builder()
                    .resultCode("failed : 조회할 수 있는 데이터가 없습니다.")
                    .build();
        } catch (IllegalStateException e) {
            return PeriodMemberVO.builder()
                    .resultCode("failed : 날짜를 잘못 입력했습니다.")
                    .build();
        } catch (Exception e) {
            return PeriodMemberVO.builder()
                    .resultCode("failed : 뭔가 잘못됐습니다..")
                    .build();
        }
    }
}

이번 케이스의 경우는 3가지 예외를 생각해봤습니다.

1) 조회할 수 있는 데이터가 없는 경우

2) 날짜를 이상하게 입력한 경우(ex. startDate가 endDate보다 큼)

3) 뭔가 이상할때

 

4.MemberService

@Service
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {

    private final MemberRepositoryJPA memberRepository;
    
    .
    .
    .
    
    /**
     * 신규 회원 검색
     * @param startDate
     * @param endDate
     * @return
     * @throws Exception
     */
    @Override
    public List<PeriodResponceDTO> checkNewMember(String startDate, String endDate) throws Exception{
        try {
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            Date start = format.parse(startDate);
            Date end = format.parse(endDate);
            if (end.getTime() - start.getTime() < 0) {
                throw new IllegalStateException("날짜를 잘못 입력했습니다.");
            }
            List<PeriodResponceDTO> newMember = memberRepository.findNewMember(startDate, endDate);
            if (newMember.size() == 0) {
                throw new NullPointerException("조회할 수 있는 데이터가 없습니다");
            } else {
                return newMember;
            }
        } catch (IllegalStateException e) {
            throw new IllegalStateException("날짜를 잘못 입력했습니다.");
        }
    }
}

날짜를 잘못 입력했는지 확인하는 방법은 이렇게 구현했습니다.

1) date.getTime()메서드는 1970년 1월 1일 00시 00분 00초 UTC를 기준으로 경과 한 밀리초를 반환함.

2) end.getTime() - start.getTime() 결과가 0보다 작으면 startDate의 값이 endDate의 값보다 큰 것임

3) 에러 반환

 

5. MemberRepository

public interface MemberRepositoryJPA extends JpaRepository<Member, Integer> {

    .
    .
    .

    //신규 회원 기간 조회
    @Query(value = "select Date_Format(m.signUp_Date, '%Y-%m-%d') as date, count(m.id) as memberNum from field_passer.Member as m where Date_Format(m.signUp_Date, '%Y-%m-%d') between :startDate AND :endDate AND m.DELETE_CHECK = 0 GROUP BY Date_Format(m.signUp_Date, '%Y-%m-%d')", nativeQuery = true)
    List<PeriodResponceDTO> findNewMember(@Param("startDate") String startDate, @Param("endDate") String endDate);
}

일단 DAO는 이러합니다. JPA랑 JPQL로는 할 방법이 없어서 바로 Native Query로 혼내줬습니다. 나름 제가 또 전공 짬바가 있지 않겠습니까?

 

SELECT Date_format(m.signup_date, '%Y-%m-%d') AS date,
       Count(m.id)                            AS memberNum
FROM   field_passer.member AS m
WHERE  Date_format(m.signup_date, '%Y-%m-%d') BETWEEN :startDate AND :endDate
       AND m.delete_check = 0
GROUP  BY Date_format(m.signup_date, '%Y-%m-%d')

SQL문은 위와 같습니다. 여기까지 오신 분들 중에 SQL문을 못 읽으실 분은 없을 거 같으니 설명은 생략할게요.

public interface PeriodResponceDTO {
    String getdate();
    Long getmemberNum();
}

제가 아까 그래서 검색한 결과를 DTO로 받고 싶다고 했잖아요? Native Query를 사용한 경우 DTO를 인터페이스로 만들어야 합니다. 그리고 생성한 DTO에 getter를 만들어주셔야 합니다. 이름은 get+{쿼리문에 선언한 별칭}으로 해주세요. 


6. 결과

1) 정상 출력

2) 예외

 

지금은 1월 1일부터 30일까지 중간에 결과가 없는 값은 아예 출력하지 않는데 이 값들도 0으로 만들어서 출력해주면 좀 더 완벽해질 거 같습니다. 추후에 한번 추가해보도록 하겠습니다.

댓글