본문 바로가기
Java & Kotlin/Spring

Start Spring Data MongoDB - 4. Application 코드 작성

by devson 2021. 9. 4.

개발을 위한 MongoDB 설정과 Spring Data MongoDB 설정을 마쳤으니 본격적으로 Application 코드를 작성해보도록 하겠다.

(컨텐츠에 대한 데이터를 저정하는 애플리케이션을 기반으로 코드를 작성하였다.)

 

(관련 코드는 여기에서 확인 가능하다)

 


 

BaseDocumentEntity

먼저 Entity id와 생성일자, 수정일자를 갖는 BaseDocumentEntity 기반 클래스를 생성해보자.

import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.Id
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.mongodb.core.mapping.Field
import org.springframework.data.mongodb.core.mapping.FieldType
import java.time.LocalDateTime

abstract class BaseDocumentEntity {
    @Id
    @Field("_id", targetType = FieldType.OBJECT_ID)
    var id: String? = null
        private set

    @Field("created_at")
    @CreatedDate
    var createdAt: LocalDateTime = LocalDateTime.now()
        private set

    @Field("updated_at")
    @LastModifiedDate
    var updatedAt: LocalDateTime = LocalDateTime.now()
        private set
}

 

MongoDB는 기본적으로 ID(_id)를 생성해할 때 ObjectId 타입으로 생성을 한다.

BaseDocumentEntityid처럼 String을 ID로 사용할 경우,

@FieldtargetType 프로퍼티를 FieldType.OBJECT_ID로 설정해줘야 애플리케이션에서 String과 MongoDB에서 ObjectID를 제대로 매핑해줄 수 있다.

 

Content

BaseDocumentEntity를 만들었으니 이를 사용하여 Document로 저장할 entity를 만들자.

특정 컨텐츠의 정보를 담기 위한 Content 클래스이다.

import com.tistory.devs0n.springmongo.common.BaseDocumentEntity
import org.springframework.data.annotation.PersistenceConstructor
import org.springframework.data.mongodb.core.mapping.Document
import org.springframework.data.mongodb.core.mapping.Field

@Document(collection = "contents")
class Content : BaseDocumentEntity {
    @Field("type")
    val type: ContentType

    @Field("information")
    val information: ContentInformation

    @PersistenceConstructor
    private constructor(type: ContentType, information: ContentInformation) {
        this.type = type
        this.information = information
    }

    constructor(type: ContentType, title: String, description: String) :
            this(type, ContentInformation(title, description))

    fun informationString(): String = "Content(${this.id}) - ${this.type}, ${this.information}"
}

enum class ContentType {
    VIDEO,
    WEBTOON,
    NOVEL
}

data class ContentInformation(
    @Field("title")
    val title: String,

    @Field("description")
    val description: String,
)

 

여기서 @PersistenceConstructor 는 constructor가 여러 개가 있을 때 Spring Data에서 어떤 것을 사용할 지에 대해 명시적으로 알려주기 위한 어노테이션이다.

지금 예제와 같이 ContentInformation VO를 직접 받아 Content를 생성하는 방식이 아니라,

ContentInformation VO 생성할 수 있는 값을 파라미터로 받아 처리하고 싶은 경우 위와 같이 처리할 수 있다.

 

만약 위 코드에서 private constructor를 제거한다면 조회 시 아래와 같은 오류를 확인할 수 있다.

 

또한 enum field를 사용 시 JPA와 다르게 @Enumerated를 사용할 필요가 없이 그냥 사용하면 해당 enum의 String 그대로 저장된다.

MongoDB에 저장된 데이터는 가장 마지막에 확인하도록 하자. 

 

ContentRepository

Content entity를 조회하기 위해 사용되는 Repository이다.

Spring Data JPA의 JpaRepository 처럼 MongoRepository를 제공하며 API 사용 방식도 동일하다.

import org.springframework.data.mongodb.repository.MongoRepository

interface ContentRepository : MongoRepository<Content, String> {
    fun findAllByType(type: ContentType): List<Content>
}

 

ContentService

간단하게 Content entity를 저장할 수 있는 Service 이다.

import com.tistory.devs0n.springmongo.content.domain.Content
import com.tistory.devs0n.springmongo.content.domain.ContentRepository
import com.tistory.devs0n.springmongo.content.domain.ContentType
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
class ContentService(
    private val contentRepository: ContentRepository
) {
    @Transactional
    fun createContent(type: ContentType, title: String, description: String, throws: Boolean = false): Content {
        val content = this.contentRepository.save(Content(type, title, description))
        LOGGER.info("Content($type, $title, $description) created")

        if (throws) {
            throw RuntimeException("RuntimeException after save")
        }

        return content
    }

    companion object {
        private val LOGGER = LoggerFactory.getLogger(this::class.java)
    }
}

 

이때 Transaction 동작을 확인할 수 있도록 createContent 메서드에 throws 파라미터를 추가하였다.

(이는 다음 포스팅에서 테스트 코드를 통해 확인하도록 하겠다)

 

Main Application

전체적인 준비는 끝났고 Main Application method에 Content를 생성하고 이를 조회하는 간단한 코드를 작성 하여 코드의 동작을 확인해보자.

 

간단하게 ContentService를 통해 Content를 저장하고, ContentRepository를 통해 저장된 Content를 조회해오는 코드이다.

(String 값으로도 특정 Entity 조회가 잘 되는 것을 확인하기 위해 ID로 Content 조회를 추가하였다)

import com.tistory.devs0n.springmongo.content.domain.ContentRepository
import com.tistory.devs0n.springmongo.content.domain.ContentType
import com.tistory.devs0n.springmongo.content.service.ContentService
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class SpringMongoApplication

fun main(args: Array<String>) {
    val ac = runApplication<SpringMongoApplication>(*args)
    val contentService = ac.getBean(ContentService::class.java)
    val contentRepository = ac.getBean(ContentRepository::class.java)

    val contents = listOf(
        contentService.createContent(type = ContentType.NOVEL, title = "Novel 1", description = "first novel"),
        contentService.createContent(type = ContentType.VIDEO, title = "Video 1", description = "first video"),
        contentService.createContent(type = ContentType.VIDEO, title = "Video 2", description = "second video"),
    )

    println("** ID로 Content 조회")
    println(contentRepository.findById(contents[0].id!!).get().informationString())

    println("** 모든 Content 조회")
    contentRepository.findAll()
        .forEach { println(it.informationString()) }
}

 

위 코드를 실행시키면 아래와 같이 콘솔에 로그가 찍히는 것을 확인할 수 있다.

** ID로 Content 조회
2021-09-02 22:46:42.555 DEBUG 66411 --- [           main] o.s.data.mongodb.core.MongoTemplate      : findOne using query: { "id" : "6130d5c215e2a832496491dd"} fields: Document{{}} for class: class com.tistory.devs0n.springmongo.content.domain.Content in collection: contents
2021-09-02 22:46:42.560 DEBUG 66411 --- [           main] o.s.data.mongodb.core.MongoTemplate      : findOne using query: { "_id" : { "$oid" : "6130d5c215e2a832496491dd"}} fields: {} in db.collection: playground.contents
Content(6130d5c215e2a832496491dd) - NOVEL, ContentInformation(title=Novel 1, description=first novel)
** 모든 Content 조회
2021-09-02 22:46:42.605 DEBUG 66411 --- [           main] o.s.data.mongodb.core.MongoTemplate      : find using query: {} fields: Document{{}} for class: class com.tistory.devs0n.springmongo.content.domain.Content in collection: contents
Content(6130d5c215e2a832496491dd) - NOVEL, ContentInformation(title=Novel 1, description=first novel)
Content(6130d5c215e2a832496491de) - VIDEO, ContentInformation(title=Video 1, description=first video)
Content(6130d5c215e2a832496491df) - VIDEO, ContentInformation(title=Video 2, description=second video

 

 

Compass를 통해 MongoDB 데이터를 확인하면 아래와 데이터가 쌓이는 것을 확인할 수 있다.

 


 

다음은 MongoDB를 사용한 Spring Boot Application의 통합테스트는 어떻게 진행하면 될지에 대해 알아보도록 하겠다.

(컨텐츠에 대한 데이터를 저정하는 애플리케이션을 기반으로 코드를 작성하였다.)

댓글