2025년 2월 15일에 작성됨
이전 포스팅에서는 크롤링으로 얻은 데이터를 MySQL에 저장하는 과정까지 다뤘다.
이제는 서버를 구축할 단계이다.
서버는 스프링부트를 이용해서 구축할 것이다.
흔히 사람들은 스프링(Spring)과 스프링 부트(Spring Boot)의 차이를 헷갈려하기도 한다.
차이점을 간단히 살펴보겠다.
스프링(Spring)과 스프링 부트(Spring Boot)의 차이점
스프링(Spring)
- 자바 기반의 웹 프레임워크로, 의존성 주입과 관점 지향 프로그래밍 같은 개념을 통해 유지보수와 확장성을 고려한 애플리케이션을 개발할 수 있도록 도와준다.
- Spring MVC, Spring Security, Spring Data JPA 등 다양한 모듈이 존재하지만, 직접 설정해야 하는 부분이 많다.
- 설정이 많고 복잡하기 때문에 개발자가 많은 XML 또는 Java 설정 파일을 작성해야 한다.
스프링 부트(Spring Boot)
- 스프링을 쉽게 사용할 수 있도록 만든 프레임워크이다.
- 내장 톰캣이 있어서, 따로 WAS(Web Application Server)를 설치할 필요가 없다.
- 기본적으로 spring-boot-starter 패키지를 제공하여 설정 없이도 쉽게 프로젝트를 시작할 수 있다.
스프링(Spring)과 스프링 부트(Spring Boot)의 사용 환경
스프링(Spring)
- 세부적인 설정이 필요한 대규모 프로젝트
- 직접 WAS를 구성하고 배포 환경을 맞춰야 하는 경우
스프링 부트(Spring Boot)
- 빠르게 웹 서비스를 개발해야 할 때
- 설정보다 기능 개발에 집중하고 싶을 때
- 마이크로서비스 아키텍처(MSA) 기반 프로젝트
- 기존의 Spring 프로젝트를 개선하고 싶을 때
어떤 걸 사용해야할까
결론은 지금 진행 중인 TorqueFinder 프로젝트는 스프링 부트를 사용하는 것이 효율적일 것 같다.
우선 설정이 간편하고 자동화가 잘 되어 있어서 개발 속도를 높일 수 있고, MySQL과 쉽게 연동할 수 있을 뿐만 아니라, 내장 톰캣을 활용해 바로 실행 가능하기 때문이다.
쉽게 말해서 스프링 부트는 스프링을 쉽게 사용할 수 있도록 도와주는 업그레이드 버전이라고 보면 된다.
스프링 부트를 이용한 서버 구축
https://start.spring.io/
위 링크에 접속해서 프로젝트를 생성할 것이다.
프로젝트는 Gradle - Groovy를 선택했고,
언어는 자바를 선택했다.
이외 버전들은 자동선택되는 버전들을 사용했다.
(자동 선택 되어있는 버전들은 해당 시점 기준으로 사용하기에 최적화된 버전이다.)
그리고 가장 중요한 의존성을 추가해야하는데,
- Spring Web (REST API 개발용)
- Spring Data JPA (MySQL과 연동)
- MySQL Driver (MySQL 데이터베이스 사용)
- Lombok (코드 간결화)
- Spring Boot DevTools (개발 편의성)
이렇게 5가지를 추가했다.
JPA를 사용하면서 느낀점은 기본적인 CRUD를 전부 메서드로 제공해준다는 점에서 너무 편리했다.
직접 SQL을 작성하지 않아도 데이터베이스에 데이터가 저장된다는 점이 한편으로는 신기했다.
그리고 또 편리한 점이 Lombok이었다.
Lombok은 기본 엔티티 클래스를 만들 때, Getter & Setter 메서드들을 만들어야하는데, 이를 직접 만들어준다.
인텔리제이에는 Getter & Setter 메서드를 만들어주는 단축키가 있긴하지만 Lombok을 사용하면 더 편리하게 사용할 수 있다.
결과적으로 위 스크린샷과 같은 세팅을 사용하였다.
그리고 아래의 GENERATE 버튼을 눌러 다운로드해주면 의존성들과 함께 포함된 파일이 다운로드된다.
압축을 해제하고 실행시켜주면 된다.
프로젝트를 실행하면 application.properties라는 파일이 있을텐데 MySQL과의 연동을 위해 세팅해야하는 항목이 존재한다.
# MySQL 데이터베이스 설정 (환경 변수 사용)
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA 및 Hibernate 설정
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
# 서버 포트 설정
server.port=8080
# 로그 레벨 설정 (디버깅 시 'debug'로 변경 가능)
logging.level.org.springframework=info
logging.level.org.hibernate.SQL=debug
개인정보들은 제거를 했다.
사용자마다 각 환경에 맞게 작성해주면 MySQL과 연동이 될 것이다.
기본적인 디렉토리 구성
다음 기본적인 디렉토리를 구성해줄 것이다.
폴더를 총 4개를 만들었다.
controller는 API 엔드포인트, service는 비즈니스 로직, repository는 데이터베이스 연동, model은 엔티티 클래스들을 관리하는 폴더이다.
상세 코드
1. 차량 엔터티(Entity) 생성
엔터티 항목부터 살펴보자.
package com.example.autofinder.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "cars")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Car {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String carType;
private String model;
private String year;
private String mileage;
private String fuel;
private String region;
private String url;
}
@Entity
: 해당 클래스가 JPA 엔티티임을 선언한다. 이는 DB의 테이블과 매핑된다.@Table(name = "cars")
: 데이터베이스의 "cars" 테이블과 연결된다.@Getter
: Lombok을 사용하여 모든 필드의 Getter 메서드를 자동으로 생성한다.@Setter
: Lombok을 사용하여 모든 필드의 Setter 메서드를 자동으로 생성한다.@NoArgsConstructor
: Lombok을 사용하여 기본 생성자를 자동으로 생성한다.@AllArgsConstructor
: Lombok을 사용하여 모든 필드를 포함한 생성자를 자동으로 생성한다.
위에서 볼 수 있듯이 Lombok을 사용했을 때의 이점이 명확히 존재한다.
그리고나서 차량 종류, 차량 모델명, 차량 연식, 주행거리, 연료 타입, 판매 지역, 차량 상세 페이지의 URL까지 만들어준다.
2. Repository 및 Service 생성
JPA를 사용하기 위해 CarRepository를 만들어준다. 그리고 비즈니스 로직인 CarService까지 만들어준다.
package com.example.autofinder.repository;
import com.example.autofinder.model.Car;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface CarRepository extends JpaRepository<Car, Long> {
@Query("SELECT c FROM Car c WHERE c.fuel = :fuel AND CAST(REPLACE(REPLACE(c.mileage, 'km', ''), ',', '') AS int) <= :mileage AND c.region = :region")
List<Car> searchCars(@Param("fuel") String fuel, @Param("mileage") int mileage, @Param("region") String region);
}
package com.example.autofinder.service;
import com.example.autofinder.model.Car;
import com.example.autofinder.repository.CarRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class CarService {
private final CarRepository carRepository;
public List<Car> searchCars(String fuel, String mileage, String region) {
int mileageInt = Integer.parseInt(mileage.replace(",", "").replace("km", "").trim());
return carRepository.searchCars(fuel, mileageInt, region);
}
}
생각보다 로직이 복잡해보이는데, 이는 바로 차량의 주행거리인 키로수가 문자열로 되어있기 때문이다.
문자열로 되어있는 주행거리를 비교하기 위해서는 정수로 바꾸어줘야하는데 그 로직이 포함되어 있는 것이다.
해당 로직을 간단히 살펴보자면,
REPLACE(c.mileage, 'km', '')
를 사용해서 km 문자열을 제거한다.REPLACE(..., ',', '')
를 사용해서 쉼표를 제거한다.CAST(... AS int)
를 사용해서 문자열을 정수로 변환하여 비교 가능하게 만든다.
3. Controller 생성 (API 엔드포인트)
이 컨트롤러는 차량 데이터를 검색하는 REST API를 제공한다.
여기에서 REST API란 웹에서 데이터를 주고받는 방식 중 하나이다. 쉽게 생각해서 우리가 웹사이트에서 정보를 요청하거나 서버에서 데이터를 받아오는 방식이라고 생각하면 된다.
예를 들어 유튜브 앱에서 "차량 리뷰"를 검색한다고 가정하자.
이때 앱이 유튜브 서버에 "차량 리뷰 영상 목록을 줘!"라고 요청을 보낼 것이다.
이 요청을 REST API 방식으로 보내면, 유튜브 서버는 "차량 리뷰 관련 영상 데이터를 JSON 형식으로 줄게!"라고 응답한다.
즉, REST API = 요청(Request) + 응답(Response) 방식의 웹 서비스 규칙이라고 보면 된다.
그렇다면 왜 REST API를 사용할까?
- 웹에서 데이터를 쉽게 주고받을 수 있다.
- 다양한 플랫폼(웹, 앱)에서 사용 가능하다.
- 간단하고 직관적인 URL 구조를 갖고 있다.
- JSON 형식으로 데이터를 주고받아 처리하기 쉽다.
라고 한다.. 사실 필자도 어쩌면 이전부터 REST API에 대해 정확히 모른 채로 사용하고 있었을 수도 있겠다 생각했다.
그럼 이어서 컨트롤러에 대해 살펴보자.
컨트롤러는 클라이언트에서 특정 검색 조건(우선 연료 타입, 주행거리, 지역으로 한정)을 전달하면 해당 조건에 맞는 차량 목록을 반환한다.
package com.example.autofinder.controller;
import com.example.autofinder.model.Car;
import com.example.autofinder.service.CarService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/cars")
@RequiredArgsConstructor
public class CarController {
private final CarService carService;
@GetMapping("/search")
public List<Car> searchCars(
@RequestParam(required = false) String fuel,
@RequestParam(required = false) String mileage,
@RequestParam(required = false) String region) {
return carService.searchCars(fuel, mileage, region);
}
}
@RestController
: RESTful 웹 서비스의 컨트롤러임을 명시한다. (JSON 형태로 데이터를 반환한다.)@RequestMapping("/api/cars")
: 기본 API 엔드포인트 경로를 설정한다.(/api/cars)@RequiredArgsConstructor
: Lombok을 사용하여 final 필드에 대한 생성자를 자동 생성한다.
carService라는 차량 정보를 처리하는 서비스 객체를 생성한다. 이를 의존성 주입이라고도 한다.
@GetMapping("/search")
: 위에서 기본 API 엔드포인트 경로가 설정되었기 때문에 전체 경로를 작성하면 /api/cars/search
가 된다.
return carService.searchCars(fuel, mileage, region);
: CarService의 searchCars 메서드를 호출해서 검색 결과를 반환한다.
이렇게 하면
http://localhost:8080/api/cars/search?fuel=디젤&mileage=100000®ion=서울
같은 API 요청으로 차량을 검색할 수 있다.
그러면 결과값이 JSON 형태로 출력되는 것을 확인할 수 있다.
정리
현재까지 프로젝트를 진행하면서, 실은 블로그에 포스팅을 하면서 더 많이 배운 것 같다.
예를 들면 지금까지 모르고 사용했던 REST API라던가 전체적인 파일의 흐름, 의존성 주입에 대해 확실하게 정리를 했다.
그래서 앞으로 프로젝트 코드뿐만 아니라 포스팅에도 집중을 하면서 내가 모르고 지나쳤던 내용들을 좀 더 심도있게, 깊이있게 살펴봐야겠다.
다음 포스팅에서는 관리자 기능을 포함해서 기본적인 CRUD 기능을 완성할 것 같다.
'SIDE PROJECT > AUTOFINDER' 카테고리의 다른 글
회원가입, 보안 로직 개발 (1) | 2025.04.17 |
---|---|
CRUD 기능 구현 (0) | 2025.04.17 |
데이터 저장 방식의 변경 (MySQL 사용) (0) | 2025.04.17 |
크롤링과 데이터 정제 (0) | 2025.04.17 |
사이드 프로젝트(AutoFinder) 개요 (0) | 2025.04.17 |