๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ”‹ | ๊ธฐํƒ€ ๊ธฐ์ˆ /๐Ÿ–ฅ๏ธ | Ktor

[Kotlin, Ktor] ktor๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—ฐ๋™ํ•˜๊ธฐ - 1(3)

by immgga 2023. 6. 15.

 

 

์ง€๋‚œ ํฌ์ŠคํŒ…

https://rkdrkd-history.tistory.com/53

 

[Kotlin, Ktor] ktor๋กœ http api ๋งŒ๋“ค๊ธฐ(2)

์ง€๋‚œ ํฌ์ŠคํŒ… https://rkdrkd-history.tistory.com/52 [Kotlin, Ktor] ktor ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ํ•˜๊ธฐ(1) ์นœ๊ตฌ๋“ค์ด๋ž‘ ํ•จ๊ป˜ ํ•˜๋Š” ํ”„๋กœ์ ํŠธ์—์„œ ์„œ๋ฒ„ ๋ถ€๋ถ„์„ ktor๋กœ ๋ฐ”๊ธฐ๋กœ ๊ฒฐ์ • ๋‚ฌ๋‹ค. ์ฒ˜์Œ ๋“ค์–ด๋ณธ ktor์ด์ง€๋งŒ, ๊ฒ€์ƒ‰์„ ์ข€ ํ•ด๋ณด

rkdrkd-history.tistory.com

์ง€๋‚œ ํฌ์ŠคํŒ…์—์„œ๋Š” ktor์—์„œ http api๋ฅผ ๋งŒ๋“ค์–ด๋ณด๋Š” ๊ฒƒ์„ ํ–ˆ๋‹ค.

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ktor์— database๋ฅผ ์‚ฌ์šฉํ•ด data๋ฅผ ์ €์žฅํ•ด ๋ณด๊ฒ ๋‹ค.


์ฝ”๋“œ๋Š” ์ง€๋‚œ ํฌ์ŠคํŒ…์„ ๋ฐ”ํƒ•์œผ๋กœ ์žฌ๊ณต ํ•  ๊ฒƒ์ด๋‹ˆ ์ด์ „ ๊ธ€์„ ๋ณด๊ณ  ์˜ค๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค.

 

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋‹ค์–‘ํ•˜์ง€๋งŒ

์ด ํฌ์ŠคํŒ…์— ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” Exposed์™€ h2database๋ฅผ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค.

https://github.com/JetBrains/Exposed

 

GitHub - JetBrains/Exposed: Kotlin SQL Framework

Kotlin SQL Framework. Contribute to JetBrains/Exposed development by creating an account on GitHub.

github.com

https://github.com/h2 database/h2database

 

GitHub - h2database/h2database: H2 is an embeddable RDBMS written in Java.

H2 is an embeddable RDBMS written in Java. Contribute to h2database/h2database development by creating an account on GitHub.

github.com

 

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€

gradle.properties

exposed_version=0.41.1
h2_version=2.1.214

 

build.gradle.kts

val exposed_version: String by project
val h2_version: String by project

. . .

dependencies {
    // exposed
    implementation("org.jetbrains.exposed:exposed-core:$exposed_version")
    implementation("org.jetbrains.exposed:exposed-dao:$exposed_version")
    implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version")

    // h2database
    implementation("com.h2database:h2:$h2_version")
    
    . . .
}

esposed, h2database ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

 

Table ์ƒ์„ฑ

Customer.kt

import org.jetbrains.exposed.sql.Table

object Customer: Table() {
    val id = integer("id").autoIncrement()
    val firstName = varchar("first_name", 10)
    val lastName = varchar("last_name", 10)

    override val primaryKey: PrimaryKey = PrimaryKey(id)
}

์ง€๋‚œ ์˜ˆ์ œ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ table์„ ์ƒ์„ฑํ•ด ์ฃผ์—ˆ๋‹ค.

  • id: autoIncrement()๋กœ ์ž๋™์œผ๋กœ ๊ฐ’์„ ํ• ๋‹นํ•œ๋‹ค.
  • firstname๊ณผ lastname์˜ ํ•„๋“œ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์—ˆ๋‹ค.
  • id๋ฅผ primary key๋กœ ์„ค์ •ํ–ˆ๋‹ค.

 

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์—ฐ๊ฒฐ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์—ฐ๊ฒฐํ•˜๋ ค๋ฉด ๋จผ์ € h2 console์„ ์„ค์น˜ํ•ด์•ผ ํ•œ๋‹ค.https://h2database.com/html/main.html

 

H2 Database Engine

H2 Database Engine Welcome to H2, the Java SQL database. The main features of H2 are: Very fast, open source, JDBC API Embedded and server modes; in-memory databases Browser based Console application Small footprint: around 2.5 MB jar file size     Supp

h2database.com

h2 database ๊ณต์‹ ์‚ฌ์ดํŠธ์—์„œ ์ž์‹ ์˜ ์ปดํ“จํ„ฐ ์šด์˜ ์ฒด์ œ์— ๋งž๋Š” database engine์„ ์„ค์น˜ํ•œ๋‹ค.

 

h2 database๋ฅผ ์„ค์น˜ํ•œ ํ›„ ํŒŒ์ผ javaw์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค ๋ผ๋Š” ์—๋Ÿฌ๊ฐ€ ๋œฌ๋‹ค๋ฉดjava๊ฐ€ ์„ค์น˜๊ฐ€ ์•ˆ ๋ผ์žˆ๋‹ค๋Š” ๋œป์ด๋ฏ€๋กœ ์•„๋ž˜์˜ ์‚ฌ์ดํŠธ์— ๋“ค์–ด๊ฐ€์„œ ์ž๋ฐ”๋ฅผ ์„ค์น˜ํ•ด ์ค€๋‹ค.

https://www.java.com/ko/download/

 

Download Java for Linux

Linux RPM ํŒŒ์ผ ํฌ๊ธฐ: 98.33 MB ์ง€์นจ Java๋ฅผ ์„ค์น˜ํ•œ ํ›„์—๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ Java๋ฅผ ์‚ฌ์šฉ์œผ๋กœ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

www.java.com

 

java๋ฅผ ์„ค์น˜ํ•˜๊ณ  ๋‚˜๋ฉด jdk๋„ ๊ฐ™์ด ์ƒ์„ฑ๋˜๋Š”๋ฐ ๊ทธ๊ฒƒ์„ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๋“ฑ๋กํ•ด ์ฃผ๋Š” ์ž‘์—…์„ ํ•ด์•ผ ํ•œ๋‹ค.

์œˆ๋„ + R ๋ˆ„๋ฅธ ํ›„ ๋‚˜์˜ค๋Š” ์ฐฝ์— sysdm.cpl ,3 ์„ ์ž…๋ ฅํ•œ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ํƒญ์— ๋“ค์–ด๊ฐ€์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜๋ฉด ๋˜๋Š”๋ฐ ์ž์„ธํ•œ ์„ค๋ช…์€ ์•„๋ž˜์˜ ๋ธ”๋กœ๊ทธ๋ฅผ ์ฐธ๊ณ ํ•˜๊ธธ ๋ฐ”๋ž€๋‹ค.

https://pingfanzhilu.tistory.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%84%A4%EC%A0%95-%EB%A9%94%EC%9D%B4%EB%B8%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EC%9C%BC%EB%A1%9C-%EC%84%A4%EC%A0%95-1

 

[Spring Project] - ์Šคํ”„๋ง ํ”„๋กœ์ ํŠธ ๋ฉ”์ด๋ธ(Maven) ๊ธฐ๋ฐ˜ Spring ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ(์ž๋ฐ” Java JDK ์„ค์น˜ ๋ฐ ํ™˜๊ฒฝ

#์Šคํ”„๋ง ํ”„๋กœ์ ํŠธ ๋ฉ”์ด๋ธ(Maven) ๊ธฐ๋ฐ˜ Spring ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ(์ž๋ฐ” Java JDK ์„ค์น˜ ๋ฐ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • ๋ฐฉ๋ฒ•) #jdk1.8 ๋‹ค์šด๋กœ๋“œ ์‚ฌ์ดํŠธ ๋งํฌ์ž…๋‹ˆ๋‹ค. https://www.oracle.com/kr/java/technologies/javase/javase8-archive-downloads.

pingfanzhilu.tistory.com

 

์—ฌ๊ธฐ๊นŒ์ง€ ์™„๋ฃŒํ•œ ํ›„ ์œˆ๋„์šฐ ํ‚ค๋ฅผ ๋ˆŒ๋Ÿฌ h2 console์„ ์‹คํ–‰ํ•˜๋ฉด...

 

๋ธŒ๋ผ์šฐ์ €์— ์ด๋Ÿฐ ์ฐฝ์ด ๋œจ๋ฉด ์„ฑ๊ณต์ด๋‹ค.

๋งŽ์ด ์™”๋Š”๋ฐ ์•„์ง ๋๋‚˜์ง€ ์•Š์•˜๋‹ค.

์ด์ œ๋Š” ktor๋กœ ๋„˜์–ด์™€์„œ database factory๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

package com.example.database

import com.example.models.Customer
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction

object DatabaseFactory {
    fun init() {
        val driverClassName = "org.h2.Driver"
        val jdbcURL = "jdbc:h2:file:./build/db"
        val database = Database.connect(jdbcURL, driverClassName)

        transaction(database) {
            SchemaUtils.create(Customer)
        }
    }
}

 ์ด ์ฝ”๋“œ์—์„œ ์šฐ๋ฆฌ๊ฐ€ ๋ฐ”๊ฟ”์•ผ ํ•  ๋ถ€๋ถ„์€ jdbcURL ์ชฝ์ธ๋ฐ

 

h2 console์—์„œ jdbc url์ด๋ผ๋Š” ์ž…๋ ฅ ์ฐฝ์ด ์žˆ๋Š”๋ฐ

jdbc:h2:~/์›ํ•˜๋Š” ์ด๋ฆ„ ์ž…๋ ฅ

์ด๋ ‡๊ฒŒ database์˜ ์ด๋ฆ„์„ ์„ค์ •ํ•œ๋‹ค.

์ด๋ฆ„์„ ์„ค์ •ํ•˜๊ณ  '์—ฐ๊ฒฐ' ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด

์ˆจ๊ฒจ์ง„ ์•„์ด์ฝ˜์—์„œ ์™ผ์ชฝ ์œ„์— ์žˆ๋Š” icon์„ ํด๋ฆญํ•ด์„œ h2 ์ฝ˜์†”์„ ์žฌ์‹คํ–‰ํ•œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ™”๋ฉด์ด ๋‚˜์˜ค๋ฉด ์„ฑ๊ณต์ด๋‹ค.

์ € ํ™”๋ฉด์ด ๋‚˜์˜ค๋ฉด

 

์‚ฌ์šฉ์ž ํด๋”์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒŒ์ผ์ด ์ƒ์„ฑ๋œ๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋งŒ๋“ค์—ˆ์œผ๋‹ˆ ktor์—์„œ๋Š”

val jdbcURL = "jdbc:h2:~/test"

jdbcURL์„ ์ž์‹ ์ด ์ƒ์„ฑํ•œ database ์ด๋ฆ„์œผ๋กœ ๋ฐ”๊พผ๋‹ค.

 

์ฟผ๋ฆฌ ์ƒ์„ฑ

databaseFactory ํŒŒ์ผ ์•ˆ์— ์ฟผ๋ฆฌ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ ๋‹ค.

suspend fun <T> dbQuery(block: suspend () -> T): T =
        newSuspendedTransaction(Dispatchers.IO) { block() }

 

๊ทธ๋ฆฌ๊ณ  dao ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์ค€๋‹ค.

DAOFacade.kt

package com.example.database

import com.example.models.Customers

interface DAOFacade {
    suspend fun allCustomers(): List<Customers>
    suspend fun customer(): Customers?
    suspend fun addNewCustomer(firstName: String, lastName: String, email: String): Customers?
    suspend fun editCustomer(id: Int, firstName: String, lastName: String, email: String): Boolean
    suspend fun deleteCustomer(id: Int): Boolean
}

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ฆฌ์— ์‚ฌ์šฉ๋  ํ•จ์ˆ˜๋“ค์„ ์ƒ์„ฑํ•ด ์ค€๋‹ค.

 

์ด์ œ DAOFacade ๊ตฌํ˜„์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ๊ธฐ๋Šฅ์„ ๋‹ด์•„์ฃผ๋„๋ก ํ•˜๊ฒ ๋‹ค.

package com.example.database

import com.example.database.DatabaseFactory.dbQuery
import com.example.models.Customer
import com.example.models.Customers
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq

class DAOFacadeImpl: DAOFacade {
    private fun resultRowToCustomer(row: ResultRow) = Customers(
        id = row[Customer.id],
        firstName = row[Customer.firstName],
        lastName = row[Customer.lastName],
        email = row[Customer.email]
    )

    override suspend fun allCustomers(): List<Customers> = dbQuery {
        Customer.selectAll().map(::resultRowToCustomer)
    }

    override suspend fun customer(id: Int): Customers? = dbQuery {
        Customer
            .select { Customer.id eq id }
            .map(::resultRowToCustomer)
            .singleOrNull()
    }

    override suspend fun addNewCustomer(firstName: String, lastName: String, email: String): Customers? = dbQuery {
        val insertStatement = Customer.insert {
            it[Customer.firstName] = firstName
            it[Customer.lastName] = lastName
            it[Customer.email] = email
        }
        insertStatement.resultedValues?.singleOrNull()?.let(::resultRowToCustomer)
    }

    override suspend fun editCustomer(id: Int, firstName: String, lastName: String, email: String): Boolean = dbQuery {
        Customer.update({ Customer.id eq id }) {
            it[Customer.firstName] = firstName
            it[Customer.lastName] = lastName
            it[Customer.email] = email
        } > 0
    }

    override suspend fun deleteCustomer(id: Int): Boolean = dbQuery {
        Customer.deleteWhere { Customer.id eq id } > 0
    }
}

์ด์ œ ๊ฐ ํ•จ์ˆ˜๋“ค์˜ ์—ญํ• ์„ ์•Œ์•„๋ณด์ž

  • resultToRowCustomer ํ•จ์ˆ˜๋Š” ์ผ๋‹จ ์ž˜ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ ๊ฐ customer item์— row์„ ๋„ฃ์–ด์ฃผ๋Š” ๊ฒƒ์œผ๋กœ ์ดํ•ดํ–ˆ๋‹ค.
  • allCustomers ํ•จ์ˆ˜์—์„œ๋Š” db์— ์ €์žฅ๋œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ฒŒ(selectAll()) ๋งŒ๋“ค์—ˆ๋‹ค.
  • customer ํ•จ์ˆ˜๋Š” ํŠน์ • id ๊ฐ’์„ ์ฐพ์•„์„œ parameter์˜ id์™€ customer์˜ id๊ฐ€ ๊ฐ™์€์ง€(eq) ํ™•์ธํ•˜๋Š” ์ž‘์—…์„ ๊ฑฐ์ณ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ฒŒ ํ–ˆ๋‹ค.
  • addNewCustomer ํ•จ์ˆ˜์—์„œ๋Š” insert๋ฅผ ์‚ฌ์šฉํ•ด insertState(it)์— parameter์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋„ฃ์–ด์ฃผ๋Š” ๋กœ์ง์œผ๋กœ ๊ตฌ์„ฑํ–ˆ๋‹ค.
  • editCustomer ํ•จ์ˆ˜๋Š” update๋ฅผ ์‚ฌ์šฉํ•ด customer ํ•จ์ˆ˜์™€ ๊ฐ™์ด customer์˜ id๊ฐ€ ๊ฐ™์€์ง€ ํ™•์ธํ•˜๋Š” ์ž‘์—…์„ ๊ฑฐ์นœ ํ›„ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์„œ update ํ•˜๋Š” ๋กœ์ง์ด๋‹ค.
  • deleteCustomer ํ•จ์ˆ˜๋Š” deleteWhere๋ฅผ ์‚ฌ์šฉํ•ด id๊ฐ€ ๊ฐ™์€๊ฐ€์˜ ์กฐ๊ฑด์„ ์ƒ์„ฑํ•ด true์ธ ๊ฒฝ์šฐ๋งŒ ๊ฐ’์„ ์‚ญ์ œ์‹œํ‚ค๋Š” ๋กœ์ง์„ ๋งŒ๋“ค์—ˆ๋‹ค.

์ง€๊ธˆ ๊ธ€์„ ์ ๋‹ค๊ฐ€ ์–‘์ด ๋งŽ์•„์ ธ์„œ ์ผ๋‹จ ์—ฌ๊ธฐ๊นŒ์ง€ ํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค.

๋‹ค์Œ ํฌ์ŠคํŒ…์— ์œ„์— ๊ธ€์—์„œ ํ–ˆ๋˜ ์ฝ”๋“œ๋“ค์„ ์ด์šฉํ•ด ๊ธฐ์กด์˜ api๋ฅผ ์ˆ˜์ •ํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

728x90