오늘 하고 싶었던 건 아주 사소했습니다. 회원의 가입 일자를 기준으로 기간 검색을 해서 특정 기간에 신규 가입한 회원이 몇 명인지 알아내고 싶었어요. 정말 사소한 소원이었습니다. 근데 이게 이렇게 오래 걸릴 줄은 몰랐어요.
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으로 만들어서 출력해주면 좀 더 완벽해질 거 같습니다. 추후에 한번 추가해보도록 하겠습니다.
'개발 > Field-Passer 프로젝트' 카테고리의 다른 글
[Field-Passer 프로젝트] AWS EC2에 스프링부트 프로젝트 배포하기 (0) | 2023.02.10 |
---|---|
[Field-Passer 프로젝트] 쿼리 최적화 (N + 1 문제 해결하기) (0) | 2023.02.04 |
[Field-Passer 프로젝트] Spring Data JPA 페이징 처리하기 (0) | 2023.02.03 |
[Field-Passer 프로젝트] SpringBoot 로그인 시 예외 처리하기 (0) | 2023.01.30 |
[Field-Passer 프로젝트] Rest API에서 JSON 반환하기 (1) | 2023.01.29 |
댓글