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์ ๊ฐ์ ธ๊ฐ๊ธฐ ๋๋ฌธ์ ์ง๊ธ ์คํค๋ง๋ก ๋ง๋ค์ด๋ ํ ์ด๋ธ๋ค์ ์ ๊ทผํ ์ ์๋ค.
๋์์ด ๋๋ค๋ฉด ๋๊ธ์ด๋ ๊ณต๊ฐ ๋ฒํผ ํ ๋ฒ์ฉ ๋๋ฅด๊ณ ๊ฐ์ฃผ์ธ์! ๋ก๊ทธ์ธ ์ํด๋ ๋ฉ๋๋ค ^_^
'Ktor๐ฅ๏ธ > ์ฝ์งโ๏ธ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Android]Ktor - Multipart ์ฌ์ฉํ๊ธฐ(feat. ํด๋ฆฐ ์ํคํ ์ณ) (2) | 2024.10.27 |
---|---|
Serializable ์ค๋ฅ(LocalDateTime, kotlin.serialization) (0) | 2024.10.16 |