프레임워크(Framework)/Ktor

[Ktor] Kotlin + Ktor + Ktorm 환경에서 MySQL 연동하기

잇트루 2023. 7. 25. 22:50
반응형

Ktorm

자바 스프링 또는 코틀린 스프링에서 사용하는 ORM인 JPA가 있다면, ktor 프레임워크에서 사용할 수 있는 ORM은 Exposed 또는 Ktorm이 있다.

이 중에서도 Ktorm은 Kotlin에서 사용할 수 있는 JDBC 기반의 ORM 프레임워크다. Kotlin 프로젝트에서 데이터베이스 운영에 대하여 중복 코드를 줄이고 코틀린 코드로 SQL 쿼리를 작성할 수 있는 DSL 기능과 시퀀스 API를 제공한다.

Ktorm 공식 문서 : https://www.ktorm.org/

 

Ktorm | Kotlin ORM lib with SQL DSL

What’s Ktorm? Ktorm is a lightweight and efficient ORM Framework for Kotlin directly based on pure JDBC. It provides strong-typed and flexible SQL DSL and convenient sequence APIs to reduce our duplicated effort on database operations. All the SQL statem

www.ktorm.org

 

 

프로젝트 생성하기

Ktor Project Generator(https://start.ktor.io/) 또는 IntelliJ IDEA Ultimate를 통해 ktor 프로젝트를 생성할 수 있다. Ktor 프로젝트 생성 방법은 아래 게시글에서 자세히 다룬다.

https://ittrue.tistory.com/439

 

[Ktor] Ktor Framework 소개 및 프로젝트 생성하기

Ktor Ktor 프레임워크는 Kotlin과 IntelliJ IDEA 개발한 것으로 유명한 JetBrains에서 개발한 연결된 애플리케이션(Connected application)을 쉽게 구축할 수 있는 프레임워크이다. 개발자들 사이에서는 케이터

ittrue.tistory.com

 

 

build.gradle.kts

Ktor 프레임워크와 Ktorm ORM을 사용하여 MySQL에 연동하기 위한 의존성이다.

dependencies {
    // Ktor Framework
    implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
    implementation("io.ktor:ktor-server-netty-jvm:$ktor_version")
    implementation("io.ktor:ktor-server-content-negotiation:$ktor_version")
    implementation("io.ktor:ktor-serialization-jackson:$ktor_version")
    implementation("io.ktor:ktor-server-config-yaml:$ktor_version")

    // Logging
    implementation("ch.qos.logback:logback-classic:$logback_version")

    // Ktorm ORM
    implementation("org.ktorm:ktorm-core:$ktorm_version")
    implementation("org.ktorm:ktorm-jackson:$ktorm_version")
    implementation("org.ktorm:ktorm-support-mysql:$ktorm_version")

    // MySQL Connector and HikariCP
    implementation("mysql:mysql-connector-java:$mysql_version")
    implementation("com.zaxxer:HikariCP:$hikari_version")

    testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
}
  • Ktor Framework 의존성은 core, netty 서버, 직렬화 콘텐츠(negotitation, jackson), yaml 파일 등의 플러그인을 사용한다.
  • logging은 기본적으로 ktor 프로젝트를 생성하면 logback이 추가된다.
  • Ktorm ORM 의존성은 core, jackson, mysql을 추가한다.
  • MySQL 데이터베이스에 접속하기 위한 Connector와 고성능 JDBC 커넥션 풀 라이브러리인 HikariCP를 추가한다.

 

 

데이터베이스 연동

Ktorm을 사용하기 위해서는 데이터베이스에 연결해야 한다. Ktorm의 Database 클래스는 데이터베이스와 상호 작용하는 핵심 클래스로 데이터베이스와의 연결, 쿼리 실행, 트랜잭션 제어 등을 담당한다.

 

데이터베이스 연결

Database 클래스는 데이터베이스와의 연결을 위한 connect() 함수를 제공한다. connect() 함수를 통해 데이터베이스 url, driver, username, password 등을 포함하여 연결할 수 있다.

import org.ktorm.database.Database

val database = Database.connect(
    url = "jdbc:mysql://localhost:3306/ktorm",
    driver = "com.mysql.jdbc.Driver",
    user = "user",
    password = "password"
)
  • 위 코드에서 선언한 Database 객체를 통해 데이터베이스 접근 및 제어를 할 수 있다.
  • 이 경우 쿼리를 실행과 같이 필요할 때마다 내부적으로 DriverManager.getConnection() 함수를 호출하여 필요할 때마다 DB Connection을 얻어 데이터베이스에 접근한 후 유용하지 않으면 Connection을 닫는다.
  • 이러한 방법은 Connection을 재사용하지 않고 필요할 때마다 생성하기 때문에 성능 저하가 일어날 수 있다.
  • 따라서 Connection Pool 프레임워크 적용하여 사용하는 것이 좋다.

 

HikariCP 적용

HikariCP는 가볍고 빠른 JDBC 커넥션 풀 프레임워크로 커넥션 풀을 관리한다. 기존 Connection을 재사용하여 성능 향상에 도움을 준다.

다음과 같이 DataSource를 사용하여 데이터베이스에 연결할 수 있다.

import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import org.ktorm.database.Database

val database = Database.connect(
    dataSource = HikariDataSource(
        HikariConfig().apply {
            jdbcUrl = "jdbc:mysql://localhost:3306/ktorm",
            driverClassName = "com.mysql.jdbc.Driver",
            username = "user",
            password = "password"
        }
    )
)
  • Database 클래스의 connect() 함수는 여러 가지 방식의 데이터베이스 연결을 위해 오버로딩된 connect() 함수를 제공한다.
  • 커넥션 풀이 적용된 DataSource 객체를 매개 변수로 사용하여 쉽게 데이터베이스에 연결이 가능하다.

 

kotlin object로 데이터베이스 연결 정의

보통 서비스는 여러 곳에서 데이터베이스를 연결하여 사용하기 때문에 object 키워드로 싱클톤 클래스로 정의하여 사용하는 것이 효율적이다.

object DatabaseConnection {
    val database = Database.connect(
        dataSource = HikariDataSource(
            HikariConfig().apply {
                jdbcUrl = "jdbc:mysql://localhost:3306/ktorm",
                driverClassName = "com.mysql.jdbc.Driver",
                username = "user",
                password = "password"
            }
        )
    )
}

 

 

데이터베이스 연결 확인

테이블 및 Entity 정의

id를 기본키로 갖고 이름과 나이 속성을 가진 Member 엔티티를 작성한다.

import org.ktorm.entity.Entity
import org.ktorm.schema.Table
import org.ktorm.schema.int
import org.ktorm.schema.long
import org.ktorm.schema.varchar

object MemberTable : Table<Member>("member") {
    val id = long("id").primaryKey().bindTo { it.id }
    val name = varchar("name").bindTo { it.name }
    val age = int("age").bindTo { it.age }
}

interface Member : Entity<Member> {
    companion object : Entity.Factory<Member>()

    val id: Long
    var name: String
    var age: Int
}

 

member 테이블 생성

Ktorm은 데이터베이스 테이블을 자동으로 생성해주지는 않는다. 따라서 MySQL에서 직접 쿼리를 실행하여 테이블을 생성하거나 인텔리제이와 같은 IDE를 통해 쿼리를 실행하여 테이블을 생성해야 한다.

CREATE TABLE member (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    age INT NOT NULL
);
  • 주의할 점은 Ktorm을 통해 정의한 테이블과 실제 데이터베이스의 테이블이 일치하도록 생성해 주어야 한다.

 

member 데이터 insert

연결한 데이터베이스에 데이터가 잘 들어가는지 확인하기 위해 다음과 같이 작성한다.

fun main() {
    database.insert(MemberTable) {
        set(it.name, "Kotlin")
        set(it.age, 10)
    }

    database.insert(MemberTable) {
        set(it.name, "Ktor")
        set(it.age, 20)
    }

    database.insert(MemberTable) {
        set(it.name, "Ktorm")
        set(it.age, 30)
    }
}
  • Ktorm의 데이터 insert하기 위한 방법 중 하나로 다양한 방법으로 CRUD를 작성할 수 있다.

 

애플리케이션이 실행되면 다음과 같이 데이터베이스에 저장된다.

 

Ktrom의 다양한 쿼리 작성 방법

Ktorm은 데이터베이스와 상호작용하기 위해 다양한 방법으로 쿼리를 작성할 수 있다.

 

1. DSL(Domain-Specific Langague) 쿼리

Ktorm에서 제공하는 DSL을 통해 쿼리를 작성할 수 있다. DSL은 SQL 문법과 유사하면서도 Kotlin의 특성을 활용하여 직관적이고 가독성이 높은 쿼리를 작성할 수 있도록 도와준다.

// 전체 회원 조회
val members = database.from(MemberTable).select()

// 나이가 30인 회원 조회
val members = database.from(MemberTable)
    .select()
    .where { MemberTable.age eq 30 }

 

2. 시퀀스 API 쿼리

Kotlin의 시퀀스 함수 문법으로 Ktorm의 쿼리를 작성할 수 있다. sequenceOf() 함수를 통해 시퀀스 객체를 생성하여 코드의 가독성을 높일 수 있다.

// 전체 회원 정보 조회
val members = database.sequenceOf(MemberTable).toList()

// 조건에 따른 회원 정보 조회
val members = database.sequenceOf(MemberTable)
    .filter { it.age eq 30 }
    .toList()

 

 

3. 동적 쿼리

Ktorm DSL은 Kotlin의 특성을 활용하여 쿼리를 쉽게 동적으로 생성할 수 있다. if나 when과 같은 제어 문을 통해 동적 쿼리를 작성한다.

// 검색 조건에 따라 동적 쿼리 작성
val keyword = "길동"
val members = database.from(MemberTable)
    .select()
    .whereWithConditions {
        if (keyword.isNotBlank()) {
            it += MemberTable.name like "%$keyword%"
        }
    }

 

4. Native SQL 쿼리

경우에 따라 Native SQL 쿼리를 작성하여 실행할 수도 있다. 이는 복잡한 쿼리나 특정 데이터베이스에 최적화된 쿼리를 사용해야 할 때 유용하며 sqlQuery() 함수를 통해 작성할 수 있다.

// Native SQL
val keyword = "John"
val sql = """
    SELECT * FROM member
    WHERE name LIKE ? OR age >= ?
""".trimIndent()

val members = database.sqlQuery(sql, "%$keyword%", 30)

 

 

데이터베이스 연동을 위한 설정

Ktorm의 Database.connect() 함수를 통해 연결할 때 몇 가지 설정을 할 수 있다.

 

dialect

데이터베이스 방언을 설정할 수 있다. 기본적으로 JDK의 ServiceLoader 기능을 이용하여 자동으로 방언을 감지한다. 만약, Ktorm이 지원하지 않는 데이터베이스와 연동할 경우 방언을 지정하여 설정해야 한다.

 

logger

Ktorm에서 생성된 SQL 쿼리 등에 대하여 log 출력에 대해 설정할 수 있다. 기본적으로 자동으로 logger를 감지하여 기본 로그 기능을 사용하며, 별도로 직접 설정할 수 있다.

 

alwaysQuoteIdentifiers

SQL 쿼리에 대하여 테이블 이름, 컬럼 이름 등을 따옴표로 묶을지에 대한 설정이다. 기본적으로 자동으로 감지하여 설정해 주지만 특정 데이터베이스에서는 명시적으로 설정해 주어야 할 수도 있다.

 

generateSqlInUpperCase

생성된 SQL 쿼리를 항상 대문자로 출력할 지에 대한 설정이다.

 

다음과 같이 데이터베이스 연결하여 설정할 수 있다.

object DatabaseConnection {
    val database = Database.connect(
        dataSource = HikariDataSource(
            HikariConfig().apply {
                jdbcUrl = yamlConfig.ktorm.database.url
                driverClassName = yamlConfig.ktorm.database.driver
                username = yamlConfig.ktorm.database.username
                password = yamlConfig.ktorm.database.password
            }
        ),
        dialect = MySqlDialect(),
        logger = ConsoleLogger(threshold = LogLevel.INFO),
        alwaysQuoteIdentifiers = true,
        generateSqlInUpperCase = false
    )
}
  • 추가적으로 Url, driver, username, password 등 민감한 정보에 대하여 Yml 파일에 정의하여 Jackson, snakeyaml 등의 라이브러리를 통해 매핑하여 작성할 수 있다.
반응형