ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SQLDelight 自定义投影(Type Projections):用 mapper 与 SQL 掌控查询返回类型

2026/10/8 1:58:56 拓冰建站 浏览量
SQLDelight 自定义投影(Type Projections):用 mapper 与 SQL 掌控查询返回类型 后端ORM【免费下载链接】sqldelightSQLDelight - Generates typesafe Kotlin APIs from SQL项目地址https://gitcode.com/gh_mirrors/sq/sqldelight点击查看免费下载本文基于 SQLDelight 官方文档中的 Type Projections类型投影主题展开该主题被 docs/jvm_h2/custom_projections.md 通过 include 机制共享自 docs/common/custom_projections.md适用于 JVM H2 及其他方言体系的 SQLDelight 项目。默认情况下SQLDelight 会为每条查询生成一个对应的数据类data class作为返回类型本文讲解如何通过mapper参数覆盖这一默认行为以及为什么在绝大多数场景下应当优先用 SQL 完成投影。读完本文你将掌握自定义 mapper 的写法、SQL 端投影的正确姿势以及编译器与运行时背后的映射原理。默认行为查询返回数据类在 SQLDelight 中只要在src/main/sqldelight下的.sq文件中定义了表结构例如 docs/common/index_schema_sq.md 中展示的经典曲棍球球员表示例CREATE TABLE hockeyPlayer ( player_number INTEGER PRIMARY KEY NOT NULL, full_name TEXT NOT NULL );那么针对该表的查询例如SELECT * FROM hockeyPlayer默认就会生成一个携带player_number: Long与full_name: String属性的数据类查询方法直接返回该数据类val selectAll playerQueries.selectAll() println(selectAll.executeAsList()) // 打印 ListHockeyPlayer这一默认行为的代码生成逻辑位于 sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/compiler/SelectQueryGenerator.kt 的defaultResultTypeFunction()编译器把查询结果列作为构造参数直接通过::Data构造函数引用生成return selectAll(::HockeyPlayer)形式的调用从而把游标中的每一行组装成数据类实例。使用 mapper 参数自定义返回类型数据类并非唯一选择。SQLDelight 允许你通过类型安全的 mapper 函数覆盖默认投影行为查询方法会额外接收一个mapper尾随 lambda其参数列表与查询的投影列一一对应返回值由你自行决定。以文档中的示例为基础假设selectAll查询返回player_number与full_name两列你可以这样把结果映射为全大写的姓名val selectAllNames playerQueries.selectAll( mapper { player_number, full_name - full_name.toUppercase() } ) println(selectAllNames.executeAsList()) // 打印 [RYAN GETZLAF, COREY PERRY]关键点在于参数名与列名对应mapper 的每个参数对应查询结果中的一列编译器会根据列类型推导出参数类型包括可空性。例如在 SelectQueryTypeTest.kt 的测试断言中INSERT ... RETURNING *生成的方法签名是fun T : Any insertReturning(mapper: (val1: String?, val2: String?) - T)可空列对应可空参数。泛型返回类型mapper的返回类型是一个类型变量T因此生成的查询方法签名形如fun T : Any selectAll(mapper: (player_number: Long, full_name: String) - T): QueryT你可以返回任意类型——String、自定义领域对象、甚至Pair。仅覆盖类型不改 SQLmapper 只是在结果集读取时做一次 Kotlin 层面的转换底层 SQL 语句本身不变。更优做法用 SQL 完成投影文档明确指出在一般情况下只要可能应当优先用 SQL 完成自定义投影。原因是 SQL 层面的投影如UPPER、CONCAT、聚合函数等直接在数据库引擎内完成既省去 Kotlin 侧逐行的对象转换开销也让查询语义更清晰、可测试。selectNames: SELECT upper(full_name) FROM hockeyPlayer;对应的调用方式同样简洁此时由于只有一列且未显式传入 mapperSQLDelight 会直接以该列的类型作为返回类型val selectAllNames playerQueries.selectNames() println(selectAllNames.executeAsList()) // 打印 [RYAN GETZLAF, COREY PERRY]从编译器源码看当查询不需要自定义包装时customResultTypeFunctionInterface()会直接返回单列类型如fun selectSomeText(_id): QueryString见 SelectQueryGenerator.kt 的注释与实现。也就是说单列查询无需任何 mapper 就能得到裸类型结果UPPER(full_name)这类 SQL 函数在此场景下就是最自然的选择。源码级原理编译器如何生成 mappermapper并非运行时魔法而是由编译期代码生成器在 SelectQueryGenerator.kt 的customResultTypeFunction()中烘焙出来的。其核心生成逻辑如下签名生成customResultTypeFunctionInterface()先为查询方法追加类型变量T再追加mapper: (列1类型, 列2类型, ...) - T参数并把返回类型设为QueryT。游标解包生成器把投影列逐个替换为对应的cursorGetter(index)调用例如cursor.getLong(0)、cursor.getString(1)当列带有自定义 Column Adapter 时还会插入tableAdapter.columnAdapter.decode(...)的解码调用。方言校验在多方言环境下生成的 lambda 开头会带有check(cursor is 方言Cursor类型)的运行时断言确保游标类型与方言实现匹配。组装返回最终生成形如return SelectForIdQuery(id) { cursor - mapper(cursor.getLong(0), cursor.getString(1)) }的代码——当查询依赖多个表、需要监听脏数据事件时编译器还会生成一个私有的QueryT子类型来承载参数与监听逻辑。这一设计意味着 mapper 的类型安全是编译期保证的列类型发生变化时生成的 mapper 签名随之变化调用方会立即出现编译错误而不是等到运行时才暴露问题。运行时执行链从 mapper 到 executeAs*无论 mapper 由谁定义最终它都被包装成一个(SqlCursor) - RowType函数交由运行时执行。核心实现在 runtime/src/commonMain/kotlin/app/cash/sqldelight/Query.ktQueryRowType抽象类持有mapper属性SimpleQuery/SimpleExecutableQuery通过driver.executeQuery(identifier, query, mapper, 0, null)将映射函数下发给底层驱动executeAsList()Query.kt遍历游标对每一行调用mapper(cursor)并收集为ListRowTypeexecuteAsOne()返回唯一一行空结果集抛NullPointerException多行抛IllegalStateExceptionQuery.ktexecuteAsOneOrNull()则允许空结果集返回nullQuery.kt。也就是说mapper与executeAsList()等执行方法正交组合同一个带 mapper 的查询既可以用executeAsList()取全部行也可以用executeAsOne()/executeAsOneOrNull()取单行。边界情况与进阶搭配多列与可空列mapper 的参数个数与顺序严格对齐查询投影列可空列映射为可空 Kotlin 类型需要在 lambda 内自行处理null。与 Column Adapter 共存若投影列使用了自定义类型适配器见 docs/common/custom_column_types.md编译器生成的解包代码会先decode再传入 mapper无需你手动转换。与协程扩展搭配mapper 是纯函数式的列映射与 docs/common/coroutines-usage.md 中介绍的Flow查询扩展完全兼容——流的每个发射元素仍是 mapper 的输出类型。JVM H2 场景本文主题位于 docs/jvm_h2 文档树H2 方言支持UPPER、CONCAT等标准 SQL 函数因此在 JVM H2 环境下文档推荐的SQL 优先策略可以放心使用。小结SQLDelight 的 Type Projections 提供了一条清晰的决策路径默认数据类投影满足大部分场景需要更轻量的返回类型时用mapper尾随 lambda 做类型安全的 Kotlin 侧映射而当转换逻辑可以用 SQL 表达如大小写转换、拼接、聚合时优先写进.sq查询里让数据库代劳。编译期生成器SelectQueryGenerator.kt保证了 mapper 的类型安全运行时Query.kt保证了执行语义的一致两者共同构成了这一特性的完整闭环。赞分享后端ORM【免费下载链接】sqldelightSQLDelight - Generates typesafe Kotlin APIs from SQL项目地址https://gitcode.com/gh_mirrors/sq/sqldelight点击查看免费下载相关推荐ik_llama.cpp 在 Armv8.0 设备上的移植实战NEON 指令模拟、量化兼容性与 Trellis Quants 性能权衡ik_llama.cpp 在 Armv8.0 设备上的移植实战NEON 指令模拟、量化兼容性与 Trellis Quants 性能权衡 本文基于 ik_lla后端ORMSQLDelight 自定义投影Type Projections实战指南从类型安全 Mapper 到纯 SQL 投影SQLDelight 自定义投影Type Projections实战指南从类型安全 Mapper 到纯 SQL 投影 SQLDelight 会根据 .sq后端ORMSQLDelight 自定义投影Custom Projections实战类型安全 Mapper 与 SQL 投影的取舍SQLDelight 自定义投影Custom Projections实战类型安全 Mapper 与 SQL 投影的取舍 本文基于 SQLDelight 官后端ORM上一篇终极指南在PC上免费体验Switch游戏的yuzu模拟器完全配置方案下一篇5个核心能力解锁如何在电脑上获得专业级的Switch游戏体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考