Source profileQuality 79/100

affaan-m/ECC/docs/zh-CN/skills/kotlin-exposed-patterns/SKILL.md

kotlin-exposed-patterns

Review kotlin-exposed-patterns's use cases, installation, workflow, and original source instructions.

Source repository stars
234,327
Declared platforms
0
Static risk flags
0
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

使用 JetBrains Exposed ORM 进行数据库访问的全面模式,包括 DSL 查询、DAO、事务以及生产就绪的配置。

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/affaan-m/ECC --skill "docs/zh-CN/skills/kotlin-exposed-patterns"
    Safe inspection promptEditorial

    Inspect the Agent Skill "kotlin-exposed-patterns" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/docs/zh-CN/skills/kotlin-exposed-patterns/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      何时使用

      使用 Exposed 设置数据库访问 使用 Exposed DSL 或 DAO 编写 SQL 查询 使用 HikariCP 配置连接池 使用 Flyway 创建数据库迁移 使用 Exposed 实现仓储模式 处理 JSON 列和复杂查询

      使用 Exposed 设置数据库访问使用 Exposed DSL 或 DAO 编写 SQL 查询使用 HikariCP 配置连接池
    2. 02

      工作原理

      Exposed 提供两种查询风格:用于直接类似 SQL 表达式的 DSL 和用于实体生命周期管理的 DAO。HikariCP 通过 HikariConfig 配置来管理可重用的数据库连接池。Flyway 在启动时运行版本化的 SQL 迁移脚本以保持模式同步。所有数据库操作都在 newSuspendedTransaction 块内运行,以确保协程安全和原子性。仓储模式将 Exposed 查询包装在接口之后,使业务逻辑与数据层解耦,并且测试可以使用内存中的 H2 数据库。

      Exposed 提供两种查询风格:用于直接类似 SQL 表达式的 DSL 和用于实体生命周期管理的 DAO。HikariCP 通过 HikariConfig 配置来管理可重用的数据库连接池。Flyway 在启动时运行版本化的 SQL 迁移脚本以保持模式同步。所有数据库操作都在 newSuspendedTransaction 块内运行,以确保协程安全和原子性。仓储模式将 Exposed 查询包装在接口之后,使业务逻辑与数据层解耦,并且测…
    3. 03

      示例

      Review the “示例” section in the pinned source before continuing.

      Review and apply the “示例” source section.
    4. 04

      DSL 查询

      Review the “DSL 查询” section in the pinned source before continuing.

      Review and apply the “DSL 查询” source section.
    5. 05

      DAO 实体用法

      Review the “DAO 实体用法” section in the pinned source before continuing.

      Review and apply the “DAO 实体用法” source section.

    Permission review

    Static risk signals and limitations

    No configured static risk pattern was detected

    This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score79/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars234,327SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    affaan-m/ECC
    Skill path
    docs/zh-CN/skills/kotlin-exposed-patterns/SKILL.md
    Commit
    4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    Kotlin Exposed 模式

    使用 JetBrains Exposed ORM 进行数据库访问的全面模式,包括 DSL 查询、DAO、事务以及生产就绪的配置。

    何时使用

    • 使用 Exposed 设置数据库访问
    • 使用 Exposed DSL 或 DAO 编写 SQL 查询
    • 使用 HikariCP 配置连接池
    • 使用 Flyway 创建数据库迁移
    • 使用 Exposed 实现仓储模式
    • 处理 JSON 列和复杂查询

    工作原理

    Exposed 提供两种查询风格:用于直接类似 SQL 表达式的 DSL 和用于实体生命周期管理的 DAO。HikariCP 通过 HikariConfig 配置来管理可重用的数据库连接池。Flyway 在启动时运行版本化的 SQL 迁移脚本以保持模式同步。所有数据库操作都在 newSuspendedTransaction 块内运行,以确保协程安全和原子性。仓储模式将 Exposed 查询包装在接口之后,使业务逻辑与数据层解耦,并且测试可以使用内存中的 H2 数据库。

    示例

    DSL 查询

    suspend fun findUserById(id: UUID): UserRow? =
        newSuspendedTransaction {
            UsersTable.selectAll()
                .where { UsersTable.id eq id }
                .map { it.toUser() }
                .singleOrNull()
        }
    

    DAO 实体用法

    suspend fun createUser(request: CreateUserRequest): User =
        newSuspendedTransaction {
            UserEntity.new {
                name = request.name
                email = request.email
                role = request.role
            }.toModel()
        }
    

    HikariCP 配置

    val hikariConfig = HikariConfig().apply {
        driverClassName = config.driver
        jdbcUrl = config.url
        username = config.username
        password = config.password
        maximumPoolSize = config.maxPoolSize
        isAutoCommit = false
        transactionIsolation = "TRANSACTION_READ_COMMITTED"
        validate()
    }
    

    数据库设置

    HikariCP 连接池

    // DatabaseFactory.kt
    object DatabaseFactory {
        fun create(config: DatabaseConfig): Database {
            val hikariConfig = HikariConfig().apply {
                driverClassName = config.driver
                jdbcUrl = config.url
                username = config.username
                password = config.password
                maximumPoolSize = config.maxPoolSize
                isAutoCommit = false
                transactionIsolation = "TRANSACTION_READ_COMMITTED"
                validate()
            }
    
            return Database.connect(HikariDataSource(hikariConfig))
        }
    }
    
    data class DatabaseConfig(
        val url: String,
        val driver: String = "org.postgresql.Driver",
        val username: String = "",
        val password: String = "",
        val maxPoolSize: Int = 10,
    )
    

    Flyway 迁移

    // FlywayMigration.kt
    fun runMigrations(config: DatabaseConfig) {
        Flyway.configure()
            .dataSource(config.url, config.username, config.password)
            .locations("classpath:db/migration")
            .baselineOnMigrate(true)
            .load()
            .migrate()
    }
    
    // Application startup
    fun Application.module() {
        val config = DatabaseConfig(
            url = environment.config.property("database.url").getString(),
            username = environment.config.property("database.username").getString(),
            password = environment.config.property("database.password").getString(),
        )
        runMigrations(config)
        val database = DatabaseFactory.create(config)
        // ...
    }
    

    迁移文件

    -- src/main/resources/db/migration/V1__create_users.sql
    CREATE TABLE users (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        name VARCHAR(100) NOT NULL,
        email VARCHAR(255) NOT NULL UNIQUE,
        role VARCHAR(20) NOT NULL DEFAULT 'USER',
        metadata JSONB,
        created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
        updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
    );
    
    CREATE INDEX idx_users_email ON users(email);
    CREATE INDEX idx_users_role ON users(role);
    

    表定义

    DSL 风格表

    // tables/UsersTable.kt
    object UsersTable : UUIDTable("users") {
        val name = varchar("name", 100)
        val email = varchar("email", 255).uniqueIndex()
        val role = enumerationByName<Role>("role", 20)
        val metadata = jsonb<UserMetadata>("metadata", Json.Default).nullable()
        val createdAt = timestampWithTimeZone("created_at").defaultExpression(CurrentTimestampWithTimeZone)
        val updatedAt = timestampWithTimeZone("updated_at").defaultExpression(CurrentTimestampWithTimeZone)
    }
    
    object OrdersTable : UUIDTable("orders") {
        val userId = uuid("user_id").references(UsersTable.id)
        val status = enumerationByName<OrderStatus>("status", 20)
        val totalAmount = long("total_amount")
        val currency = varchar("currency", 3)
        val createdAt = timestampWithTimeZone("created_at").defaultExpression(CurrentTimestampWithTimeZone)
    }
    
    object OrderItemsTable : UUIDTable("order_items") {
        val orderId = uuid("order_id").references(OrdersTable.id, onDelete = ReferenceOption.CASCADE)
        val productId = uuid("product_id")
        val quantity = integer("quantity")
        val unitPrice = long("unit_price")
    }
    

    复合表

    object UserRolesTable : Table("user_roles") {
        val userId = uuid("user_id").references(UsersTable.id, onDelete = ReferenceOption.CASCADE)
        val roleId = uuid("role_id").references(RolesTable.id, onDelete = ReferenceOption.CASCADE)
        override val primaryKey = PrimaryKey(userId, roleId)
    }
    

    DSL 查询

    基本 CRUD

    // Insert
    suspend fun insertUser(name: String, email: String, role: Role): UUID =
        newSuspendedTransaction {
            UsersTable.insertAndGetId {
                it[UsersTable.name] = name
                it[UsersTable.email] = email
                it[UsersTable.role] = role
            }.value
        }
    
    // Select by ID
    suspend fun findUserById(id: UUID): UserRow? =
        newSuspendedTransaction {
            UsersTable.selectAll()
                .where { UsersTable.id eq id }
                .map { it.toUser() }
                .singleOrNull()
        }
    
    // Select with conditions
    suspend fun findActiveAdmins(): List<UserRow> =
        newSuspendedTransaction {
            UsersTable.selectAll()
                .where { (UsersTable.role eq Role.ADMIN) }
                .orderBy(UsersTable.name)
                .map { it.toUser() }
        }
    
    // Update
    suspend fun updateUserEmail(id: UUID, newEmail: String): Boolean =
        newSuspendedTransaction {
            UsersTable.update({ UsersTable.id eq id }) {
                it[email] = newEmail
                it[updatedAt] = CurrentTimestampWithTimeZone
            } > 0
        }
    
    // Delete
    suspend fun deleteUser(id: UUID): Boolean =
        newSuspendedTransaction {
            UsersTable.deleteWhere { UsersTable.id eq id } > 0
        }
    
    // Row mapping
    private fun ResultRow.toUser() = UserRow(
        id = this[UsersTable.id].value,
        name = this[UsersTable.name],
        email = this[UsersTable.email],
        role = this[UsersTable.role],
        metadata = this[UsersTable.metadata],
        createdAt = this[UsersTable.createdAt],
        updatedAt = this[UsersTable.updatedAt],
    )
    

    高级查询

    // Join queries
    suspend fun findOrdersWithUser(userId: UUID): List<OrderWithUser> =
        newSuspendedTransaction {
            (OrdersTable innerJoin UsersTable)
                .selectAll()
                .where { OrdersTable.userId eq userId }
                .orderBy(OrdersTable.createdAt, SortOrder.DESC)
                .map { row ->
                    OrderWithUser(
                        orderId = row[OrdersTable.id].value,
                        status = row[OrdersTable.status],
                        totalAmount = row[OrdersTable.totalAmount],
                        userName = row[UsersTable.name],
                    )
                }
        }
    
    // Aggregation
    suspend fun countUsersByRole(): Map<Role, Long> =
        newSuspendedTransaction {
            UsersTable
                .select(UsersTable.role, UsersTable.id.count())
                .groupBy(UsersTable.role)
                .associate { row ->
                    row[UsersTable.role] to row[UsersTable.id.count()]
                }
        }
    
    // Subqueries
    suspend fun findUsersWithOrders(): List<UserRow> =
        newSuspendedTransaction {
            UsersTable.selectAll()
                .where {
                    UsersTable.id inSubQuery
                        OrdersTable.select(OrdersTable.userId).withDistinct()
                }
                .map { it.toUser() }
        }
    
    // LIKE and pattern matching — always escape user input to prevent wildcard injection
    private fun escapeLikePattern(input: String): String =
        input.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
    
    suspend fun searchUsers(query: String): List<UserRow> =
        newSuspendedTransaction {
            val sanitized = escapeLikePattern(query.lowercase())
            UsersTable.selectAll()
                .where {
                    (UsersTable.name.lowerCase() like "%${sanitized}%") or
                        (UsersTable.email.lowerCase() like "%${sanitized}%")
                }
                .map { it.toUser() }
        }
    

    分页

    data class Page<T>(
        val data: List<T>,
        val total: Long,
        val page: Int,
        val limit: Int,
    ) {
        val totalPages: Int get() = ((total + limit - 1) / limit).toInt()
        val hasNext: Boolean get() = page < totalPages
        val hasPrevious: Boolean get() = page > 1
    }
    
    suspend fun findUsersPaginated(page: Int, limit: Int): Page<UserRow> =
        newSuspendedTransaction {
            val total = UsersTable.selectAll().count()
            val data = UsersTable.selectAll()
                .orderBy(UsersTable.createdAt, SortOrder.DESC)
                .limit(limit)
                .offset(((page - 1) * limit).toLong())
                .map { it.toUser() }
    
            Page(data = data, total = total, page = page, limit = limit)
        }
    

    批量操作

    // Batch insert
    suspend fun insertUsers(users: List<CreateUserRequest>): List<UUID> =
        newSuspendedTransaction {
            UsersTable.batchInsert(users) { user ->
                this[UsersTable.name] = user.name
                this[UsersTable.email] = user.email
                this[UsersTable.role] = user.role
            }.map { it[UsersTable.id].value }
        }
    
    // Upsert (insert or update on conflict)
    suspend fun upsertUser(id: UUID, name: String, email: String) {
        newSuspendedTransaction {
            UsersTable.upsert(UsersTable.email) {
                it[UsersTable.id] = EntityID(id, UsersTable)
                it[UsersTable.name] = name
                it[UsersTable.email] = email
                it[updatedAt] = CurrentTimestampWithTimeZone
            }
        }
    }
    

    DAO 模式

    实体定义

    // entities/UserEntity.kt
    class UserEntity(id: EntityID<UUID>) : UUIDEntity(id) {
        companion object : UUIDEntityClass<UserEntity>(UsersTable)
    
        var name by UsersTable.name
        var email by UsersTable.email
        var role by UsersTable.role
        var metadata by UsersTable.metadata
        var createdAt by UsersTable.createdAt
        var updatedAt by UsersTable.updatedAt
    
        val orders by OrderEntity referrersOn OrdersTable.userId
    
        fun toModel(): User = User(
            id = id.value,
            name = name,
            email = email,
            role = role,
            metadata = metadata,
            createdAt = createdAt,
            updatedAt = updatedAt,
        )
    }
    
    class OrderEntity(id: EntityID<UUID>) : UUIDEntity(id) {
        companion object : UUIDEntityClass<OrderEntity>(OrdersTable)
    
        var user by UserEntity referencedOn OrdersTable.userId
        var status by OrdersTable.status
        var totalAmount by OrdersTable.totalAmount
        var currency by OrdersTable.currency
        var createdAt by OrdersTable.createdAt
    
        val items by OrderItemEntity referrersOn OrderItemsTable.orderId
    }
    

    DAO 操作

    suspend fun findUserByEmail(email: String): User? =
        newSuspendedTransaction {
            UserEntity.find { UsersTable.email eq email }
                .firstOrNull()
                ?.toModel()
        }
    
    suspend fun createUser(request: CreateUserRequest): User =
        newSuspendedTransaction {
            UserEntity.new {
                name = request.name
                email = request.email
                role = request.role
            }.toModel()
        }
    
    suspend fun updateUser(id: UUID, request: UpdateUserRequest): User? =
        newSuspendedTransaction {
            UserEntity.findById(id)?.apply {
                request.name?.let { name = it }
                request.email?.let { email = it }
                updatedAt = OffsetDateTime.now(ZoneOffset.UTC)
            }?.toModel()
        }
    

    事务

    挂起事务支持

    // Good: Use newSuspendedTransaction for coroutine support
    suspend fun performDatabaseOperation(): Result<User> =
        runCatching {
            newSuspendedTransaction {
                val user = UserEntity.new {
                    name = "Alice"
                    email = "alice@example.com"
                }
                // All operations in this block are atomic
                user.toModel()
            }
        }
    
    // Good: Nested transactions with savepoints
    suspend fun transferFunds(fromId: UUID, toId: UUID, amount: Long) {
        newSuspendedTransaction {
            val from = UserEntity.findById(fromId) ?: throw NotFoundException("User $fromId not found")
            val to = UserEntity.findById(toId) ?: throw NotFoundException("User $toId not found")
    
            // Debit
            from.balance -= amount
            // Credit
            to.balance += amount
    
            // Both succeed or both fail
        }
    }
    

    事务隔离级别

    suspend fun readCommittedQuery(): List<User> =
        newSuspendedTransaction(transactionIsolation = Connection.TRANSACTION_READ_COMMITTED) {
            UserEntity.all().map { it.toModel() }
        }
    
    suspend fun serializableOperation() {
        newSuspendedTransaction(transactionIsolation = Connection.TRANSACTION_SERIALIZABLE) {
            // Strictest isolation level for critical operations
        }
    }
    

    仓储模式

    接口定义

    interface UserRepository {
        suspend fun findById(id: UUID): User?
        suspend fun findByEmail(email: String): User?
        suspend fun findAll(page: Int, limit: Int): Page<User>
        suspend fun search(query: String): List<User>
        suspend fun create(request: CreateUserRequest): User
        suspend fun update(id: UUID, request: UpdateUserRequest): User?
        suspend fun delete(id: UUID): Boolean
        suspend fun count(): Long
    }
    

    Exposed 实现

    class ExposedUserRepository(
        private val database: Database,
    ) : UserRepository {
    
        override suspend fun findById(id: UUID): User? =
            newSuspendedTransaction(db = database) {
                UsersTable.selectAll()
                    .where { UsersTable.id eq id }
                    .map { it.toUser() }
                    .singleOrNull()
            }
    
        override suspend fun findByEmail(email: String): User? =
            newSuspendedTransaction(db = database) {
                UsersTable.selectAll()
                    .where { UsersTable.email eq email }
                    .map { it.toUser() }
                    .singleOrNull()
            }
    
        override suspend fun findAll(page: Int, limit: Int): Page<User> =
            newSuspendedTransaction(db = database) {
                val total = UsersTable.selectAll().count()
                val data = UsersTable.selectAll()
                    .orderBy(UsersTable.createdAt, SortOrder.DESC)
                    .limit(limit)
                    .offset(((page - 1) * limit).toLong())
                    .map { it.toUser() }
                Page(data = data, total = total, page = page, limit = limit)
            }
    
        override suspend fun search(query: String): List<User> =
            newSuspendedTransaction(db = database) {
                val sanitized = escapeLikePattern(query.lowercase())
                UsersTable.selectAll()
                    .where {
                        (UsersTable.name.lowerCase() like "%${sanitized}%") or
                            (UsersTable.email.lowerCase() like "%${sanitized}%")
                    }
                    .orderBy(UsersTable.name)
                    .map { it.toUser() }
            }
    
        override suspend fun create(request: CreateUserRequest): User =
            newSuspendedTransaction(db = database) {
                UsersTable.insert {
                    it[name] = request.name
                    it[email] = request.email
                    it[role] = request.role
                }.resultedValues!!.first().toUser()
            }
    
        override suspend fun update(id: UUID, request: UpdateUserRequest): User? =
            newSuspendedTransaction(db = database) {
                val updated = UsersTable.update({ UsersTable.id eq id }) {
                    request.name?.let { name -> it[UsersTable.name] = name }
                    request.email?.let { email -> it[UsersTable.email] = email }
                    it[updatedAt] = CurrentTimestampWithTimeZone
                }
                if (updated > 0) findById(id) else null
            }
    
        override suspend fun delete(id: UUID): Boolean =
            newSuspendedTransaction(db = database) {
                UsersTable.deleteWhere { UsersTable.id eq id } > 0
            }
    
        override suspend fun count(): Long =
            newSuspendedTransaction(db = database) {
                UsersTable.selectAll().count()
            }
    
        private fun ResultRow.toUser() = User(
            id = this[UsersTable.id].value,
            name = this[UsersTable.name],
            email = this[UsersTable.email],
            role = this[UsersTable.role],
            metadata = this[UsersTable.metadata],
            createdAt = this[UsersTable.createdAt],
            updatedAt = this[UsersTable.updatedAt],
        )
    }
    

    JSON 列

    使用 kotlinx.serialization 的 JSONB

    // Custom column type for JSONB
    inline fun <reified T : Any> Table.jsonb(
        name: String,
        json: Json,
    ): Column<T> = registerColumn(name, object : ColumnType<T>() {
        override fun sqlType() = "JSONB"
    
        override fun valueFromDB(value: Any): T = when (value) {
            is String -> json.decodeFromString(value)
            is PGobject -> {
                val jsonString = value.value
                    ?: throw IllegalArgumentException("PGobject value is null for column '$name'")
                json.decodeFromString(jsonString)
            }
            else -> throw IllegalArgumentException("Unexpected value: $value")
        }
    
        override fun notNullValueToDB(value: T): Any =
            PGobject().apply {
                type = "jsonb"
                this.value = json.encodeToString(value)
            }
    })
    
    // Usage in table
    @Serializable
    data class UserMetadata(
        val preferences: Map<String, String> = emptyMap(),
        val tags: List<String> = emptyList(),
    )
    
    object UsersTable : UUIDTable("users") {
        val metadata = jsonb<UserMetadata>("metadata", Json.Default).nullable()
    }
    

    使用 Exposed 进行测试

    用于测试的内存数据库

    class UserRepositoryTest : FunSpec({
        lateinit var database: Database
        lateinit var repository: UserRepository
    
        beforeSpec {
            database = Database.connect(
                url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL",
                driver = "org.h2.Driver",
            )
            transaction(database) {
                SchemaUtils.create(UsersTable)
            }
            repository = ExposedUserRepository(database)
        }
    
        beforeTest {
            transaction(database) {
                UsersTable.deleteAll()
            }
        }
    
        test("create and find user") {
            val user = repository.create(CreateUserRequest("Alice", "alice@example.com"))
    
            user.name shouldBe "Alice"
            user.email shouldBe "alice@example.com"
    
            val found = repository.findById(user.id)
            found shouldBe user
        }
    
        test("findByEmail returns null for unknown email") {
            val result = repository.findByEmail("unknown@example.com")
            result.shouldBeNull()
        }
    
        test("pagination works correctly") {
            repeat(25) { i ->
                repository.create(CreateUserRequest("User $i", "user$i@example.com"))
            }
    
            val page1 = repository.findAll(page = 1, limit = 10)
            page1.data shouldHaveSize 10
            page1.total shouldBe 25
            page1.hasNext shouldBe true
    
            val page3 = repository.findAll(page = 3, limit = 10)
            page3.data shouldHaveSize 5
            page3.hasNext shouldBe false
        }
    })
    

    Gradle 依赖项

    // build.gradle.kts
    dependencies {
        // Exposed
        implementation("org.jetbrains.exposed:exposed-core:1.0.0")
        implementation("org.jetbrains.exposed:exposed-dao:1.0.0")
        implementation("org.jetbrains.exposed:exposed-jdbc:1.0.0")
        implementation("org.jetbrains.exposed:exposed-kotlin-datetime:1.0.0")
        implementation("org.jetbrains.exposed:exposed-json:1.0.0")
    
        // Database driver
        implementation("org.postgresql:postgresql:42.7.5")
    
        // Connection pooling
        implementation("com.zaxxer:HikariCP:6.2.1")
    
        // Migrations
        implementation("org.flywaydb:flyway-core:10.22.0")
        implementation("org.flywaydb:flyway-database-postgresql:10.22.0")
    
        // Testing
        testImplementation("com.h2database:h2:2.3.232")
    }
    

    快速参考:Exposed 模式

    模式描述
    object Table : UUIDTable("name")定义具有 UUID 主键的表
    newSuspendedTransaction { }协程安全的事务块
    Table.selectAll().where { }带条件的查询
    Table.insertAndGetId { }插入并返回生成的 ID
    Table.update({ condition }) { }更新匹配的行
    Table.deleteWhere { }删除匹配的行
    Table.batchInsert(items) { }高效的批量插入
    innerJoin / leftJoin连接表
    orderBy / limit / offset排序和分页
    count() / sum() / avg()聚合函数

    记住:对于简单查询使用 DSL 风格,当需要实体生命周期管理时使用 DAO 风格。始终使用 newSuspendedTransaction 以获得协程支持,并将数据库操作包装在仓储接口之后以提高可测试性。

    Alternatives

    Compare before choosing