Spring Boot를 사용한 여행 계획 API 개발

hyuko·2023년 5월 21일
0

팀 프로젝트

목록 보기
6/8

이번 블로그에서는 Spring Boot와 MySQL을 활용하여
여행 계획 API를 개발하는 방법에 대해 알아보겠습니다.
여행 계획을 저장하고 조회하는 기능을 구현하며,
API 요청을 처리하는 컨트롤러와 데이터베이스와의 상호작용을
담당하는 서비스 클래스에 대해 설명하겠습니다.

1. 개발 환경 설정

먼저, 개발을 시작하기 전에 필요한 환경 설정을 완료해야 합니다.
아래는 개발 환경 설정에 필요한 내용입니다.

Java Development Kit (JDK) 설치
Spring Boot 프로젝트 생성
의존성 관리를 위한 Maven 또는 Gradle 설정
MySQL 데이터베이스 설치 및 설정
자세한 환경 설정 방법은 본 블로그에서 다루지 않으므로,
이미 환경이 설정되어 있다고 가정하고 진행하겠습니다.


2. API 요청 처리를 위한 컨트롤러 개발

여행 계획을 저장하기 위한 API 요청을 처리하기 위해
TravelPlanController 클래스를 개발합니다.
해당 클래스는 Spring Boot의 @RestController 어노테이션을 통해
RESTful API 엔드포인트로 동작하며, /api/v1/travel/plan 엔드포인트에 POST 요청을 처리합니다.

@RestController
@RequestMapping("/api/v1/travel")
@RequiredArgsConstructor
public class TravelPlanController {

    private final TravelService travelService;

    @PostMapping("/plan")
    public ResponseEntity<?> plan(@RequestBody List<TravelPlanReqDto> travels) {

        travelService.travelSave(travels);
        return ResponseEntity.ok(DataRespDto.ofDefault());
    }
}

위 코드에서 @RequiredArgsConstructor 어노테이션은
필수 인자를 가진 생성자를 자동으로 생성하기 위한 Lombok 어노테이션입니다.
TravelService 객체를 주입받아 요청을 처리하도록 구현되어 있습니다.


3. DTO 클래스 정의

API 요청과 응답에 사용될 DTO(Data Transfer Object) 클래스를 정의합니다.
DTO 클래스는 데이터 전달을 위한 객체로, API 요청 데이터를 담거나 응답 데이터를 반환하는 용도로 사용됩니다. 여기서는 TravelPlanReqDto, LocationReqDto, Location, Schedule, TravelRoutes, Travels 등의
DTO 클래스가 사용됩니다.

아래는 TravelPlanReqDto의 예시입니다.

@Getter @Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TravelPlanReqDto {
    private int id;
    private LocalDate date;
    private List<LocationReqDto> location;
    private List<PartyDataReqDto> partyData;

    public void forEachLocation(Consumer<LocationReqDto> action) {
        for (LocationReqDto locationReqDto : location) {
            if (locationReqDto != null) {
                action.accept(locationReqDto);
            }
        }
    }

}

위 코드에서 @Getter와 @Setter 어노테이션은 Lombok을 사용하여
Getter와 Setter 메서드를 자동으로 생성하도록 합니다.
또한, @AllArgsConstructor, @NoArgsConstructor, @Builder 어노테이션을 통해
생성자와 빌더 패턴을 사용할 수 있도록 합니다.
TravelPlanReqDto는 여행 계획의 요청 데이터를 담기 위한 객체로, id, date, location 필드로 구성됩니다.


4. 서비스 클래스 개발

컨트롤러에서 호출되는 비즈니스 로직을 처리하기 위해 TravelService 클래스를 개발합니다.
해당 클래스는 TravelRepository를 주입받아 데이터베이스와의 상호작용을 담당합니다.
travelSave() 메서드는 여행 계획 데이터를 저장하는 기능을 구현한 메서드입니다.

@Service
@RequiredArgsConstructor
public class TravelService {
    private final TravelRepository travelRepository;

    public void travelSave(List<TravelPlanReqDto> travels) {
        if (travels != null && !travels.isEmpty()) {
            String travelName = UUID.randomUUID().toString();
            Integer travelId = null;

            for (TravelPlanReqDto dto : travels) {
                if (dto != null && dto.getLocation() != null && !dto.getPartyData().isEmpty()) {
                    List<LocationReqDto> locations = dto.getLocation();
                    List<PartyDataReqDto> partyData = dto.getPartyData();

                    for (int i = 0; i < locations.size(); i++) {
                        LocationReqDto locationReqDto = locations.get(i);
                        for (PartyDataReqDto partyDataReqDto : partyData) {
                            System.out.println(partyDataReqDto.getUserId());

                            if (i == 0 && travelId == null) {
                                // Save the travel with the first location and first participant and get the travel ID
                                travelId = travelRepository.callInsertTravelData(
                                        travelName,
                                        locationReqDto.getAddr(),
                                        locationReqDto.getLat(),
                                        locationReqDto.getLng(),
                                        partyDataReqDto.getUserId(),
                                        dto.getDate() // Visit date
                                );
                            } else {
                                // Save the other locations with the same travel ID and all participants
                                travelRepository.callInsertTravelData(
                                        null,
                                        locationReqDto.getAddr(),
                                        locationReqDto.getLat(),
                                        locationReqDto.getLng(),
                                        partyDataReqDto.getUserId(),
                                        dto.getDate() // Visit date
                                );
                            }
                        }
                    }
                }
            }

        }
    }

위 코드에서 TravelRepository 객체를 주입받아 데이터베이스와의 상호작용을 수행합니다.
travelSave() 메서드는 전달받은 여행 계획 데이터를 순회하며, 데이터베이스에 저장하는 로직을 구현하였습니다.


5. 데이터베이스 연동

위에서 구현한 TravelService 클래스에서 사용되는 데이터베이스 연동을 위해
TravelRepository 인터페이스를 개발합니다.
해당 인터페이스는 MyBatis나 JPA와 같은 ORM(Object-Relational Mapping) 프레임워크를 활용하여
데이터베이스와의 상호작용을 담당합니다.
여기서는 MyBatis의 XML 매퍼 파일을 사용한 방식을 예시로 설명하겠습니다.

<!-- travel_mapper.xml -->
 <insert id="callInsertTravelData">
        {CALL InsertTravelData(#{travelName}, #{addr}, #{lat}, #{lng}, #{userId}, #{scheduleDate})}
    </insert>
</insert>

위 코드는 TravelRepository의 callInsertTravelData 메서드에 대한 SQL 매퍼(XML) 파일의 일부입니다.
해당 매퍼에서는 InsertTravelData 프로시저를 호출하여 여행 데이터를 데이터베이스에 저장합니다.
프로시저는 주어진 매개변수를 기반으로 데이터를 저장한 후,
저장된 여행 ID, 위치 ID, 스케줄 위치 ID를 반환합니다.

CREATE DEFINER=`admin`@`%` PROCEDURE `InsertTravelData`(IN travelName VARCHAR(255), IN addr VARCHAR(255), IN lat decimal(18,10), IN lng decimal(18,10), IN userId integer, IN scheduleDate date)
BEGIN
    DECLARE v_travelId INT DEFAULT (SELECT MAX(travel_id) FROM travels_tb);
    DECLARE v_locationId INT;
    DECLARE v_scheduleId INT;

    -- Check if the travel name is NULL
    IF travelName IS NOT NULL THEN
        -- Insert into travels_tb and get the generated ID
        INSERT INTO travels_tb(travel_name) VALUES (travelName);
        SET v_travelId = LAST_INSERT_ID();
    END IF;

    -- Check if the same dateId and visitDate already exist in schedule_tb
   IF NOT EXISTS (SELECT 1 FROM schedule_tb WHERE travel_id = v_travelId AND schedule_date = scheduleDate) THEN
    -- Insert into schedule_tb and get the generated ID
    INSERT INTO schedule_tb(travel_id, schedule_date) VALUES (v_travelId, scheduleDate);
    SET v_scheduleId = LAST_INSERT_ID();
ELSE 
    -- If record already exists, get the schedule_id
    SET v_scheduleId = (SELECT schedule_id FROM schedule_tb WHERE travel_id = v_travelId AND schedule_date = scheduleDate);
END IF;

    -- Insert into location_tb and get the generated ID
    IF NOT EXISTS (SELECT 1 FROM location_tb WHERE address = addr) THEN
    
    INSERT INTO location_tb(address, location_x, location_y) VALUES (addr, lat, lng);
    SET v_locationId = LAST_INSERT_ID();
ELSE
	SET v_locationId = (SELECT location_id FROM location_tb WHERE address = addr);
END IF;

    -- Insert into travel_participants_tb, ignore if the pair (travel_id, user_id) already exists
    IF NOT EXISTS (SELECT 1 FROM travel_participants_tb WHERE travel_id = v_travelId AND user_id = userId) THEN
    INSERT INTO travel_participants_tb(travel_id, user_id) VALUES (v_travelId, userId);
END IF;

    -- Insert into travel_routes_tb
    IF NOT EXISTS (SELECT 1 FROM travel_routes_tb WHERE schedule_id = v_scheduleId AND location_id = v_locationId) THEN
    
    INSERT INTO travel_routes_tb(schedule_id, location_id) VALUES (v_scheduleId, v_locationId);
    END IF;
    
    SELECT v_travelId, v_locationId, v_scheduleId;
END

위 SQL 코드는 InsertTravelData 프로시저의 내용을 담고 있습니다.
프로시저는 입력받은 매개변수를 기반으로 데이터를 여러 테이블에 저장한 후,
해당 테이블에서 생성된 ID 값을 반환합니다.


마무리

이제 Spring Boot를 사용하여 여행 계획 API를 개발하는 방법에 대해 알아보았습니다.
TravelPlanController를 통해 API 요청을 처리하고,
TravelService를 통해 비즈니스 로직을 처리하며,
TravelRepository를 통해 데이터베이스와의 상호작용을 수행합니다.
이를 통해 사용자는 API를 통해 여행 계획을 저장하고 조회할 수 있습니다.

profile
백엔드 개발자 준비중

0개의 댓글