Skip to content

Latest commit

ย 

History

History
450 lines (356 loc) ยท 18.2 KB

2021-05-20-exposed.md

File metadata and controls

450 lines (356 loc) ยท 18.2 KB

Exposed ๋ž€ ?

Exposed๋Š” JetBrains์—์„œ ๋งŒ๋“  Kotlin ์–ธ์–ด ๊ธฐ๋ฐ˜์˜ ORM ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. Exposed๋Š” ๋‘ ๊ฐ€์ง€ ๋ ˆ๋ฒจ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค access๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. SQL์„ ๋งคํ•‘ ํ•œ DSL ๋ฐฉ์‹, ๊ฒฝ๋Ÿ‰ํ™”ํ•œ DAO ๋ฐฉ์‹์„ ์ œ๊ณตํ•˜๋ฉฐ ๊ณต์‹์ ์œผ๋กœ H2, MySQL, MariaDB, Oracle, PostgreSQL, SQL Server, SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์™œ ์“ฐ๋Š” ๋ฐ ?

์ €๋Š” ๊ฐœ์ธ์ , ํšŒ์‚ฌ ์—…๋ฌด์—์„œ Spring Data JPA๋ฅผ ์ฃผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. JPA๊ฐ€ ๊ฐ€์ ธ๋‹ค์ฃผ๋Š” ํฐ ์žฅ์ ์ด ๋งŽ์•„ ์ ๊ทน์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€๋งŒ ํŠน์ • ์ƒํ™ฉ์—์„œ ํ•˜์ด๋ฒ„๋„ค์ดํŠธ์˜ ๋‹จ์ ์ด ์žˆ์–ด ์ด๋ฅผ ๋ณด์•ˆํ•˜๊ธฐ ์œ„ํ•ด์„œ Exposed๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ๋‚ด์šฉ์€ Batch Insert ์„ฑ๋Šฅ ํ–ฅ์ƒ๊ธฐ 1ํŽธ - With JPA, Batch Insert ์„ฑ๋Šฅ ํ–ฅ์ƒ๊ธฐ 2ํŽธ - ์„ฑ๋Šฅ ์ธก์ •์—์„œ ํฌ์ŠคํŒ…ํ•œ ๋ฐ” ์žˆ์Šต๋‹ˆ๋‹ค.

Getting Started

MySQL

version: '3'

services:
    db_mysql:
        container_name: mysql.local
        image: mysql/mysql-server:5.7
        environment:
            MYSQL_ROOT_HOST: '%'
            MYSQL_DATABASE: 'exposed_study'
            MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
        ports:
            - '3366:3306'
        volumes:
            - './volumes/mysql/default:/var/lib/mysql'
        command:
            - 'mysqld'
            - '--character-set-server=utf8mb4'
            - '--collation-server=utf8mb4_unicode_ci'
            - '--sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
$ docker-compose up -d

Exposed์—์„œ ์ง€์›ํ•ด ์ฃผ๋Š” MySQL ๊ธฐ๋ฐ˜์œผ๋กœ ์ง„ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ ํ•ด๋‹น ํ™˜๊ฒฝ์„ Docker๋กœ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

Gradle

dependencies {
    implementation("mysql:mysql-connector-java")
    implementation("org.jetbrains.exposed:exposed-core:0.31.1")
    implementation("org.jetbrains.exposed:exposed-dao:0.31.1")
    implementation("org.jetbrains.exposed:exposed-jdbc:0.31.1")
    implementation("org.jetbrains.exposed:exposed-java-time:0.31.1")
}

ํ•„์š”ํ•œ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

Config

class ExposedGettingStarted {
    private val config = HikariConfig().apply {
        jdbcUrl = "jdbc:mysql://localhost:3366/exposed_study?useSSL=false&serverTimezone=UTC&autoReconnect=true&rewriteBatchedStatements=true"
        driverClassName = "com.mysql.cj.jdbc.Driver"
        username = "root"
        password = ""
    }

    private val dataSource = HikariDataSource(config)
    ...
}

HikariConfig์— ์œ„์—์„œ ์ƒ์„ฑํ•œ MySQL ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜์—ฌ DataSource๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

DSL ๋ฐฉ์‹

object Payments : LongIdTable(name = "payment") {
    val orderId = long("order_id")
    val amount = decimal("amount", 19, 4)
}

class ExposedGettingStarted {

    @Test
    fun `exposed DSL`() {
        // connection to MySQL
        Database.connect(dataSource)

        transaction {
            // Show SQL logging
            addLogger(StdOutSqlLogger)

            // CREATE TABLE IF NOT EXISTS payment (id BIGINT AUTO_INCREMENT PRIMARY KEY, order_id BIGINT NOT NULL, amount DECIMAL(19, 4) NOT NULL)
            SchemaUtils.create(Payments)

            // INSERT INTO payment (amount, order_id) VALUES (1, 1)
            // ...
            (1..5).map {
                Payments.insert { payments ->
                    payments[amount] = it.toBigDecimal()
                    payments[orderId] = it.toLong()
                }
            }

            // UPDATE payment SET amount=0 WHERE payment.amount >= 0
            Payments.update({ amount greaterEq BigDecimal.ZERO })
            {
                it[amount] = BigDecimal.ZERO
            }

            // SELECT payment.id, payment.order_id, payment.amount FROM payment WHERE payment.amount = 0
            // Payment(amount=1.0000, orderId=1)
            Payments.select { amount eq BigDecimal.ZERO }
                    .forEach { println(it) }

            // DELETE FROM payment WHERE payment.amount >= 1
            Payments.deleteWhere { amount greaterEq BigDecimal.ONE }

            // DROP TABLE IF EXISTS payment
            SchemaUtils.drop(Payments)
        }
    }
}

Payments๊ฐ์ฒด์— ํ…Œ์ด๋ธ” ์ •๋ณด๋ฅผ ์ž‘์„ฑ ํ•˜์—ฌ ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•˜์—ฌ Insert, Select, Update, Delete๋ฅผ ํ•˜๊ณ  ํ…Œ์ด๋ธ”์„ Drop ํ•ฉ๋‹ˆ๋‹ค. SQL๋ฅผ ๋งคํ•‘ํ•œ DSL ๋ฐฉ์‹์œผ๋กœ ์ฝ”ํ‹€๋ฆฐ ์ฝ”๋“œ ๋ฒ ์ด์Šค๋กœ SQL์„ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

DAO ๋ฐฉ์‹

class Payment(id: EntityID<Long>) : LongEntity(id) {
    companion object : LongEntityClass<Payment>(Payments)
    var amount by Payments.amount
    var orderId by Payments.orderId
}

class ExposedGettingStarted {
    @Test
    fun `exposed DAO`() {
        // connection to MySQL
        Database.connect(dataSource)

        transaction {
            // Show SQL logging
            addLogger(StdOutSqlLogger)

            // CREATE TABLE IF NOT EXISTS payment (id BIGINT AUTO_INCREMENT PRIMARY KEY, order_id BIGINT NOT NULL, amount DECIMAL(19, 4) NOT NULL)
            SchemaUtils.create(Payments)

            // INSERT INTO payment (amount, order_id) VALUES (1, 1)
            // ...
            (1..20).map {
                Payment.new {
                    amount = it.toBigDecimal()
                    orderId = it.toLong()
                }
            }

            // UPDATE payment SET amount=0 WHERE id = 1
            // ...
            Payment.all()
                    .forEach { it.amount = BigDecimal.ZERO }

            // SELECT payment.id, payment.order_id, payment.amount FROM payment WHERE payment.amount >= 1
            // Payment(amount=1.0000, orderId=1)
            Payment.find { amount eq BigDecimal.ONE }
                    .forEach { println(it) }

            // DELETE FROM payment WHERE payment.id = 1
            // ...
            Payment.all()
                    .forEach { it.delete() }

            // DROP TABLE IF EXISTS payment
            SchemaUtils.drop(Payments)
        }
    }
}

DSL ๊ฐ์ฒด์ธ Payments ๊ธฐ๋ฐ˜์œผ๋กœ DAO Payment๋ฅผ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. DAO๋Š” payment ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ Data Access Object์˜ ๊ธฐ๋Šฅ์„ ์ „๋‹ดํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. Data JPA์˜ Repository์™€ ๋น„์Šทํ•œ ๊ฐœ๋…์ž…๋‹ˆ๋‹ค.

With Spring Boot

Exposed์—์„œ๋Š” Spring Boot๋ฅผ ์ง€์›ํ•˜๋Š” exposed-spring-boot-starter ์˜์กด์„ฑ์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

gradle

implementation("org.jetbrains.exposed:exposed-spring-boot-starter:0.31.1")

์Šคํ”„๋ง ๋ถ€ํŠธ์—์„œ ๊ณต์‹์ ์œผ๋กœ ์ง€์›ํ•˜๋Š” ์˜์กด์„ฑ์€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฒ„์ „์„ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ช…์‹œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

properties

spring:
    datasource:
        url: jdbc:mysql://localhost:3366/exposed_study?useSSL=false&serverTimezone=UTC&autoReconnect=true&rewriteBatchedStatements=true&logger=Slf4JLogger&profileSQL=false&maxQuerySizeToLog=100000
        username: root
        password:
        driver-class-name: com.mysql.cj.jdbc.Driver
    exposed:
        generate-ddl: true
#        excluded-packages: com.example.exposedstudy

logging.level.Exposed: debug

datasource์— ๋Œ€ํ•œ ์„ค์ •์„ ์Šคํ”„๋ง ๋ถ€ํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ  generate-ddl ์„ค์ •์ด ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ํŠน์ • ์Šคํ‚ค๋งˆ๋ฅผ ์ œ์™ธํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ excluded-packages ์„ค์ •์œผ๋กœ ์ œ์™ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ๋งค์šฐ ๋‹จ์ˆœํ•ฉ๋‹ˆ๋‹ค. logging.level.Exposed: debug ๊ฒฝ์šฐ ๋ณ„๋„์˜ ์„ค์ • ์—†์ด Show SQL Log๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Configuration
@AutoConfigureAfter(DataSourceAutoConfiguration::class)
@EnableTransactionManagement
open class ExposedAutoConfiguration(private val applicationContext: ApplicationContext) {

    @Value("\${spring.exposed.excluded-packages:}#{T(java.util.Collections).emptyList()}")
    private lateinit var excludedPackages: List<String>

    @Bean
    open fun springTransactionManager(datasource: DataSource) = SpringTransactionManager(datasource)

    @Bean
    @ConditionalOnProperty("spring.exposed.generate-ddl", havingValue = "true", matchIfMissing = false)
    open fun databaseInitializer() = DatabaseInitializer(applicationContext, excludedPackages)
}

open class DatabaseInitializer(private val applicationContext: ApplicationContext, private val excludedPackages: List<String>) : ApplicationRunner, Ordered {
    override fun getOrder(): Int = DATABASE_INITIALIZER_ORDER

    companion object {
        const val DATABASE_INITIALIZER_ORDER = 0
    }

    private val logger = LoggerFactory.getLogger(javaClass)

    @Transactional
    override fun run(args: ApplicationArguments?) {
        val exposedTables = discoverExposedTables(applicationContext, excludedPackages)
        logger.info("Schema generation for tables '{}'", exposedTables.map { it.tableName })

        logger.info("ddl {}", exposedTables.map { it.ddl }.joinToString())
        SchemaUtils.create(*exposedTables.toTypedArray())
    }
}

fun discoverExposedTables(applicationContext: ApplicationContext, excludedPackages: List<String>): List<Table> {
    val provider = ClassPathScanningCandidateComponentProvider(false)
    provider.addIncludeFilter(AssignableTypeFilter(Table::class.java))
    excludedPackages.forEach { provider.addExcludeFilter(RegexPatternTypeFilter(Pattern.compile(it.replace(".", "\\.") + ".*"))) }
    val packages = AutoConfigurationPackages.get(applicationContext)
    val components = packages.map { provider.findCandidateComponents(it) }.flatten()
    return components.map { Class.forName(it.beanClassName).kotlin.objectInstance as Table }
}

์Šคํ”„๋ง ํ‘œํ˜„์‹์œผ๋กœ ์ œ์™ธ ์‹œํ‚ฌ excludedPackages๋ฅผ List๋กœ ๋ฐ›๊ณ , generate-ddl ์—ฌ๋ถ€์— ๋”ฐ๋ผ DatabaseInitializer ๋นˆ์„ ๋“ฑ๋ก์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํ•ด๋‹น ๋นˆ์„ ๋“ฑ๋กํ•˜๊ฒŒ ๋˜๋ฉด DatabaseInitializer ๊ฐ์ฒด๊ฐ€ ApplicationRunner์„ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์Šคํ”„๋ง ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ run() ๋ฉ”์„œ๋“œ์—์„œ ์Šคํ‚ค๋งˆ๋ฅผ ์ƒ์„ฑํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. (excludedPackages๋Š” ์ œ์™ธ)

run() ๋ฉ”์„œ๋“œ์— @Transactional ์–ด๋…ธํ…Œ์ด์…˜์ด ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ spring-transaction๋ชจ๋“ˆ์„ ํ†ตํ•ด์„œ TransactionSynchronizationManager๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์Šคํ”„๋ง์˜ ํŠธ๋žœ์žญ์…˜ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ ์ž…๋‹ˆ๋‹ค.

์Šคํ”„๋ง์˜ ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™” ๋ฉ”์ปค๋‹ˆ์ฆ˜ ํ† ๋น„์˜ ์Šคํ”„๋ง 3.1, 361 ํŽ˜์ด์ง€ ์Šคํ”„๋ง์€ ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ํŠธ๋žœ์žญ์…˜ ๋™๊ธฐํ™”๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ๋ฐฉ์‹์€ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“  Connection ์˜ค๋ธŒ์ ํŠธ๋ฅผ ํŠน๋ณ„ํ•œ ์ €์žฅ์†Œ์— ๋ณด๊ด€ํ•ด๋‘๊ณ , ์ดํ›„์— ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์„œ๋“œ์—์„œ ์ €์žฅ๋œ Connection์„ ๊ฐ€์ ธ๋‹ค๊ฐ€ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Testing in Spring Boot

@SpringBootTest
@Transactional
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@ActiveProfiles("test")
open class ExposedTestSupport

class ExposedGettingStartedInSpringBoot : ExposedTestSupport() {

    @Test
    fun `exposed DAO`() {
        // connection to MySQL
        // Database.connect(dataSource) ์Šคํ”„๋ง Bean์˜ DataSource๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์„

        // transaction { ์Šคํ”„๋ง @Transactional ์œผ๋กœ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์„
            // Show SQL logging
            // addLogger(StdOutSqlLogger)  logging.level.Exposed: debug ์œผ๋กœ Show SQL logging ํ™•์ธ

            // CREATE TABLE IF NOT EXISTS payment (id BIGINT AUTO_INCREMENT PRIMARY KEY, order_id BIGINT NOT NULL, amount DECIMAL(19, 4) NOT NULL)
            //  SchemaUtils.create(Payments)  generate-ddl: true ์œผ๋กœ ์Šคํ‚ค๋งˆ ์ƒ์„ฑ

            // INSERT INTO payment (amount, order_id) VALUES (1, 1)
            // ...
            (1..20).map {
                Payment.new {
                    amount = it.toBigDecimal()
                    orderId = it.toLong()
                }
            }

            // UPDATE payment SET amount=0 WHERE id = 1
            // ...
            Payment.all()
                    .forEach { it.amount = BigDecimal.ZERO }

            // SELECT payment.id, payment.order_id, payment.amount FROM payment WHERE payment.amount >= 1
            // Payment(amount=1.0000, orderId=1)
            Payment.find { amount eq BigDecimal.ONE }
                    .forEach { println(it) }

            // DELETE FROM payment WHERE payment.id = 1
            // ...
            Payment.all()
                    .forEach { it.delete() }

            // DROP TABLE IF EXISTS payment
            // SchemaUtils.drop(Payments)
        // }
    }

    @Test
    fun `exposed DSL`() {
        // connection to MySQL
        // Database.connect(dataSource) ์Šคํ”„๋ง Bean์˜ DataSource๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์„

        // transaction { ์Šคํ”„๋ง @Transactional ์œผ๋กœ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์„
            // Show SQL logging
            // addLogger(StdOutSqlLogger)  logging.level.Exposed: debug ์œผ๋กœ Show SQL logging ํ™•์ธ

            // CREATE TABLE IF NOT EXISTS payment (id BIGINT AUTO_INCREMENT PRIMARY KEY, order_id BIGINT NOT NULL, amount DECIMAL(19, 4) NOT NULL)
            //  SchemaUtils.create(Payments)  generate-ddl: true ์œผ๋กœ ์Šคํ‚ค๋งˆ ์ƒ์„ฑ

            // INSERT INTO payment (amount, order_id) VALUES (1, 1)
            // ...
            (1..5).map {
                Payments.insert { payments ->
                    payments[amount] = it.toBigDecimal()
                    payments[orderId] = it.toLong()
                }
            }

            // UPDATE payment SET amount=0 WHERE payment.amount >= 0
            Payments.update({ amount greaterEq BigDecimal.ZERO })
            {
                it[amount] = BigDecimal.ZERO
            }

            // SELECT payment.id, payment.order_id, payment.amount FROM payment WHERE payment.amount = 0
            // Payment(amount=1.0000, orderId=1)
            Payments.select { amount eq BigDecimal.ZERO }
                    .forEach { println(it) }

            // DELETE FROM payment WHERE payment.amount >= 1
            Payments.deleteWhere { amount greaterEq BigDecimal.ONE }

            // DROP TABLE IF EXISTS payment
            // SchemaUtils.drop(Payments)
        // }
    }
}

DataSource๋Š” ์Šคํ”„๋ง Bean์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ œ๊ฑฐํ–ˆ์œผ๋ฉฐ, transaction { ... }์œผ๋กœ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ–ˆ๋˜ ์ฝ”๋“œ๋ฅผ ์Šคํ”„๋ง์˜ @Transactional์œผ๋กœ ๋Œ€์ฒดํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ SchemaUtils.create(Payments)์œผ๋กœ ์Šคํ‚ค๋งˆ๋ฅผ ์ƒ์„ฑํ–ˆ๋˜ ๋ถ€๋ถ„์„ generate-ddl: true ์†์„ฑ ํŒŒ์ผ๋กœ ๋Œ€์ฒดํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ExposedTestSupport ๊ฐ์ฒด์— @Transactional๊ฐ€ ์žˆ์–ด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์˜ ์ตœ์ข… ๋ฐ์ดํ„ฐ๋Š” ๋ชจ๋‘ Rollback์„ ์ง„ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. Spring ํ™˜๊ฒฝ์—์„œ Exposed๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ๋ณด๋‹ค ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์‹ฌํ™”

batch insert

@Test
fun `batch insert`() {
    val data = (1..10).map { it }
    Books.batchInsert(
            data,
            ignore = false,
            shouldReturnGeneratedValues = false
    ) {
        this[Books.writer] = 1L
        this[Books.title] = "$it-title"
        this[Books.price] = it.toBigDecimal()
        this[Books.createdAt] = LocalDateTime.now()
        this[Books.updatedAt] = LocalDateTime.now()
    }
}

Exposed๋Š” batchInsert() ๋ฉ”์„œ๋“œ๋ฅผ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‰ฝ๊ฒŒ batch insert๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Mysql์˜ ๊ฒฝ์šฐ JDBC ๋“œ๋ผ์ด๋ฒ„์— rewriteBatchedStatements=true ์†์„ฑ์„ ๋ฐ˜๋“œ์‹œ ์ž…๋ ฅํ•ด์•ผ batch insert๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. shouldReturnGeneratedValues ๊ฐ’์„ false๋กœ ์ง€์ •ํ•˜๋ฉด auto_increment์œผ๋กœ ์ฆ๊ฐ€๋œ ID ๊ฐ’์„ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๊ธฐ์— ์„ฑ๋Šฅ์ด ํ–ฅ์ƒ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋กœ๊ทธ์— ์ถœ๋ ฅ๋˜๋Š” SQL์€ batch insert๊ฐ€ ์ง„ํ–‰๋˜์ง€ ์•Š๊ณ  ๊ฐœ๋ณ„ insert๋กœ ์ถœ๋ ฅ ๋ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด batch insert๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

SQL Log ํ™•์ธ ๋ฐฉ๋ฒ• show variables like 'general_log%'; ํ™•์ธ ํ•ด์„œ general_log๊ฐ€ OFF์ธ ๊ฒฝ์šฐ set global general_log = 'ON'; ์„ค์ • ์ดํ›„ general_log_file ๊ฒฝ๋กœ์— ๋กœ๊ทธ ํŒŒ์ผ ํ™•์ธ

Variable_name Value
general_log OFF
general_log_file /var/lib/mysql/2eb41ec6a5fe.log

์—ฐ๊ด€ ๊ด€๊ณ„

object Books : LongIdTable("book") {
    val writer = reference("writer_id", Writers)
    val title = varchar("title", 150)
    val price = decimal("price", 10, 4)
    val createdAt = datetime("created_at")
    val updatedAt = datetime("updated_at")
}

object Writers : LongIdTable("writer") {
    val name = varchar("name", 150)
    val email = varchar("email", 150)
    val createdAt = datetime("created_at")
    val updatedAt = datetime("updated_at")
}

reference์„ ํ†ตํ•ด์„œ ๊ฐ์ฒด์˜ ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Join

@Test
fun `join`() {
    val writerId = insertWriter("yun", "yun@asd.com")[Writers.id].value
    (1..5).map {
        insertBook("$it-title", BigDecimal.TEN, writerId)
    }

    // SELECT book.id, book.title, book.price, writer.`name`, writer.email FROM book INNER JOIN writer ON writer.id = book.writer_id
    (Books innerJoin Writers)
            .slice(
                    Books.id,
                    Books.title,
                    Books.price,
                    Writers.name,
                    Writers.email,
            )
            .selectAll()
            .forEach {
                it.fieldIndex
                println("bookId: ${it[Books.id]}, title: ${it[Books.title]}, writerName: ${it[Writers.name]}, writerEmail: ${it[Writers.email]}")
            }
}

DSL ๊ธฐ๋ฐ˜์œผ๋กœ ์กฐ์ธ SQL๋กœ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฐธ๊ณ