이번 시간에는 데이터베이스에서 CRUD 작업을 하기 위한 JPA를 적용해 보도록 하겠습니다. 클린 아키텍처인 만큼 도메인을 만드는 것부터 시작해 보며 이를 DB에 저장하고 조회하는 것까지 해보도록 합니다.
Project issue: https://github.com/kdohyeon/crypto-labs/issues/12
Pull request: https://github.com/kdohyeon/crypto-labs/pull/13
Gradle 설정
JPA를 사용하기 위해서는 의존성을 먼저 추가해주어야 한다. 멀티 모듈 환경에서는 필요한 의존성을 주로 각 모듈에 위치한 build.gradle 에서 관리하게 되는데 Entity는 보통 Domain 모듈에서 관리된다. 따라서 Domain 모듈의 build.gradle 에 아래 의존성을 추가해준다.
dependencies {
implementation("org.springframework.boot:spring-boot-starter")
implementation("jakarta.persistence:jakarta.persistence-api")
}
Entity 설정
데이터베이스에 저장할 엔티티는 다음과 같이 작성할 수 있다. Upbit에서 거래 가능한 마켓에 대한 정보이다.
@Entity
class Market(
id: Long = 0,
marketPair: String,
koreanName: String,
englishName: String,
marketWarning: Boolean,
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = id
protected set
var marketPair: String = marketPair
protected set
var koreanName: String = koreanName
protected set
var englishName: String = englishName
protected set
var marketWarning: Boolean = marketWarning
protected set
}
H2 DB
엔티티까지 만들고 H2 인메모리 데이터베이스가 연결된 스프링부트 어플리케이션을 시작하면 알아서 테이블까지 만들어준다.
localhost:8080/h2-console
1) h2-console 로 접근을 하면 MARKET 이라는 테이블이 이미 생성되어 있는 것을 확인할 수 있다. 컬럼도 엔티티에서 정의한대로 잘 생성되어 있다. 2) SELECT문을 활용하여 데이터를 조회해보면 바로 아래에 해당 조회에 대한 결과가 노출된다. 3) 아직 데이터를 넣지 않았기 때문에 아무런 데이터가 없다.
데이터 넣기
이제 해보아야 할 것은 데이터를 넣고, 데이터를 JPA와 QueryDSL으로 조회하는 것이다. 수작업으로 하나씩 넣긴 귀찮으니 한번에 다 넣을 수 코드를 작성해보자.
Upbit에서 데이터를 호출하여 DB에 Insert하기
MarketController 에서 요청을 받는다. 해당 요청은 CreateMarketUseCase 로 전달이 되며 이를 구현하는 MarketCreateService 가 수행된다. MarketCreateService 에서는 Upbit API 를 호출하여 거래 가능한 마켓 정보를 모두 조회하고 이를 H2 DB 에 저장한다. 가져온 각 마켓 정보를 순회하며 Market 엔티티로 변환하고 MarketRepository 의 save(...) 를 통해 저장한다. 데이터를 저장하는 방법은 JPA 를 활용한다 (MarketJpaRepository)
흐름을 알아보았으니 각 코드에 대해 알아보자. 코드는 반대로 뒤에서부터 알아보는 것이 편하기 때문에 MarketJpaRepository 부터 알아보도록 하자.
MarketJpaRepository
JpaRepository 를 implement 하고 있다. 대상 엔티티는 Market 이며 해당 엔티티의 ID 의 데이터 타입은 Long 이다. marketPair 로 조회를 하고 싶기 때문에 별도 메소드로 만들었다.
interface MarketJpaRepository : JpaRepository<Market, Long> {
fun findByMarketPair(marketPair: String): Optional<Market>
}
MarketRepository & MarketRepositoryImpl
MarketRepository 는 마켓 정보에 대한 DB 컨트롤을 하기 위한 output port 이다. 이를 구현한 것이 MarketRepositoryImpl 인데 DB 컨트롤을 하기 위한 문지기 역할 정도로 보면 될 것 같다.
interface MarketRepository {
fun save(market: Market)
fun findByMarketPair(marketPair: String): Optional<Market>
}
@Repository
class MarketRepositoryImpl(
private val marketJpaRepository: MarketJpaRepository,
) : MarketRepository {
override fun save(market: Market) {
marketJpaRepository.save(market)
}
override fun findByMarketPair(marketPair: String): Optional<Market> {
return marketJpaRepository.findByMarketPair(marketPair)
}
}
CreateMarketUseCase & MarketCreateService
CreateMarketUseCase 는 마켓 데이터 생성을 위한 유즈케이스를 정의한다. CreateMarketUseCase 를 구현하는 구현체이다. Upbit에서 거래 가능한 마켓 정보를 조회하는 OPEN API를 호출하여 데이터를 받아온 다음 MarketRepository를 통해 데이터를 저장한다.
interface CreateMarketUseCase {
fun marketAll()
}
@Service
class MarketCreateService(
private val marketSearchService: MarketSearchService,
private val marketRepository: MarketRepository,
) : CreateMarketUseCase {
override fun marketAll() {
marketSearchService.marketAll()
.forEach {
marketRepository.save(
Market.create(
CreateMarket(it.marketPair, it.koreanName, it.englishName)
)
)
}
}
}
MarketController
작업한 코드를 호출하기 위한 API 를 정의한다.
@RestController
class MarketController(
private val createMarketUseCase: CreateMarketUseCase,
) {
@PostMapping("/api/v1/market/all")
fun saveAllMarkets() {
createMarketUseCase.marketAll()
}
}
API 호출하기
위와 작업을 하고 이제 호출을 해보자.
### Upbit 에서 마켓 코드 전체 조회 후 전체 저장
POST http://localhost:8080/api/v1/market/all
성공적으로 데이터가 저장되었는지 확인해보자.
잘 저장된 것을 확인해보았으니 이제 저장된 데이터를 조회해보자.
데이터 조회
JpaRepository 를 구현하는 MarketJpaRepository 를 만들었고, 기본적인 CRUD 기능은 제공된다. MarketPair 로 조회하는 메소드는 없어서 별도로 선언해주었다 (findByMarketPair).
SearchMarketUseCase & MarketSearchService
조회를 위한 유즈케이스와 구현체이다.
interface SearchMarketUseCase {
fun marketAll(): List<MarketDto>
fun marketPair(marketPair: String): MarketDto
}
@Service
class MarketSearchService(
private val upbitMarketClient: UpbitMarketClient,
private val marketRepository: MarketRepository,
private val marketDtoConverter: MarketDtoConverter,
) : SearchMarketUseCase {
override fun marketAll(): List<MarketDto> {
return upbitMarketClient.marketAll().map { marketDtoConverter.convert(it) }
}
override fun marketPair(marketPair: String): MarketDto {
return marketRepository.findByMarketPair(marketPair)
.orElseThrow { throw IllegalArgumentException("MarketPair does not exist") }
.let { marketDtoConverter.convert(it) }
}
}
API 호출하기
이를 호출하는 API 를 만들고 호출을 해보자.
### 마켓 코드 단건 조회 (DB)
GET http://localhost:8080/api/v1/market/KRW-BTC
데이터를 잘 가져온 것을 확인할 수 있다.
다음 시간에는 QueryDSL을 적용해보도록 하겠습니다.
'스프링 > 만들면서 배우는 실무 백엔드 개발' 카테고리의 다른 글
10. ArchUnit 으로 아키텍쳐 검사하기 (0) | 2023.04.05 |
---|---|
9. 단위 테스트 작성하기 (feat. JUnit5 + MockK) (0) | 2023.04.02 |
7. 스프링부트에서 H2 데이터베이스 연결하기 (0) | 2023.03.29 |
6. buildSrc 기반으로 라이브러리 버전 관리하기 (0) | 2023.03.27 |
5. Upbit API 연결하기 (0) | 2023.03.22 |
댓글