jOOQ (Kotlin)

jOOQ(Java Object Oriented Querying)は「SQL重視のType-safeなクエリビルダー」として開発された、Kotlinでも活用できるデータベースアクセスライブラリです。「SQLファースト」の哲学を掲げ、SQLの表現力を損なうことなく、コンパイル時型安全性を提供します。データベーススキーマからKotlinコードを自動生成し、IDEの自動補完とコンパイル時チェックにより、実行時SQLエラーを大幅に削減できます。複雑なJOIN、ウィンドウ関数、集約クエリなどのSQLの全機能をKotlinのタイプセーフなDSLで記述でき、エンタープライズレベルのデータベースアプリケーション開発における信頼性と生産性を同時に実現します。

SQLKotlinType-safeQuery DSLCode Generationデータベース

GitHub概要

jOOQ/jOOQ

jOOQ is the best way to write SQL in Java

ホームページ:https://www.jooq.org
スター6,446
ウォッチ153
フォーク1,216
作成日:2011年4月17日
言語:Java
ライセンス:Other

トピックス

code-generatordatabasedb2hibernatejavajdbcjdbc-utilitiesjooqjpamysqloracleormpostgresqlsqlsql-buildersql-formattersql-querysql-query-buildersql-query-formattersqlserver

スター履歴

jOOQ/jOOQ Star History
データ取得日時: 2025/7/17 00:43

ライブラリ

jOOQ (Kotlin)

概要

jOOQ(Java Object Oriented Querying)は「SQL重視のType-safeなクエリビルダー」として開発された、Kotlinでも活用できるデータベースアクセスライブラリです。「SQLファースト」の哲学を掲げ、SQLの表現力を損なうことなく、コンパイル時型安全性を提供します。データベーススキーマからKotlinコードを自動生成し、IDEの自動補完とコンパイル時チェックにより、実行時SQLエラーを大幅に削減できます。複雑なJOIN、ウィンドウ関数、集約クエリなどのSQLの全機能をKotlinのタイプセーフなDSLで記述でき、エンタープライズレベルのデータベースアプリケーション開発における信頼性と生産性を同時に実現します。

詳細

jOOQ 2025年版は、Kotlin Multiplatform対応とCoroutinesサポートにより、現代的なKotlinアプリケーション開発に最適化されています。データベーススキーマの変更を自動検出し、対応するKotlinコードを再生成することで、スキーマとコードの整合性を常に保持します。PostgreSQL、MySQL、Oracle、SQL Server、H2など主要データベースの方言や固有機能を完全サポートし、ベンダー固有のSQL機能も型安全に利用できます。バッチ処理、トランザクション管理、接続プール統合、結果セットストリーミングなど、企業向けアプリケーションに必要な高度な機能を包括的に提供しています。

主な特徴

  • SQL-First哲学: SQLの表現力を最大限活用した型安全なクエリ構築
  • スキーマ同期: データベーススキーマからKotlinコードの自動生成
  • 完全な型安全性: コンパイル時のSQL構文・型チェック
  • データベース方言対応: 主要データベースの固有機能フルサポート
  • Kotlin統合: Coroutines、拡張関数、型推論の活用
  • 企業向け機能: バッチ処理、トランザクション、パフォーマンス最適化

メリット・デメリット

メリット

  • SQLの表現力を損なわない高度なクエリ記述能力
  • コンパイル時型チェックによる実行時エラーの大幅削減
  • データベーススキーマとコードの自動同期
  • IDEの強力な自動補完とリファクタリング支援
  • 複雑なビジネスロジックのSQL内表現が可能
  • 豊富なデータベース方言サポートとポータビリティ

デメリット

  • 学習コストが高く、SQLとjOOQ両方の知識が必要
  • コード生成ステップが必要で、ビルドプロセスが複雑化
  • シンプルなCRUD操作には過剰で、開発初期のオーバーヘッド
  • 生成されるコードが大量で、プロジェクトサイズが増大
  • ライセンス費用(商用データベース使用時)が必要な場合がある
  • Active Recordパターンに慣れた開発者には学習障壁

参考ページ

書き方の例

セットアップ

// build.gradle.kts
plugins {
    kotlin("jvm") version "1.9.22"
    id("nu.studer.jooq") version "8.2.1"
}

dependencies {
    implementation("org.jooq:jooq:3.18.7")
    implementation("org.jooq:jooq-kotlin:3.18.7")
    implementation("org.jooq:jooq-kotlin-coroutines:3.18.7")
    
    // データベースドライバー
    implementation("org.postgresql:postgresql:42.7.1")
    // または implementation("mysql:mysql-connector-java:8.0.33")
    
    // 接続プール
    implementation("com.zaxxer:HikariCP:5.1.0")
    
    // jOOQ コード生成用
    jooqGenerator("org.postgresql:postgresql:42.7.1")
}

// jOOQ 設定
jooq {
    configurations {
        create("main") {
            generateSchemaSourceOnCompilation.set(true)
            
            generator.apply {
                name = "org.jooq.codegen.KotlinGenerator"
                
                database.apply {
                    name = "org.jooq.meta.postgres.PostgresDatabase"
                    inputSchema = "public"
                    
                    // データベース接続設定
                    properties.add(
                        org.jooq.meta.jaxb.Property().apply {
                            key = "dialect"
                            value = "POSTGRES"
                        }
                    )
                }
                
                target.apply {
                    packageName = "com.example.jooq.generated"
                    directory = "src/main/kotlin"
                }
                
                generate.apply {
                    isDeprecated = false
                    isRecords = true
                    isImmutablePojos = true
                    isFluentSetters = true
                    isKotlinSetterJvmNameAnnotations = true
                }
            }
        }
    }
}
-- sample_schema.sql (サンプルスキーマ)
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    age INTEGER,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    is_active BOOLEAN DEFAULT true
);

CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    content TEXT,
    user_id INTEGER REFERENCES users(id),
    category_id INTEGER,
    published_at TIMESTAMP,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    is_published BOOLEAN DEFAULT false
);

CREATE TABLE categories (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL UNIQUE,
    description TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_active ON users(is_active);
CREATE INDEX idx_posts_user_id ON posts(user_id);
CREATE INDEX idx_posts_published ON posts(is_published, published_at);

基本的な使い方

// データベース接続設定
import org.jooq.*
import org.jooq.impl.DSL
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import com.example.jooq.generated.Tables.*
import com.example.jooq.generated.tables.records.*

class DatabaseConfig {
    companion object {
        fun createDataSource(): HikariDataSource {
            val config = HikariConfig().apply {
                jdbcUrl = "jdbc:postgresql://localhost:5432/mydb"
                username = "user"
                password = "password"
                driverClassName = "org.postgresql.Driver"
                
                maximumPoolSize = 20
                minimumIdle = 5
                connectionTimeout = 30000
                idleTimeout = 600000
                maxLifetime = 1800000
            }
            return HikariDataSource(config)
        }
        
        fun createContext(dataSource: HikariDataSource): DSLContext {
            return DSL.using(dataSource, SQLDialect.POSTGRES)
        }
    }
}

// 基本的なCRUD操作
class UserRepository(private val create: DSLContext) {
    
    // ユーザー作成
    fun insertUser(name: String, email: String, age: Int): UsersRecord {
        return create.insertInto(USERS)
            .set(USERS.NAME, name)
            .set(USERS.EMAIL, email)
            .set(USERS.AGE, age)
            .returning()
            .fetchOne()!!
    }
    
    // ユーザー検索
    fun findUserById(id: Int): UsersRecord? {
        return create.selectFrom(USERS)
            .where(USERS.ID.eq(id))
            .fetchOne()
    }
    
    fun findUserByEmail(email: String): UsersRecord? {
        return create.selectFrom(USERS)
            .where(USERS.EMAIL.eq(email))
            .fetchOne()
    }
    
    // 全ユーザー取得
    fun findAllUsers(): List<UsersRecord> {
        return create.selectFrom(USERS)
            .where(USERS.IS_ACTIVE.isTrue)
            .orderBy(USERS.CREATED_AT.desc())
            .fetch()
    }
    
    // ユーザー更新
    fun updateUser(id: Int, name: String?, email: String?, age: Int?): Int {
        val update = create.update(USERS)
        
        name?.let { update.set(USERS.NAME, it) }
        email?.let { update.set(USERS.EMAIL, it) }
        age?.let { update.set(USERS.AGE, it) }
        
        return update.set(USERS.UPDATED_AT, DSL.currentTimestamp())
            .where(USERS.ID.eq(id))
            .execute()
    }
    
    // ユーザー削除(論理削除)
    fun deactivateUser(id: Int): Int {
        return create.update(USERS)
            .set(USERS.IS_ACTIVE, false)
            .set(USERS.UPDATED_AT, DSL.currentTimestamp())
            .where(USERS.ID.eq(id))
            .execute()
    }
    
    // ユーザー削除(物理削除)
    fun deleteUser(id: Int): Int {
        return create.deleteFrom(USERS)
            .where(USERS.ID.eq(id))
            .execute()
    }
    
    // 条件検索
    fun searchUsers(
        nameQuery: String? = null,
        minAge: Int? = null,
        maxAge: Int? = null,
        isActive: Boolean = true
    ): List<UsersRecord> {
        var condition = USERS.IS_ACTIVE.eq(isActive)
        
        nameQuery?.let { 
            condition = condition.and(USERS.NAME.likeIgnoreCase("%$it%"))
        }
        
        minAge?.let {
            condition = condition.and(USERS.AGE.greaterOrEqual(it))
        }
        
        maxAge?.let {
            condition = condition.and(USERS.AGE.lessOrEqual(it))
        }
        
        return create.selectFrom(USERS)
            .where(condition)
            .orderBy(USERS.NAME.asc())
            .fetch()
    }
}

// 使用例
fun demonstrateBasicUsage() {
    val dataSource = DatabaseConfig.createDataSource()
    val create = DatabaseConfig.createContext(dataSource)
    val userRepository = UserRepository(create)
    
    // ユーザー作成
    val user1 = userRepository.insertUser("田中太郎", "[email protected]", 30)
    println("Created user: ${user1.id} - ${user1.name}")
    
    // ユーザー検索
    val foundUser = userRepository.findUserByEmail("[email protected]")
    println("Found user: ${foundUser?.name}")
    
    // ユーザー更新
    val updated = userRepository.updateUser(
        id = user1.id!!,
        name = "田中太郎(更新済み)",
        age = 31
    )
    println("Updated $updated records")
    
    // 条件検索
    val searchResults = userRepository.searchUsers(
        nameQuery = "田中",
        minAge = 25
    )
    println("Search results: ${searchResults.size} users")
}

複雑なクエリとJOIN操作

// 高度なクエリ操作
class AdvancedQueryService(private val create: DSLContext) {
    
    // JOIN クエリ
    fun getUsersWithPostCount(): List<Record> {
        return create.select(
            USERS.ID,
            USERS.NAME,
            USERS.EMAIL,
            DSL.count(POSTS.ID).`as`("post_count")
        )
        .from(USERS)
        .leftJoin(POSTS).on(USERS.ID.eq(POSTS.USER_ID))
        .where(USERS.IS_ACTIVE.isTrue)
        .groupBy(USERS.ID, USERS.NAME, USERS.EMAIL)
        .orderBy(DSL.count(POSTS.ID).desc())
        .fetch()
    }
    
    // 複雑なJOIN
    fun getPublishedPostsWithUserAndCategory(): List<Record> {
        return create.select(
            POSTS.ID,
            POSTS.TITLE,
            POSTS.CONTENT,
            POSTS.PUBLISHED_AT,
            USERS.NAME.`as`("author_name"),
            USERS.EMAIL.`as`("author_email"),
            CATEGORIES.NAME.`as`("category_name")
        )
        .from(POSTS)
        .join(USERS).on(POSTS.USER_ID.eq(USERS.ID))
        .leftJoin(CATEGORIES).on(POSTS.CATEGORY_ID.eq(CATEGORIES.ID))
        .where(POSTS.IS_PUBLISHED.isTrue)
        .and(USERS.IS_ACTIVE.isTrue)
        .orderBy(POSTS.PUBLISHED_AT.desc())
        .fetch()
    }
    
    // サブクエリ
    fun getActiveUsersWithRecentPosts(daysAgo: Int): List<UsersRecord> {
        val recentDate = DSL.currentTimestamp().minus(daysAgo)
        
        return create.selectFrom(USERS)
            .where(USERS.IS_ACTIVE.isTrue)
            .and(USERS.ID.`in`(
                DSL.select(POSTS.USER_ID)
                    .from(POSTS)
                    .where(POSTS.PUBLISHED_AT.greaterThan(recentDate))
                    .and(POSTS.IS_PUBLISHED.isTrue)
            ))
            .orderBy(USERS.NAME)
            .fetch()
    }
    
    // ウィンドウ関数
    fun getUserPostRankings(): List<Record> {
        return create.select(
            USERS.NAME,
            POSTS.TITLE,
            POSTS.PUBLISHED_AT,
            DSL.rowNumber().over(
                DSL.partitionBy(USERS.ID)
                    .orderBy(POSTS.PUBLISHED_AT.desc())
            ).`as`("post_rank"),
            DSL.count().over(
                DSL.partitionBy(USERS.ID)
            ).`as`("total_posts")
        )
        .from(POSTS)
        .join(USERS).on(POSTS.USER_ID.eq(USERS.ID))
        .where(POSTS.IS_PUBLISHED.isTrue)
        .orderBy(USERS.NAME, POSTS.PUBLISHED_AT.desc())
        .fetch()
    }
    
    // 集約クエリ
    fun getUserStatistics(): List<Record> {
        return create.select(
            USERS.ID,
            USERS.NAME,
            DSL.count(POSTS.ID).`as`("total_posts"),
            DSL.countDistinct(POSTS.CATEGORY_ID).`as`("categories_used"),
            DSL.min(POSTS.PUBLISHED_AT).`as`("first_post_date"),
            DSL.max(POSTS.PUBLISHED_AT).`as`("latest_post_date"),
            DSL.avg(DSL.length(POSTS.CONTENT)).`as`("avg_content_length")
        )
        .from(USERS)
        .leftJoin(POSTS).on(USERS.ID.eq(POSTS.USER_ID))
        .where(USERS.IS_ACTIVE.isTrue)
        .groupBy(USERS.ID, USERS.NAME)
        .having(DSL.count(POSTS.ID).greaterThan(0))
        .orderBy(DSL.count(POSTS.ID).desc())
        .fetch()
    }
    
    // CTE (Common Table Expression)
    fun getTopUsersWithPostDetails(): List<Record> {
        val topUsers = DSL.name("top_users")
        val topUsersQuery = create.select(
            USERS.ID,
            USERS.NAME,
            DSL.count(POSTS.ID).`as`("post_count")
        )
        .from(USERS)
        .join(POSTS).on(USERS.ID.eq(POSTS.USER_ID))
        .where(POSTS.IS_PUBLISHED.isTrue)
        .groupBy(USERS.ID, USERS.NAME)
        .having(DSL.count(POSTS.ID).greaterThan(5))
        
        return create.with(topUsers).`as`(topUsersQuery)
            .select(
                topUsers.field("name"),
                topUsers.field("post_count"),
                POSTS.TITLE,
                POSTS.PUBLISHED_AT
            )
            .from(topUsers)
            .join(POSTS).on(topUsers.field("id", Int::class.java).eq(POSTS.USER_ID))
            .where(POSTS.IS_PUBLISHED.isTrue)
            .orderBy(
                topUsers.field("post_count").desc(),
                POSTS.PUBLISHED_AT.desc()
            )
            .fetch()
    }
}

// PostgreSQL特有の機能
class PostgreSQLSpecificQueries(private val create: DSLContext) {
    
    // JSON操作
    fun searchUsersByPreferences(key: String, value: String): List<Record> {
        // preferencesカラムがJSONB型の場合
        return create.select(USERS.ID, USERS.NAME, USERS.EMAIL)
            .from(USERS)
            .where(DSL.condition("preferences ->> {0} = {1}", key, value))
            .fetch()
    }
    
    // 配列操作
    fun findUsersWithSkill(skill: String): List<Record> {
        // skillsカラムが配列型の場合
        return create.select(USERS.ID, USERS.NAME)
            .from(USERS)
            .where(DSL.condition("{0} = ANY(skills)", skill))
            .fetch()
    }
    
    // 全文検索
    fun fullTextSearch(query: String): List<Record> {
        return create.select(
            POSTS.ID,
            POSTS.TITLE,
            POSTS.CONTENT,
            DSL.function("ts_rank", Double::class.java,
                DSL.function("to_tsvector", Any::class.java, 
                    DSL.val("english"), POSTS.CONTENT),
                DSL.function("plainto_tsquery", Any::class.java, 
                    DSL.val("english"), DSL.val(query))
            ).`as`("rank")
        )
        .from(POSTS)
        .where(DSL.condition(
            "to_tsvector('english', content) @@ plainto_tsquery('english', {0})",
            query
        ))
        .orderBy(DSL.field("rank").desc())
        .fetch()
    }
}

トランザクションとバッチ処理

// トランザクション管理
class TransactionalService(private val create: DSLContext) {
    
    // 基本的なトランザクション
    fun createUserWithProfile(
        name: String,
        email: String,
        age: Int,
        bio: String?
    ): Result<UsersRecord> {
        return try {
            create.transactionResult { configuration ->
                val transactionalCreate = DSL.using(configuration)
                
                // ユーザー作成
                val user = transactionalCreate.insertInto(USERS)
                    .set(USERS.NAME, name)
                    .set(USERS.EMAIL, email)
                    .set(USERS.AGE, age)
                    .returning()
                    .fetchOne()!!
                
                // プロフィール作成(仮想的なテーブル)
                bio?.let {
                    transactionalCreate.insertInto(DSL.table("user_profiles"))
                        .set(DSL.field("user_id"), user.id)
                        .set(DSL.field("bio"), it)
                        .execute()
                }
                
                user
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    // 複雑なトランザクション処理
    fun transferPosts(fromUserId: Int, toUserId: Int): Result<Int> {
        return try {
            create.transactionResult { configuration ->
                val ctx = DSL.using(configuration)
                
                // 移行元ユーザーの存在確認
                val fromUser = ctx.selectFrom(USERS)
                    .where(USERS.ID.eq(fromUserId))
                    .fetchOne() 
                    ?: throw IllegalArgumentException("Source user not found")
                
                // 移行先ユーザーの存在確認
                val toUser = ctx.selectFrom(USERS)
                    .where(USERS.ID.eq(toUserId))
                    .fetchOne()
                    ?: throw IllegalArgumentException("Target user not found")
                
                // 投稿の移行
                val transferredCount = ctx.update(POSTS)
                    .set(POSTS.USER_ID, toUserId)
                    .set(POSTS.UPDATED_AT, DSL.currentTimestamp())
                    .where(POSTS.USER_ID.eq(fromUserId))
                    .execute()
                
                // 移行元ユーザーの無効化
                ctx.update(USERS)
                    .set(USERS.IS_ACTIVE, false)
                    .set(USERS.UPDATED_AT, DSL.currentTimestamp())
                    .where(USERS.ID.eq(fromUserId))
                    .execute()
                
                transferredCount
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    // バッチ処理
    fun batchInsertUsers(users: List<UserData>): Result<IntArray> {
        return try {
            val result = create.transactionResult { configuration ->
                val ctx = DSL.using(configuration)
                
                val batch = ctx.batch(
                    ctx.insertInto(USERS)
                        .set(USERS.NAME, DSL.param("name", String::class.java))
                        .set(USERS.EMAIL, DSL.param("email", String::class.java))
                        .set(USERS.AGE, DSL.param("age", Int::class.java))
                )
                
                users.forEach { userData ->
                    batch.bind(userData.name, userData.email, userData.age)
                }
                
                batch.execute()
            }
            Result.success(result)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    // バッチ更新
    fun batchUpdateUserStatus(userIds: List<Int>, isActive: Boolean): Result<IntArray> {
        return try {
            val result = create.transactionResult { configuration ->
                val ctx = DSL.using(configuration)
                
                val batch = ctx.batch(
                    ctx.update(USERS)
                        .set(USERS.IS_ACTIVE, DSL.param("isActive", Boolean::class.java))
                        .set(USERS.UPDATED_AT, DSL.currentTimestamp())
                        .where(USERS.ID.eq(DSL.param("id", Int::class.java)))
                )
                
                userIds.forEach { userId ->
                    batch.bind(isActive, userId)
                }
                
                batch.execute()
            }
            Result.success(result)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

// データクラス
data class UserData(
    val name: String,
    val email: String,
    val age: Int
)

// 結果型
sealed class Result<out T> {
    data class Success<T>(val value: T) : Result<T>()
    data class Failure(val error: Throwable) : Result<Nothing>()
    
    companion object {
        fun <T> success(value: T): Result<T> = Success(value)
        fun failure(error: Throwable): Result<Nothing> = Failure(error)
    }
    
    inline fun <R> map(transform: (T) -> R): Result<R> = when (this) {
        is Success -> success(transform(value))
        is Failure -> this
    }
    
    inline fun <R> flatMap(transform: (T) -> Result<R>): Result<R> = when (this) {
        is Success -> transform(value)
        is Failure -> this
    }
}

Kotlin Coroutinesとの統合

// Coroutines対応のリポジトリ
import kotlinx.coroutines.*
import org.jooq.kotlin.coroutines.*

class AsyncUserRepository(private val create: DSLContext) {
    
    // 非同期ユーザー検索
    suspend fun findUserByIdAsync(id: Int): UsersRecord? = withContext(Dispatchers.IO) {
        create.selectFrom(USERS)
            .where(USERS.ID.eq(id))
            .awaitSingle()
    }
    
    // 非同期ユーザー一覧取得
    suspend fun findAllUsersAsync(): List<UsersRecord> = withContext(Dispatchers.IO) {
        create.selectFrom(USERS)
            .where(USERS.IS_ACTIVE.isTrue)
            .orderBy(USERS.CREATED_AT.desc())
            .awaitAll()
    }
    
    // 非同期ユーザー作成
    suspend fun insertUserAsync(name: String, email: String, age: Int): UsersRecord = 
        withContext(Dispatchers.IO) {
            create.insertInto(USERS)
                .set(USERS.NAME, name)
                .set(USERS.EMAIL, email)
                .set(USERS.AGE, age)
                .returning()
                .awaitSingle()
        }
    
    // 並行処理による一括データ取得
    suspend fun getUsersWithPostsAsync(userIds: List<Int>): Map<UsersRecord, List<PostsRecord>> = 
        coroutineScope {
            val userDeferreds = userIds.map { userId ->
                async {
                    val user = create.selectFrom(USERS)
                        .where(USERS.ID.eq(userId))
                        .awaitSingleOrNull()
                    
                    val posts = user?.let {
                        create.selectFrom(POSTS)
                            .where(POSTS.USER_ID.eq(userId))
                            .awaitAll()
                    } ?: emptyList()
                    
                    user to posts
                }
            }
            
            userDeferreds.awaitAll()
                .mapNotNull { (user, posts) -> user?.let { it to posts } }
                .toMap()
        }
    
    // ストリーミング処理
    suspend fun processAllUsersStream(processor: suspend (UsersRecord) -> Unit) {
        withContext(Dispatchers.IO) {
            create.selectFrom(USERS)
                .where(USERS.IS_ACTIVE.isTrue)
                .awaitStream()
                .collect { user ->
                    processor(user)
                }
        }
    }
}

// 非同期サービス層
class AsyncUserService(
    private val userRepository: AsyncUserRepository,
    private val create: DSLContext
) {
    
    // 非同期ユーザー作成とバリデーション
    suspend fun createUserSafely(
        name: String,
        email: String,
        age: Int
    ): Result<UsersRecord> = try {
        // 重複チェック
        val existingUser = create.selectFrom(USERS)
            .where(USERS.EMAIL.eq(email))
            .awaitSingleOrNull()
        
        if (existingUser != null) {
            Result.failure(IllegalArgumentException("Email already exists"))
        } else {
            val user = userRepository.insertUserAsync(name, email, age)
            Result.success(user)
        }
    } catch (e: Exception) {
        Result.failure(e)
    }
    
    // 非同期検索とフィルタリング
    suspend fun searchUsersAsync(
        nameQuery: String? = null,
        minAge: Int? = null,
        maxAge: Int? = null,
        limit: Int = 100
    ): List<UsersRecord> = withContext(Dispatchers.IO) {
        var condition = USERS.IS_ACTIVE.isTrue
        
        nameQuery?.let { 
            condition = condition.and(USERS.NAME.likeIgnoreCase("%$it%"))
        }
        
        minAge?.let {
            condition = condition.and(USERS.AGE.greaterOrEqual(it))
        }
        
        maxAge?.let {
            condition = condition.and(USERS.AGE.lessOrEqual(it))
        }
        
        create.selectFrom(USERS)
            .where(condition)
            .limit(limit)
            .orderBy(USERS.NAME.asc())
            .awaitAll()
    }
    
    // 並列バッチ処理
    suspend fun processUsersBatch(
        batchSize: Int = 100,
        processor: suspend (List<UsersRecord>) -> Unit
    ) = coroutineScope {
        val totalUsers = create.selectCount()
            .from(USERS)
            .where(USERS.IS_ACTIVE.isTrue)
            .awaitSingle()
        
        val batches = (0 until totalUsers step batchSize)
        
        batches.map { offset ->
            async {
                val batch = create.selectFrom(USERS)
                    .where(USERS.IS_ACTIVE.isTrue)
                    .limit(batchSize)
                    .offset(offset)
                    .awaitAll()
                
                processor(batch)
            }
        }.awaitAll()
    }
}

// 使用例
suspend fun demonstrateAsyncUsage() {
    val dataSource = DatabaseConfig.createDataSource()
    val create = DatabaseConfig.createContext(dataSource)
    val asyncRepository = AsyncUserRepository(create)
    val asyncService = AsyncUserService(asyncRepository, create)
    
    // 非同期ユーザー作成
    val userResult = asyncService.createUserSafely(
        name = "非同期太郎",
        email = "[email protected]",
        age = 25
    )
    
    when (userResult) {
        is Result.Success -> println("User created: ${userResult.value.name}")
        is Result.Failure -> println("Error: ${userResult.error.message}")
    }
    
    // 並行検索
    val searchResults = asyncService.searchUsersAsync(
        nameQuery = "太郎",
        minAge = 20
    )
    println("Found ${searchResults.size} users")
    
    // バッチ処理
    asyncService.processUsersBatch { batch ->
        println("Processing batch of ${batch.size} users")
        // 各バッチの処理
    }
}

エラーハンドリング

// 包括的なエラーハンドリング
import org.jooq.exception.*

class RobustUserService(private val create: DSLContext) {
    
    // 安全なユーザー作成
    fun createUserSafely(name: String, email: String, age: Int): DatabaseResult<UsersRecord> {
        return try {
            // 入力値検証
            validateUserInput(name, email, age).let { validation ->
                if (validation != null) {
                    return DatabaseResult.ValidationError(validation)
                }
            }
            
            // トランザクション内で実行
            val user = create.transactionResult { configuration ->
                val ctx = DSL.using(configuration)
                
                // 重複チェック
                val existingUser = ctx.selectFrom(USERS)
                    .where(USERS.EMAIL.eq(email))
                    .fetchOne()
                
                if (existingUser != null) {
                    throw DuplicateEmailException("Email $email already exists")
                }
                
                // ユーザー作成
                ctx.insertInto(USERS)
                    .set(USERS.NAME, name)
                    .set(USERS.EMAIL, email)
                    .set(USERS.AGE, age)
                    .returning()
                    .fetchOne()!!
            }
            
            DatabaseResult.Success(user)
            
        } catch (e: DuplicateEmailException) {
            DatabaseResult.BusinessError(e.message ?: "Duplicate email")
        } catch (e: DataAccessException) {
            when (e) {
                is DataChangedException -> 
                    DatabaseResult.ConcurrencyError("Data was modified by another transaction")
                is IntegrityConstraintViolationException -> 
                    DatabaseResult.ConstraintError("Database constraint violation: ${e.message}")
                else -> 
                    DatabaseResult.DatabaseError("Database error: ${e.message}")
            }
        } catch (e: Exception) {
            DatabaseResult.UnknownError("Unexpected error: ${e.message}")
        }
    }
    
    // 安全なユーザー更新
    fun updateUserSafely(
        id: Int,
        name: String? = null,
        email: String? = null,
        age: Int? = null
    ): DatabaseResult<Int> {
        return try {
            // 入力値検証
            if (name != null || email != null || age != null) {
                validateUserUpdateInput(name, email, age).let { validation ->
                    if (validation != null) {
                        return DatabaseResult.ValidationError(validation)
                    }
                }
            }
            
            val updatedRows = create.transactionResult { configuration ->
                val ctx = DSL.using(configuration)
                
                // 存在確認
                val existingUser = ctx.selectFrom(USERS)
                    .where(USERS.ID.eq(id))
                    .fetchOne()
                    ?: throw UserNotFoundException("User with ID $id not found")
                
                // メール重複チェック
                email?.let { newEmail ->
                    val duplicateUser = ctx.selectFrom(USERS)
                        .where(USERS.EMAIL.eq(newEmail))
                        .and(USERS.ID.ne(id))
                        .fetchOne()
                    
                    if (duplicateUser != null) {
                        throw DuplicateEmailException("Email $newEmail already exists")
                    }
                }
                
                // 更新実行
                val updateQuery = ctx.update(USERS)
                
                name?.let { updateQuery.set(USERS.NAME, it) }
                email?.let { updateQuery.set(USERS.EMAIL, it) }
                age?.let { updateQuery.set(USERS.AGE, it) }
                
                updateQuery.set(USERS.UPDATED_AT, DSL.currentTimestamp())
                    .where(USERS.ID.eq(id))
                    .execute()
            }
            
            DatabaseResult.Success(updatedRows)
            
        } catch (e: UserNotFoundException) {
            DatabaseResult.NotFoundError(e.message ?: "User not found")
        } catch (e: DuplicateEmailException) {
            DatabaseResult.BusinessError(e.message ?: "Duplicate email")
        } catch (e: DataAccessException) {
            DatabaseResult.DatabaseError("Database error: ${e.message}")
        } catch (e: Exception) {
            DatabaseResult.UnknownError("Unexpected error: ${e.message}")
        }
    }
    
    // データベース接続の健全性チェック
    fun checkDatabaseHealth(): DatabaseResult<Map<String, Any>> {
        return try {
            val stats = mutableMapOf<String, Any>()
            
            // 基本接続テスト
            val connectionTest = create.selectOne().fetch()
            stats["connectionStatus"] = "OK"
            
            // テーブル存在確認
            val tableExists = create.meta().tables.any { it.name == "users" }
            stats["tableExists"] = tableExists
            
            // レコード数チェック
            val userCount = create.selectCount().from(USERS).fetchOne(0, Int::class.java)
            stats["userCount"] = userCount ?: 0
            
            // 最新レコードの確認
            val latestUser = create.select(USERS.CREATED_AT)
                .from(USERS)
                .orderBy(USERS.CREATED_AT.desc())
                .limit(1)
                .fetchOne()
            
            stats["latestUserDate"] = latestUser?.get(USERS.CREATED_AT)?.toString() ?: "No users"
            
            DatabaseResult.Success(stats.toMap())
            
        } catch (e: Exception) {
            DatabaseResult.DatabaseError("Health check failed: ${e.message}")
        }
    }
    
    // バッチ操作の安全な実行
    fun safeBatchOperation(
        operations: List<BatchOperation>
    ): DatabaseResult<BatchResult> {
        return try {
            val result = create.transactionResult { configuration ->
                val ctx = DSL.using(configuration)
                val batchResult = BatchResult()
                
                operations.forEach { operation ->
                    try {
                        when (operation) {
                            is BatchOperation.CreateUser -> {
                                val user = ctx.insertInto(USERS)
                                    .set(USERS.NAME, operation.name)
                                    .set(USERS.EMAIL, operation.email)
                                    .set(USERS.AGE, operation.age)
                                    .returning()
                                    .fetchOne()
                                
                                batchResult.successful.add(operation)
                                batchResult.results[operation] = user
                            }
                            
                            is BatchOperation.UpdateUser -> {
                                val updated = ctx.update(USERS)
                                    .set(USERS.NAME, operation.name)
                                    .set(USERS.UPDATED_AT, DSL.currentTimestamp())
                                    .where(USERS.ID.eq(operation.id))
                                    .execute()
                                
                                if (updated > 0) {
                                    batchResult.successful.add(operation)
                                } else {
                                    batchResult.failed.add(
                                        FailedOperation(operation, "User not found")
                                    )
                                }
                            }
                            
                            is BatchOperation.DeleteUser -> {
                                val deleted = ctx.deleteFrom(USERS)
                                    .where(USERS.ID.eq(operation.id))
                                    .execute()
                                
                                if (deleted > 0) {
                                    batchResult.successful.add(operation)
                                } else {
                                    batchResult.failed.add(
                                        FailedOperation(operation, "User not found")
                                    )
                                }
                            }
                        }
                    } catch (e: Exception) {
                        batchResult.failed.add(
                            FailedOperation(operation, e.message ?: "Unknown error")
                        )
                    }
                }
                
                batchResult
            }
            
            DatabaseResult.Success(result)
            
        } catch (e: Exception) {
            DatabaseResult.DatabaseError("Batch operation failed: ${e.message}")
        }
    }
    
    // プライベートヘルパーメソッド
    private fun validateUserInput(name: String, email: String, age: Int): String? {
        if (name.isBlank()) return "Name cannot be blank"
        if (name.length > 255) return "Name too long (max 255 characters)"
        if (!isValidEmail(email)) return "Invalid email format"
        if (age < 0 || age > 150) return "Age must be between 0 and 150"
        return null
    }
    
    private fun validateUserUpdateInput(name: String?, email: String?, age: Int?): String? {
        name?.let { 
            if (it.isBlank()) return "Name cannot be blank"
            if (it.length > 255) return "Name too long (max 255 characters)"
        }
        email?.let { 
            if (!isValidEmail(it)) return "Invalid email format"
        }
        age?.let { 
            if (it < 0 || it > 150) return "Age must be between 0 and 150"
        }
        return null
    }
    
    private fun isValidEmail(email: String): Boolean {
        return email.matches(Regex("[^@]+@[^@]+\\.[^@]+"))
    }
}

// カスタム例外クラス
class UserNotFoundException(message: String) : Exception(message)
class DuplicateEmailException(message: String) : Exception(message)

// 結果型
sealed class DatabaseResult<out T> {
    data class Success<T>(val data: T) : DatabaseResult<T>()
    data class ValidationError(val message: String) : DatabaseResult<Nothing>()
    data class BusinessError(val message: String) : DatabaseResult<Nothing>()
    data class NotFoundError(val message: String) : DatabaseResult<Nothing>()
    data class ConstraintError(val message: String) : DatabaseResult<Nothing>()
    data class ConcurrencyError(val message: String) : DatabaseResult<Nothing>()
    data class DatabaseError(val message: String) : DatabaseResult<Nothing>()
    data class UnknownError(val message: String) : DatabaseResult<Nothing>()
    
    fun isSuccess(): Boolean = this is Success
    fun isError(): Boolean = this !is Success
    
    inline fun <R> map(transform: (T) -> R): DatabaseResult<R> = when (this) {
        is Success -> Success(transform(data))
        else -> this as DatabaseResult<R>
    }
    
    inline fun onSuccess(action: (T) -> Unit): DatabaseResult<T> {
        if (this is Success) action(data)
        return this
    }
    
    inline fun onError(action: (String) -> Unit): DatabaseResult<T> {
        when (this) {
            is ValidationError -> action(message)
            is BusinessError -> action(message)
            is NotFoundError -> action(message)
            is ConstraintError -> action(message)
            is ConcurrencyError -> action(message)
            is DatabaseError -> action(message)
            is UnknownError -> action(message)
            else -> {}
        }
        return this
    }
}

// バッチ操作関連クラス
sealed class BatchOperation {
    data class CreateUser(val name: String, val email: String, val age: Int) : BatchOperation()
    data class UpdateUser(val id: Int, val name: String) : BatchOperation()
    data class DeleteUser(val id: Int) : BatchOperation()
}

data class BatchResult(
    val successful: MutableList<BatchOperation> = mutableListOf(),
    val failed: MutableList<FailedOperation> = mutableListOf(),
    val results: MutableMap<BatchOperation, Any?> = mutableMapOf()
)

data class FailedOperation(
    val operation: BatchOperation,
    val error: String
)