10
17

Ktor ๊ฐ•์˜๋ฅผ ๋”ฐ๋ผ๊ฐ€๊ณ  ์žˆ๋Š”๋ฐ, deprecated ๋œ ๋ถ€๋ถ„์ด ์žˆ์–ด์„œ ๊ฐœ์„ ํ•ด๋ณด๋„๋ก ํ–ˆ๋‹ค.

H2๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋กœ์ปฌ ์„œ๋ฒ„(์ธ๋ฉ”๋ชจ๋ฆฌ DB)๋ผ๊ณ  ๋ณผ์ˆ˜ ์žˆ๊ณ , Exposed๋Š” ์ด๊ฑธ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ผ์ข…์˜ ORM์ด์ž ํ”„๋ ˆ์ž„์›Œํฌ๋ผ๊ณ  ์ดํ•ดํ–ˆ๋‹ค.

fun Application.configureH2() {
    // h2 DB๋ฅผ ์‹คํ–‰ํ•˜๋Š” ํ•จ์ˆ˜
    // JDBC Driver๊ฐ™์€ ํ˜•ํƒœ - ์ธ๋ฉ”๋ชจ๋ฆฌ(์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒ์—์„œ ์ ‘๊ทผ)
    val h2Server = Server.createTcpServer("-tcp", "-tcpAllowOthers", "-tcpPort", "9092")
    environment.monitor.subscribe(ApplicationStarted) { application ->
        h2Server.start()
        application.environment.log.info("H2 server started. ${h2Server.url}")
    }

    environment.monitor.subscribe(ApplicationStopped) { application ->
        h2Server.stop()
        application.environment.log.info("H2 server stopped. ${h2Server.url}")
    }
}

์ด๊ฒŒ h2Server๋ฅผ TCP์—ฐ๊ฒฐ๋กœ ๋ณด๋‚ด๊ณ , Application ์‹คํ–‰ ์‹œ h2์„œ๋ฒ„๊ฐ€ ์‹คํ–‰๋˜๋„๋ก, ์ข…๋ฃŒ์‹œ ๋ฉˆ์ถ”๋„๋ก ์„ค์ •ํ•œ ํ•จ์ˆ˜๋‹ค. ๋ฌธ์ œ๋Š” ์ด ๋ถ€๋ถ„์ด deprecated ๋๋‹ค๋Š” ์ ์ด๋‹ค.

fun Application.configureH2() {
    val h2Server = Server.createTcpServer("-tcp", "-tcpAllowOthers", "-tcpPort", "9092")
    monitor.subscribe(ApplicationStarted) { application ->
        application.environment.log.info("H2 server started. ${h2Server.url}")
    }

    monitor.subscribe(ApplicationStopped) { application ->
        h2Server.stop()
        application.environment.log.info("H2 server stopped. ${h2Server.url}")
    }
}

์•„์ง ์ˆ™๋ จ๋„๊ฐ€ ๋‚ฎ์•„์„œ ์ž์„ธํžˆ๋Š” ๋ชจ๋ฅด๊ฒ ์œผ๋‚˜, ์ธํ„ฐํŽ˜์ด์Šค ์ •๋„๋กœ๋งŒ ๊ตฌ์„ฑ๋˜์–ด์žˆ๋˜ ๊ธฐ์กด ๊ตฌ์กฐ๊ฐ€, coroutineScope๋กœ ๊ฐ์‹ธ์ ธ ์กฐ๊ธˆ ๋” ๊ดœ์ฐฎ๊ฒŒ ๋ฐ”๋€ ๊ฒƒ ๊ฐ™๋‹ค.

๋น„๋™๊ธฐ๋กœ ์ด๋ฒคํŠธ๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ฒŒ ํ•จ์œผ๋กœ ์จ ์ข€ ๋” ํšจ์œจ์ ์ธ ์ œ์–ด๋ฅผ ํ•˜๊ฒŒ ๋˜์ง€์•Š์•˜๋‚˜.. ์ถ”๋ก ํ•ด๋ดค๋‹ค.

### ๊ธฐ์กด `monitor` ๊ตฌ์กฐ

package io.ktor.server.application

public interface ApplicationEnvironment {
    public abstract val classLoader: java.lang.ClassLoader

    public abstract val log: org.slf4j.Logger /* from: io.ktor.util.logging.Logger */

    public abstract val config: io.ktor.server.config.ApplicationConfig

    @kotlin.Deprecated public abstract val monitor: io.ktor.events.Events
}

 

### ํ˜„์žฌ `monitor` ๊ตฌ์กฐ

package io.ktor.server.application

@io.ktor.utils.io.KtorDsl public final class Application internal constructor(environment: io.ktor.server.application.ApplicationEnvironment, developmentMode: kotlin.Boolean, rootPath: kotlin.String, monitor: io.ktor.events.Events, parentCoroutineContext: kotlin.coroutines.CoroutineContext, engineProvider: () -> io.ktor.server.engine.ApplicationEngine) : io.ktor.server.application.ApplicationCallPipeline, kotlinx.coroutines.CoroutineScope {
    public final var rootPath: kotlin.String /* compiled code */

    public final val monitor: io.ktor.events.Events /* compiled code */

    public final val parentCoroutineContext: kotlin.coroutines.CoroutineContext /* compiled code */

    private final val engineProvider: () -> io.ktor.server.engine.ApplicationEngine /* compiled code */

    private final val applicationJob: kotlinx.coroutines.CompletableJob /* compiled code */

    public final val engine: io.ktor.server.engine.ApplicationEngine /* compiled code */
        public final get

    public open val coroutineContext: kotlin.coroutines.CoroutineContext /* compiled code */

    public final fun dispose(): kotlin.Unit { /* compiled code */ }
}

Hikari๋Š” ์›๋ž˜ ์กฐ๊ธˆ ๋“ค์–ด๋ดค์ง€๋งŒ ์ด๋ฒˆ์— ์ข€ ์ œ๋Œ€๋กœ ๋ณด๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

private fun connectDatabase() {
    val config =
        HikariConfig().apply {
            jdbcUrl = "jdbc:h2:mem:cafedb"
            driverClassName = "org.h2.Driver"
            validate()
        }

    val dataSource: DataSource = HikariDataSource(config)
    Database.connect(dataSource)
}

HikariCP๋Š” ๊ณ ์„ฑ๋Šฅ JDBC ์ปค๋„ฅ์…˜ ํ’€๋ง ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐ„์˜ ์—ฐ๊ฒฐ์„ ๊ด€๋ฆฌํ•˜๊ณ , ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—ฐ๊ฒฐ์„ ์š”์ฒญํ•  ๋•Œ ์—ฐ๊ฒฐ์„ ํ’€(pool)๋กœ ๊ด€๋ฆฌํ•˜์—ฌ ์„ฑ๋Šฅ๊ณผ ์•ˆ์ •์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚จ๋‹ค. 

 

HikariCP๋ฅผ ์จ์„œ H2 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์—ฐ๊ฒฐํ•˜๊ณ , HikariConfig ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ JDBC URL๊ณผ ๋“œ๋ผ์ด๋ฒ„ ์ •๋ณด๋ฅผ ์„ค์ •ํ•œ๋‹ค. ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ HikariDataSource๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ๋œ๋‹ค. Database.connect๋ฅผ ํ†ตํ•ด Exposed ORM์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์—ฐ๊ฒฐํ•œ๋‹ค. ๊ทธ ๋’ค๋Š” JDBC URL์„ ๋งž์ถฐ์ฃผ๋Š”๊ฒŒ ๋์ด๋‹ค.

 

๋‹ค์Œ์€ custom column type ๋ฌธ์ œ๋‹ค. ๊ฐ•์˜์—์„œ๋Š” VARCHAR๋ฅผ ์—ฌ๋Ÿฌ๊ฐœ ๋‹ด์„ ์ˆ˜ ์žˆ๋„๋ก ํ•œ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด๋’€๋Š”๋ฐ, ๋ฒ„์ „์ด ์˜ฌ๋ผ๊ฐ€๋ฉด์„œ ํ˜ธํ™˜๋˜์ง€์•Š๋Š”๋‹ค. 

ColumnType์˜ ํ˜•ํƒœ๊ฐ€ ๋ฐ”๋€Œ์–ด์„œ ๊ทธ๋Ÿฐ ๊ฒƒ ๊ฐ™๋‹ค. ์ข€ ๋” type safe ํ•˜๊ฒŒ ๋ฐ”๋€ ๊ฑฐ๋‹ˆ๊นŒ ํ”„๋ ˆ์ž„์›Œํฌ ์ด์šฉ ์ƒ์œผ๋กœ๋Š” ์ข‹์ง€๋งŒ, ๋‚˜ํ•œํ…Œ๋Š” ์ข‹์ง€์•Š๋‹ค.

public abstract class ColumnType public constructor(nullable: kotlin.Boolean = COMPILED_CODE) : org.jetbrains.exposed.sql.IColumnType {
    public open var nullable: kotlin.Boolean /* compiled code */

    public open operator fun equals(other: kotlin.Any?): kotlin.Boolean { /* compiled code */ }

    public open fun hashCode(): kotlin.Int { /* compiled code */ }

    public open fun toString(): kotlin.String { /* compiled code */ }
}

// ๋ณ€๊ฒฝ ํ›„ -> T๊ฐ€ ์ƒ๊น€
public abstract class ColumnType<T> public constructor(nullable: kotlin.Boolean = COMPILED_CODE) : org.jetbrains.exposed.sql.IColumnType<T> {
    public open var nullable: kotlin.Boolean /* compiled code */

    public open fun toString(): kotlin.String { /* compiled code */ }

    public open operator fun equals(other: kotlin.Any?): kotlin.Boolean { /* compiled code */ }

    public open fun hashCode(): kotlin.Int { /* compiled code */ }
}

์ผ๋‹จ ๊ฐ•์˜๋ฅผ ์ด์–ด๋‚˜๊ฐ€์•ผํ•˜๋‹ˆ๊นŒ, Kotlin์—์„œ ๊ฐ€์žฅ ์ตœ์ƒ์œ„ ํƒ€์ž…์ธ Any๋ฅผ ๋„ฃ์–ด์„œ ํ•ด๊ฒฐํ–ˆ๋‹ค.

import com.example.shared.CafeUserRole
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.ColumnType
import org.jetbrains.exposed.sql.Table

class EnumListColumnType<T : Enum<T>>(
    private val enumClass: Class<T>,
    private val varcharLength: Int,
) : ColumnType<Any>() {
    ...
}

@Suppress("UNCHECKED_CAST")
fun <T : Enum<T>> Table.enumList(name: String, enumClass: Class<T>, varcharLength: Int): Column<List<CafeUserRole>> =
    registerColumn(name, EnumListColumnType(enumClass, varcharLength)) as Column<List<CafeUserRole>>

 

dbSchema๋ฅผ ์ œ์–ดํ•˜๋Š” ํ•จ์ˆ˜๋„ ๊ฐ„๋‹จํ•˜๋‹ค. transaction ๋ธ”๋ก ์•ˆ์—์„œ SchemaUtils.create๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ…Œ์ด๋ธ”๋“ค์„ ์ƒ์„ฑํ•˜๊ณ  addLogger(StdOutSqlLogger)๋ฅผ ๊ฐ™์ด ์จ์„œ SQL ๋กœ๊ทธ๋ฅผ ํ‘œ์ค€ ์ถœ๋ ฅ์— ๊ธฐ๋กํ•˜๋„๋ก ํ–ˆ๋‹ค.

transaction {
    addLogger(StdOutSqlLogger)

    SchemaUtils.create(
        CafeMenuTable,
        CafeUserTable,
        CafeOrderTable
    )
}

๊ทธ๋ƒฅ jdbc์˜€์œผ๋ฉด DriverManager์— getConnection ๊ฑธ๊ณ  ๋ญ ๋‹ค๋ฅธ๊ฑฐ ๋‹ค ํ–ˆ์–ด์•ผ ํ–ˆ์„ ๊ฒƒ์ด๋‹ค.

๋กœ๊ทธ ๋ ˆ๋ฒจ์€ resources ํŒจํ‚ค์ง€ ์•ˆ์˜ `logback.xml`์—์„œ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, root level๊ธฐ๋ณธ์ด trace์ด๊ณ  ์Šคํ”„๋ง์—์„œ ํ•  ๋•Œ ์ฒ˜๋Ÿผ INFO๋กœ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
    <logger name="org.eclipse.jetty" level="INFO"/>
    <logger name="io.netty" level="INFO"/>
</configuration>

 

IntelliJ Ultimate๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, Database ํˆด์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ํ…Œ์ด๋ธ”์ด ์‹œ๊ฐํ™” ๋œ ์ฑ„๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๊ฑด๋ฐ, ์ด๋Ÿฌ๋ ค๋ฉด remote๋กœ ์„ค์ •๋œ ์šฐ๋ฆฌ์˜ localhost ์ธ๋ฉ”๋ชจ๋ฆฌ h2 ์„œ๋ฒ„์— ์ ‘๊ทผํ•ด์•ผ๋œ๋‹ค.

val h2Server = Server.createTcpServer("-tcp", "-tcpAllowOthers", "-tcpPort", "9092")

๊ธฐ๋ณธ h2ํฌํŠธ๊ฐ€ 9092๊ฐ™๋‹ค. ๊ธฐ๋ณธ tcp๋Š” ์™ธ๋ถ€ ์ ‘์†์ด ํ—ˆ์šฉ๋˜์ง€์•Š๋Š”๋ฐ, tcpAllowOthers ์˜ต์…˜์„ ๋‹ฌ์•„์„œ remote์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค€๋‹ค.

HikariConfig์—์„œ ์„ค์ •ํ•ด๋‘” jdbcUrl์„ ์ž…๋ ฅํ•ด์„œ H2 Datasource์— ์ ‘๊ทผํ•  ๊ฒƒ์ด๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ธ๋ฉ”๋ชจ๋ฆฌ์— ์ ‘๊ทผํ•œ๋‹ค. ๊ทผ๋ฐ H2์˜ remote ์˜ต์…˜๋ง๊ณ  inmemory ์˜ต์…˜๋„ ์žˆ๋‹ค. ๊ฑฐ๊ธฐ์„œ ์ ‘๊ทผํ•ด๋ฒ„๋ฆฌ๋ฉด ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  H2 ์„œ๋ฒ„์™€๋Š” ๋‹ค๋ฅธ url์„ ๊ฐ€์ ธ๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ์ง€๊ธˆ ์Šคํ‚ค๋งˆ๋กœ ๋งŒ๋“ค์–ด๋‘” ํ…Œ์ด๋ธ”๋“ค์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค.

 

 

๋„์›€์ด ๋๋‹ค๋ฉด ๋Œ“๊ธ€์ด๋‚˜ ๊ณต๊ฐ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ๊ฐ€์ฃผ์„ธ์š”! ๋กœ๊ทธ์ธ ์•ˆํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค ^_^

 

๋ฐ˜์‘ํ˜•
COMMENT