Search

Exposed - 코틀린 SQL 프레임워크

태그
Kotlin
DB
작성 상태
작성 완료
작성일
2024/12/21
참고 링크
참고 링크 2

Exposed란

Jetbrain 에서 만든 코틀린을 위한 ORM 프레임워크다.
공식 깃허브의 소개글이다.
Exposed is a lightweight SQL library on top of a JDBC driver for the Kotlin language. Exposed has two flavors of database access: typesafe SQL wrapping DSL and lightweight Data Access Objects (DAO). With Exposed, you have two options for database access: wrapping DSL and a lightweight DAO. Our official mascot is the cuttlefish, which is well-known for its outstanding mimicry ability that enables it to blend seamlessly into any environment. Similar to our mascot, Exposed can be used to mimic a variety of database engines, which helps you to build applications without dependencies on any specific database engine and to switch between them with very little or no changes.

왜 JPA 냅두고 Exposed를 쓰나?

JPA는 영속성 컨텍스트와 1차 캐시, 변경 감지 등의 기능을 제공해준다. 이는 분명 편리한 기능이긴 하지만, 반드시 필요한 기능은 아니다. 또한, 이런 기능을 제공하기 위해 JPA와 Hibernate의 구조가 크고 복잡해 이해하기 힘들다.
또한, JPA와 코틀린의 궁합이 썩 좋지 않은 것도 문제다. JPA는 말 그대로 자바를 위한 것이기 때문에, 코틀린 환경에서 이를 사용할 때 문제가 발생하기도 한다. 당장 관련 키워드로 검색해 보면 사례가 많이 나온다. 물론, 대부분의 사례는 이미 명확한 해결법이 존재하기 때문에 문제가 되지 않을 수 있다. 그러나 모든 코드를 코틀린으로 작성한다면, 처음부터 코틀린을 위한 프레임워크를 사용하는 것도 합리적이라 생각한다.
그러나 관련 정보가 JPA에 비해 적다는 치명적인 단점이 있다. 사실상 공식 문서 원툴이다.

DSL 방식 사용법

Exposed를 사용하는 방식은 공식적으로 DAO 방식과 DSL 방식이 있다. DAO 방식은 Spring Data JPA의 Repository와, DSL 방식은 QueryDSL 과 비슷한 사용 방식을 보여준다. 두 사용법 모두 명시적으로 커넥션을 얻고, 트랜잭션을 시작하는 API를 호출해야한다.
현업의 테이블 구조는 토이프로젝트와는 차원이 다르게 복잡하다. 따라서, 복잡한 쿼리가 필요하고, 이는 DSL 방식을 사용하는 압력으로 작용한다. 그래서, DSL 방식의 사용법을 익혀봤다.
DSL 방식으로 Exposed를 사용하기 위해서 필요한 클래스는 2종류다. 하나는 데이터를 담을 대상인 DTO이고, 다른 하나는 테이블을 표현하는 클래스다. 즉, JPA에서는 @Entitiy 어노테이션을 붙인 클래스가 데이터를 담을 대상이면서 테이블을 표현하는 클래스였지만, Exposed의 DSL 방식에서는 두 역할이 분리되어있다.
import org.jetbrains.exposed.sql.Table const val MAX_VARCHAR_LENGTH = 128 object Tasks : Table("tasks") { val id = integer("id").autoIncrement() val title = varchar("name", MAX_VARCHAR_LENGTH) val description = varchar("description", MAX_VARCHAR_LENGTH) val isCompleted = bool("completed").default(false) }
Kotlin
복사
테이블을 표현하는 클래스. Table 클래스를 상속해야 한다.
package org.example import Tasks import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.transactions.transaction fun main() { Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver") transaction { val taskId = Tasks.insert { it[title] = "Learn Exposed" it[description] = "Go through the Get started with Exposed tutorial" } get Tasks.id val secondTaskId = Tasks.insert { it[title] = "Read The Hobbit" it[description] = "Read the first two chapters of The Hobbit" it[isCompleted] = true } get Tasks.id println("Created new tasks with ids $taskId and $secondTaskId.") Tasks.select(Tasks.id.count(), Tasks.isCompleted) .groupBy(Tasks.isCompleted) .forEach { println("${it[Tasks.isCompleted]}: ${it[Tasks.id.count()]} ") } } }
Kotlin
복사
위 코드에서 볼 수 있듯 직관적인 API들을 제공한다. 코틀린에 익숙하다면 IDE의 자동완성 기능만으로도 금방 사용법을 익힐 수 있다.
~Returning 이 붙은 API가 있는데, 이는 insert, update, delete 한 대상을 반환해주는 API이다. 그러나, 이 API들은 PostgreSQLSQLite 에서만 지원한다. 이들의 자체 기능을 활용하기 때문이다. Spring Data JPA 에서는 DBMS와 무관하게 이런 기능을 제공해주는 것과 대조적이다. JPA에서는 영속성 컨텍스트가 중간에서 이를 위한 행위를 해주는 것이라 판단된다.

Spring Boot와 함께 사용하기

Spring Boot를 사용해 데이터베이스와 연결하기 위해 흔히 application.yml 과 같은 설정 파일에 DB 접속 정보들을 작성해둔다. 그럼 Spring Boot 어플리케이션이 시작되면서 이를 읽어 DataSource 인스턴스로 만들어 관리한다. 또한, Spring의 Transaction Manager 를 이용해 @Transactional 어노테이션을 사용하는 것으로 간단하게 트랜잭션 범위를 정한다.
이런 기능들을 Exposed와 함께 사용하기 위해서는 exposed-spring-boot-starter 를 import 해야 한다. 필요한 설정을 자동으로 해주는 ExposedAutoConfiguration.kt이 작성되어 있기 때문이다.
스키마를 자동으로 만들어주는 기능도 포함되어있다. 이를 활성화 하고 싶으면 application.yml에 다음과 같은 설정을 하면 된다.
spring: exposed: generate-ddl: true
YAML
복사
실행되는 sql 쿼리를 보기 위해선 다음과 같은 설정을 해주면 된다. 그러면 바인드된 값까지 모두 보여준다.
spring: exposed: show-sql: true
YAML
복사