[ElectionPJT] 4. Candidate Service

Jake·2022년 3월 16일
0

프로젝트

목록 보기
4/9
post-thumbnail

1. 지원 기능

  • 후보자 관련 기능
    • 후보자 등록
    • 후보자 정보 수정
    • 후보자 정보 삭제

2. Candidate Entity 수정

  • Sns, Youtube 엔티티와 양방향 매핑으로 수정
    • 추후에 total likes, total comments 등 정보를 제공하는 기능도 만들어 보고 싶어서
    • 후보 삭제 시 관련된 항목들까지 함께 삭제해야 함으로, cascade all을 적용
  • 생성자 추가
    • 기본 생성자로 생성하는 것을 방지하기 위해 AccessLevel.PROTECTED로 설정
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Candidate {

    @Id @GeneratedValue
    @Column(name = "candidate_id")
    private Long id;

    private int number;

    @Column(name = "candidate_name")
    private String name;

    @Column(name = "candidate_likes")
    private int likes;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "city_id")
    private City city;

    @OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Sns> snsList = new ArrayList<>();

    @OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL)
    private List<Youtube> youtubeList = new ArrayList<>();

    @Builder
    public Candidate(int number, String name, City city) {
        this.number = number;
        this.name = name;
        this.likes = 0;
        this.city = city;
    }

    //== 연관관계 편의 메서드 ==//
    public void addSns(Sns sns) {
        snsList.add(sns);
        sns.setCandidate(this);
    }

    public void addYoutube(Youtube youtube) {
        youtubeList.add(youtube);
        youtube.setCandidate(this);
    }
}


3. Candidate Repository

@Repository
@RequiredArgsConstructor
public class CandidateRepository {

    private final EntityManager em;

    public void save(Candidate candidate) {
        em.persist(candidate);
    }

    public void remove(Candidate candidate) {
        em.remove(candidate);
    }

    public Candidate findById(Long id) {
        return em.find(Candidate.class, id);
    }

    public List<Candidate> findAllByCity(City city) {
        return em.createQuery("select c from Candidate c where c.city = :city")
                .setParameter("city", city)
                .getResultList();
    }

    public List<Candidate> findByCityAndNumber(City city, int number) {
        return em.createQuery("select c from Candidate c where c.city = :city and c.number = :number")
                .setParameter("city", city)
                .setParameter("number", number)
                .getResultList();
    }
}

4. Candidate Service

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class CandidateService {

    private final CityRepository cityRepository;
    private final CandidateRepository candidateRepository;

    @Transactional
    public Long join(CandidateRequestDto candidateRequestDto) {
        Long cityId = candidateRequestDto.getCityId();
        City city = cityRepository.findOne(cityId);

        Candidate candidate = candidateRequestDto.toEntity(city);

        validateDuplicateCandidate(candidate);
        candidateRepository.save(candidate);
        return candidate.getId();
    }

    @Transactional
    public void delete(Long candidateId) {
        Candidate candidate = candidateRepository.findById(candidateId);
        validateCandidateExistence(candidate);
        candidateRepository.remove(candidate);
    }

    public void validateDuplicateCandidate(Candidate candidate) {
        List<Candidate> candidateList = candidateRepository.findByCityAndNumber(candidate.getCity(), candidate.getNumber());
        if(!candidateList.isEmpty()) {
            throw new IllegalStateException("이미 존재하는 후보 번호입니다");
        }
    }

    public void validateCandidateExistence(Candidate candidate) {
        List<Candidate> candidateList = candidateRepository.findByCityAndNumber(candidate.getCity(), candidate.getNumber());
        if(candidateList.isEmpty()) {
            throw new IllegalStateException("후보가 존재하지 않습니다.");
        }
    }
}

5. Candidate Dto

RequestDto

@Getter @Setter
public class CandidateRequestDto {

    private int number;
    private String name;
    private Long cityId;

    @Builder
    public CandidateRequestDto(int number, String name, Long cityId) {
        this.number = number;
        this.name = name;
        this.cityId = cityId;
    }

    public Candidate toEntity(City city) {
        return Candidate.builder()
                .number(number)
                .name(name)
                .city(city)
                .build();
    }
}

ResponseDto

@Getter @Setter
public class CandidateResponseDto {

    private int number;
    private String name;

    @Builder
    public CandidateResponseDto(Candidate candidate) {
        this.number = candidate.getNumber();
        this.name = candidate.getName();
    }
}

6. Candidate Service Test

테스트 요구사항

  • 후보 추가
  • 후보 삭제
  • 한 도시에 같은 번호(number)를 가진 후보가 둘 이상 존재할 수 없다

테스트 코드

@SpringBootTest
@Transactional
class CandidateServiceTest {

    @Autowired CandidateRepository candidateRepository;
    @Autowired CandidateService candidateService;
    @Autowired CityService cityService;

    @Test
    public void 후보_추가() throws Exception {
        //given
        City city = createCity();

        //when
        CandidateRequestDto candidateRequestDto1 = new CandidateRequestDto(1, "Jake", city.getId());
        CandidateRequestDto candidateRequestDto2 = new CandidateRequestDto(2, "Sam", city.getId());
        candidateService.join(candidateRequestDto1);
        candidateService.join(candidateRequestDto2);

        //then
        assertEquals(2, candidateRepository.findAllByCity(city).size());
    }

    @Test
    public void 후보_삭제() throws Exception {
        //given
        City city = createCity();

        CandidateRequestDto candidateRequestDto = new CandidateRequestDto(1, "Jake", city.getId());
        Long candidateId = candidateService.join(candidateRequestDto);

        //when
        candidateService.delete(candidateId);

        //then
        assertEquals(null, candidateRepository.findById(candidateId));

    }

    @Test
    public void 후보_중복_검증() throws Exception {
        //given
        City city = createCity();

        //when
        CandidateRequestDto candidateRequestDto = new CandidateRequestDto(1, "Jake", city.getId());
        candidateService.join(candidateRequestDto);

        CandidateRequestDto candidateRequestDto2 = new CandidateRequestDto(1, "Sam", city.getId());

        //then
        try {
            candidateService.join(candidateRequestDto2);
        } catch (IllegalStateException e) {
            return;
        }
        Assertions.fail();
    }

    private City createCity() {
        District district = new District("서울", "서울특별시 종로구", "서울 종로");
        City city = new City(district);
        cityService.join(city);
        return city;
    }
}

테스트 결과

profile
Java/Spring Back-End Developer

0개의 댓글