[Study-3주차] PetClinicProject CRUD - ①
[Study-3주차] PetClinicProject CRUD - ①
[Study-2주차] PetClinicProject 초기 설정 + 클래스 생성 + 코드 설명 [Study-2주차] PetClinicProject 초기 설정 + 클래스 생성 + 코드 설명 [Study-1주차] PetClinicProject 기능 명세 + 개념 정리 + 2주차 과제 [Study-1주
soohykeee.tistory.com
앞서는 Owners, Pets에 대해서 CRUD 작성을 해주었다. 이번에는 Vists, Vets, Specialties, Vets_Specialties를 CRUD해줄 것이다.
Visits
VisitsController
package kr.co.jshpetclinicstudy.controller;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/visits/")
public class VisitsController {
private final VisitsService visitsService;
public void createVisit(@RequestBody @Valid VisitsRequestDto.CREATE create) {
visitsService.createVisit(create);
}
public List<VisitsResponseDto.READ> getVisitList() {
return visitsService.getVisitList();
}
public VisitsResponseDto.DETAIL_READ getVisit(Long id) {
VisitsResponseDto.DETAIL_READ detailRead = visitsService.getVisit(id);
return detailRead;
}
public List<VisitsResponseDto.READ> getVisitListOfPet(Long petId) {
return visitsService.getVisitListOfPet(petId);
}
public void updateVisit(@RequestBody @Valid VisitsRequestDto.UPDATE update) {
visitsService.updateVisit(update);
}
public void deleteVisit(Long id) {
visitsService.deleteVisit(id);
}
}
Visits DTO
package kr.co.jshpetclinicstudy.service.model;
public class VisitsRequestDto {
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class CREATE {
private LocalDate visitDate;
private String description;
private Pets pets;
}
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class UPDATE {
private Long visitId;
private LocalDate visitDate;
private String description;
private Pets pets;
}
}
package kr.co.jshpetclinicstudy.service.model;
public class VisitsResponseDto {
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public static class READ {
private Long visitId;
private LocalDate visitDate;
private String description;
private Pets pets;
}
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public static class DETAIL_READ {
private Long visitId;
private LocalDate visitDate;
private String description;
private Pets pets;
private String petName;
private String petType;
private String ownerFirstName;
}
}
Visits Entity
package kr.co.jshpetclinicstudy.persistence.entity;
@Entity
@AttributeOverride(name = "id", column = @Column(name = "visit_id", length = 4))
@Getter
@NoArgsConstructor
@Table(name="tbl_visits")
public class Visits extends BaseEntity{
// ... 생략
public static Visits dtoToEntity(VisitsRequestDto.CREATE create) {
return Visits.builder()
.visitDate(create.getVisitDate())
.description(create.getDescription())
.pets(create.getPets())
.build();
}
public static VisitsResponseDto.READ entityToDto(Visits visits) {
return VisitsResponseDto.READ.builder()
.visitId(visits.getId())
.visitDate(visits.getVisitDate())
.description(visits.getDescription())
.pets(visits.getPets())
.build();
}
public static VisitsResponseDto.DETAIL_READ entityToDetailDto(Visits visits) {
return VisitsResponseDto.DETAIL_READ.builder()
.visitId(visits.getId())
.visitDate(visits.getVisitDate())
.description(visits.getDescription())
.pets(visits.getPets())
.petName(visits.getPets().getName())
.petType(visits.getPets().getTypes().toString())
.ownerFirstName(visits.getPets().getOwners().getFirstName())
.build();
}
public void changeVisitDate(LocalDate changeVisitDate) {
this.visitDate = changeVisitDate;
}
public void changeVisitDescription(String changeDescription) {
this.description = changeDescription;
}
public void changeVisitPet(Pets changeVisitPet) {
this.pets = changeVisitPet;
}
}
VisitsRespository
package kr.co.jshpetclinicstudy.persistence.repository;
@Repository
public interface VisitsRepository extends JpaRepository<Visits, Long> {
@Query(value = "select v.visit_id, v.visit_date, v.description, v.pet_id " +
"from tbl_visits v " +
"where v.pet_id=:petId", nativeQuery = true)
List<Visits> findVisitListByPetId(Long petId);
}
VisitsService
package kr.co.jshpetclinicstudy.service;
@Service
@RequiredArgsConstructor
public class VisitsService {
private final VisitsRepository visitsRepository;
public void createVisit(VisitsRequestDto.CREATE create) {
final Visits visits = Visits.dtoToEntity(create);
visitsRepository.save(visits);
}
public List<VisitsResponseDto.READ> getVisitList() {
return visitsRepository.findAll().stream().map(Visits::entityToDto).collect(Collectors.toList());
}
@Transactional
public VisitsResponseDto.DETAIL_READ getVisit(Long id) {
return Visits.entityToDetailDto(visitsRepository.findById(id).get());
}
@Transactional
public List<VisitsResponseDto.READ> getVisitListOfPet(Long petId) {
return visitsRepository.findVisitListByPetId(petId).stream().map(Visits::entityToDto).collect(Collectors.toList());
}
public void updateVisit(VisitsRequestDto.UPDATE update) {
final Optional<Visits> visits = visitsRepository.findById(update.getVisitId());
isVisits(visits);
visits.get().changeVisitDate(update.getVisitDate());
visits.get().changeVisitDescription(update.getDescription());
visits.get().changeVisitPet(update.getPets());
visitsRepository.save(visits.get());
}
public void deleteVisit(Long id) {
final Optional<Visits> visits = visitsRepository.findById(id);
isVisits(visits);
visitsRepository.deleteById(id);
}
private void isVisits(Optional<Visits> visits) {
if (visits.isEmpty()) {
throw new RuntimeException("This Visits is Not Exist");
}
}
}
VisitsService Test Code
package kr.co.jshpetclinicstudy.service;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
class VisitsServiceTest {
@Autowired
private VisitsService visitsService;
@Autowired
private VisitsRepository visitsRepository;
@Autowired
private PetsRepository petsRepository;
@Test
void createVisit() {
Optional<Pets> pets = petsRepository.findById(1L);
VisitsRequestDto.CREATE create = VisitsRequestDto.CREATE.builder()
.visitDate(LocalDate.of(2023, 10, 12))
.description("건강검진 및 예방접종")
.pets(pets.get())
.build();
visitsService.createVisit(create);
Optional<Visits> visits = visitsRepository.findById(1L);
assertThat(visits.get().getDescription()).startsWith("건강검진");
}
@Test
void getVisitList() {
List<VisitsResponseDto.READ> readList = visitsService.getVisitList();
assertThat(readList.get(0).getDescription()).startsWith("건강검진");
assertThat(visitsRepository.findAll().size()).isEqualTo(readList.size());
}
@Test
void getVisit() {
VisitsResponseDto.DETAIL_READ detailRead = visitsService.getVisit(1L);
assertThat(detailRead.getOwnerFirstName()).isEqualTo("수혁");
assertThat(detailRead.getPetName()).isEqualTo("멍멍이");
}
@Test
void getVisitListOfPet() {
List<VisitsResponseDto.READ> visitListOfPet = visitsService.getVisitListOfPet(1L);
assertThat(visitListOfPet.size()).isEqualTo(1);
}
@Test
void updateVisit() {
Optional<Pets> pets = petsRepository.findById(1L);
VisitsRequestDto.UPDATE update = VisitsRequestDto.UPDATE.builder()
.visitId(1L)
.visitDate(LocalDate.of(2023, 05, 01))
.description("수정테스트")
.pets(pets.get())
.build();
visitsService.updateVisit(update);
assertThat(visitsRepository.findById(1L).get().getDescription()).isEqualTo("수정테스트");
}
@Test
void deleteVisit() {
visitsService.deleteVisit(1L);
assertThat(visitsRepository.findById(1L)).isEmpty();
}
}
여기서 Vets와 Speciatieis에 대해 어떻게 할지 고민이 많았다. 아직 스터디 모임을 통해 어떻게 할지 듣지 못했기에 내가 했던 방식에 대해 설명해보려 한다.
우선 Vets(수의사) 와 Sepcialties(전문학위)에 대해 다대다로 생각하여 Vets_Specialties 라는 연관 테이블을 생성한 후 일대다 2개로 엮어줬다. 우선적으로 내가 생각한 방식은, Vets와 Specialties를 연관테이블은 비워둔 채로 각각 생성할 수 있게 만들었다. 그런 다음 Vets와 Specialties를 서로 연관테이블에 집어넣는 방식으로 개발을 하려했다.
그렇기에 Vets와 Specialties에 대해 앞서와 같은 방식으로 CRUD가 가능하도록 해주었고, 연관테이블에 관해서는 CRUD를 진행하긴 했지만 아직 명확한 부분이 없어 미완성인 코드이다. 아래의 코드를 보며 다시한번 설명하겠다.
기존에는 위와 같은 방식으로 개발하려 했지만, 이렇게 하지 않고, Vets인 수의사쪽에서 모두 생성하고 연관할 수 있도록 CRUD를 해주었다.
Vets
VetsController
package kr.co.jshpetclinicstudy.controller;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/vets/")
public class VetsController {
private final VetsService vetsService;
public void createVet(@RequestBody @Valid VetsRequestDto.CREATE create) {
vetsService.createVet(create);
}
public List<VetsResponseDto.READ> getVetList() {
return vetsService.getVetList();
}
public VetsResponseDto.DETAIL_READ getVet(Long id) {
VetsResponseDto.DETAIL_READ detailRead = vetsService.getVet(id);
return detailRead;
}
public void updateVet(@RequestBody @Valid VetsRequestDto.UPDATE update) {
vetsService.updateVet(update);
}
public void deleteVet(Long id) {
vetsService.deleteVet(id);
}
}
위에서 설명했듯이, Vets를 Specialties가 비어있는 채로 생성할 수 있게 만들었기에 앞서와 동일한 방식으로 CRUD를 진행해주었다.
VetsDTO
package kr.co.jshpetclinicstudy.service.model;
public class VetsRequestDto {
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class CREATE {
private String firstName;
private String lastName;
private List<VetsSpecialties> specialties;
}
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class UPDATE {
private Long vetId;
private String firstName;
private String lastName;
private List<VetsSpecialties> specialties;
}
}
package kr.co.jshpetclinicstudy.service.model;
public class VetsResponseDto {
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class READ {
private Long vetId;
private String firstName;
private String lastName;
private List<VetsSpecialties> specialties;
}
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class DETAIL_READ {
private Long vetId;
private String firstName;
private String lastName;
private List<VetsSpecialties> specialties;
}
}
Vets Entity
package kr.co.jshpetclinicstudy.persistence.entity;
@Entity
@AttributeOverride(name = "id", column = @Column(name = "vet_id", length = 4))
@Getter
@NoArgsConstructor
@Table(name="tbl_vets")
public class Vets extends BaseEntity{
// ... 생략
public static Vets dtoToEntity(VetsRequestDto.CREATE create) {
return Vets.builder()
.firstName(create.getFirstName())
.lastName(create.getLastName())
.build();
}
public static VetsResponseDto.READ entityToDto(Vets vets) {
return VetsResponseDto.READ.builder()
.vetId(vets.getId())
.firstName(vets.getFirstName())
.lastName(vets.getLastName())
.specialties(vets.getSpecialties())
.build();
}
public static VetsResponseDto.DETAIL_READ entityToDetailDto(Vets vets) {
return VetsResponseDto.DETAIL_READ.builder()
.vetId(vets.getId())
.firstName(vets.getFirstName())
.lastName(vets.getLastName())
.specialties(vets.getSpecialties())
.build();
}
public void changeVetFirstName(String changeFirstName) {
this.firstName = changeFirstName;
}
public void changeVetLastName(String changeLastName) {
this.lastName = changeLastName;
}
}
VetsService
package kr.co.jshpetclinicstudy.service;
@Service
@RequiredArgsConstructor
public class VetsService {
private final VetsRepository vetsRepository;
public void createVet(VetsRequestDto.CREATE create) {
final Vets vets = Vets.dtoToEntity(create);
vetsRepository.save(vets);
}
public List<VetsResponseDto.READ> getVetList() {
return vetsRepository.findAll().stream().map(Vets::entityToDto).collect(Collectors.toList());
}
public VetsResponseDto.DETAIL_READ getVet(Long id) {
return Vets.entityToDetailDto(vetsRepository.findById(id).get());
}
public void updateVet(VetsRequestDto.UPDATE update) {
final Optional<Vets> vets = vetsRepository.findById(update.getVetId());
isVets(vets);
vets.get().changeVetFirstName(update.getFirstName());
vets.get().changeVetLastName(update.getLastName());
vetsRepository.save(vets.get());
}
public void deleteVet(Long id) {
final Optional<Vets> vets = vetsRepository.findById(id);
isVets(vets);
vetsRepository.deleteById(id);
}
private void isVets(Optional<Vets> vets) {
if (vets.isEmpty()) {
throw new RuntimeException("This Vet is Not Exist");
}
}
}
VetsService Test Code
package kr.co.jshpetclinicstudy.service;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
class VetsServiceTest {
@Autowired
private VetsService vetsService;
@Autowired
private VetsRepository vetsRepository;
@Test
void createVet() {
VetsRequestDto.CREATE create = VetsRequestDto.CREATE.builder()
.firstName("닥터")
.lastName("김")
.build();
vetsService.createVet(create);
Optional<Vets> vets = vetsRepository.findById(1L);
assertThat(vets.get().getFirstName()).isEqualTo("닥터");
}
@Test
void getVetList() {
List<VetsResponseDto.READ> readList = vetsService.getVetList();
assertThat(readList.get(0).getFirstName()).isEqualTo("닥터");
assertThat(vetsRepository.findAll().size()).isEqualTo(readList.size());
}
@Test
void getVet() {
VetsResponseDto.DETAIL_READ detailRead = vetsService.getVet(1L);
assertThat(detailRead.getFirstName()).isEqualTo("닥터");
}
@Test
void updateVet() {
VetsRequestDto.UPDATE update = VetsRequestDto.UPDATE.builder()
.vetId(1L)
.firstName("민수")
.lastName("이")
.build();
vetsService.updateVet(update);
assertThat(vetsRepository.findById(1L).get().getFirstName()).isEqualTo("민수");
}
@Test
void deleteVet() {
vetsService.deleteVet(1L);
assertThat(vetsRepository.findById(1L)).isEmpty();
}
}
아직 CRUD가 제대로 완성되지 않았다.
다음에 Exception과 Mapper를 적용하며 다시한번 CRUD를 점검하겠다.