ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SQLDelight 类型投影(Type Projections)与自定义 Mapper 实战指南

SQLDelight 类型投影(Type Projections)与自定义 Mapper 实战指南 后端ORM【免费下载链接】sqldelightSQLDelight - Generates typesafe Kotlin APIs from SQL项目地址https://gitcode.com/gh_mirrors/sq/sqldelight点击查看免费下载SQLDelight 默认会把每条 SELECT 查询的结果集映射为自动生成的数据类但实际业务中我们往往只需要投影中的某几个字段、或者想把原始字段组合成自定义类型。本文以 Android SQLite 场景为切入点系统讲解 SQLDelight 的类型投影机制如何通过类型安全的 Mapper 参数接管结果映射、如何优先用SQL 本身完成投影并结合本仓库的编译器与运行时源码说明这些能力从.sq文件到生成代码再到查询执行的完整链路。读完本文你将掌握自定义投影的两种正交手段并理解它们各自的适用边界。本文对应仓库文档为 docs/android_sqlite/custom_projections.md其正文内容来自所有平台共用的 docs/common/custom_projections.mdAndroid 之外JVM、Native、JS 等的投影行为完全一致。一、什么是类型投影默认数据类与自定义映射SQLDelight 在编译.sq文件时会为每个表生成对应的数据类例如sample工程中的 Player.sq 会生成Player数据类。默认情况下任何 SELECT 查询的每一行都会被映射为该数据类的一个实例-- Player.sq selectAll: SELECT * FROM player JOIN team ON player.team team.id;// 默认返回 ListPlayer val players playerQueries.selectAll().executeAsList()这就是默认投影查询返回的数据类即投影。它覆盖了绝大多数场景但当你只需要投影中的一部分字段、或想对列做即时变换如把名字转大写、拼接字符串、封装成业务类型时默认数据类就显得笨重——你不得不为一行数据分配一个包含全部字段的对象再手动取用。此时就轮到类型安全的 Mapper登场SQLDelight 为每个 SELECT 查询额外生成一个接受mapper参数的函数重载让调用方用一行 lambda 自定义从这一行的列到目标类型的映射规则。二、用 Mapper 参数覆盖默认投影文档给出的核心示例是查询player表的两列球员号码与姓名但不想构造整个数据类只想拿到姓名的大写形式列表val selectAllNames playerQueries.selectAll( mapper { player_number, full_name - full_name.toUppercase() } ) println(selectAllNames.executeAsList()) // Prints [RYAN GETZLAF, COREY PERRY]关键点拆解如下lambda 参数即投影的每一列mapper的入参顺序、类型与查询返回的列一一对应例如上述查询返回player_number与full_name两列lambda 就接收这两个参数参数名来自.sq中的列名由编译器保证类型安全写错列名或类型会直接编译失败。返回值任意mapper的返回值类型T就是整个查询的结果行类型executeAsList()返回ListT。你可以返回String、Int、自定义业务类甚至Pair、data class。只影响该次调用传入mapper只改变这次查询的映射结果不会改动表的数据类定义也不会影响其他查询。2.1 带参数的查询同样适用如果查询声明了 SQL 参数如WHERE id :team_id生成的带 mapper 函数会先接收这些参数、再接 mapper 尾参val result playerQueries.forTeam( teamId, mapper { first_name, last_name, teamName - $first_name $last_name ($teamName) } )这一签名的源码形态可以在编译器生成逻辑中看到见下文第三节。2.2 单列查询的简化当查询只投影一列时编译器不会生成带 mapper 的重载而是直接返回该列的 Kotlin 类型如QueryString因为你已经可以通过executeAsList()拿到最简类型无需再包一层 lambda。这是编译器在 SelectQueryGenerator.kt#L120-L124 中的显式分支needsWrapper()为 false 时no custom type possible, just returns the single column。三、编译器如何生成带 Mapper 的查询方法源码视角要理解自定义投影为什么类型安全最好的方式看代码生成器。SQLDelight 编译器中的 SelectQueryGenerator.kt 负责生成所有 SELECT 查询的接口方法其中customResultTypeFunctionInterface()L86-L127精确描述了带 mapper 的函数签名// 生成的函数签名示意源码注释中的原话 fun T selectForId(id, mapper: (column1: String) - T): QueryT其生成步骤为先按顺序把查询的 SQL 参数逐个加入参数列表query.parameters.forEach { function.addParameter(it.name, it.argumentType()) }若查询需要包装needsWrapper()即返回多列则声明类型变量T追加mapper尾参类型为按列构造的 lambda 类型(col1: Type1, col2: Type2, ...) - T每个参数的类型来自resultColumns各列的javaType返回类型变为QueryT。而真正执行映射的 lambda 在customResultTypeFunction()L134-L237中组装。生成的运行时代码大致等价于{ cursor - check(cursor is SqlCursorSubclass) // 游标类型校验 mapper( cursor.getLong(0), // 按列索引反序列化 adapter.decode(cursor.getString(1)) ) }这里可以看到两个值得注意的实现事实列的读取顺序与索引生成代码用resultColumns.mapIndexed { index, column - column.cursorGetter(index) }把第 index 列的反序列化表达式按顺序拼进mapper(...)调用因此 lambda 的形参顺序恰好等于 SELECT 列表的顺序类型适配器自动衔接如果某列声明了自定义列类型AS SomeType生成代码会调用表名Adapter.列名Adapter.decode(...)先把游标原始值解码成 Kotlin 类型再传给mapper。这意味着自定义投影与自定义列类型可以无缝组合。四、运行时如何消费 Mapper源码视角生成的带 mapper 查询最终构造一个QueryT实例runtime 的 Query.kt其中Query/ExecutableQuery构造函数接收一个mapper: (SqlCursor) - RowType执行时由驱动把游标交给你自定义的 lambda。核心执行入口executeAsList()Query.kt#L174-L178的实现非常直白fun executeAsList(): ListRowType execute { cursor - val result mutableListOfRowType() while (cursor.next().value) result.add(mapper(cursor)) QueryResult.Value(result) }.value即逐行推进游标每行调用一次 mapper收集成 List。默认数据类投影本质上也是同一个机制——编译器为默认查询生成的函数内部以数据类构造器引用::Data作为 mapper 传入见defaultResultTypeFunction()的ctorCallSelectQueryGenerator.kt#L68-L72。所以自定义 mapper 并不是什么特殊通道它和默认投影走的是同一条执行管线只是映射逻辑由你接管。此外ExecutableQuery还提供了另外两个消费方式Query.kt#L187-L203executeAsOne()要求结果集恰好一行无行抛NullPointerException多行抛IllegalStateExceptionexecuteAsOneOrNull()返回唯一一行或null多行同样抛IllegalStateException。查询真正下发给数据库时走的是SqlDriver.executeQuery(identifier, sql, mapper, parameters, binders)接口SqlDriver.kt#L40-L46。需要特别留意的是接口 KDoc 中的约束游标不能逃出 mapper 块的作用域——在自定义 mapper 中不要试图保存游标引用只能在 lambda 内同步消费列值。五、优先用 SQL 完成投影文档在给出 Mapper 用法后立刻强调了一条重要的工程原则In general you should be leveraging SQL to do custom projections whenever possible.一般而言只要可能应优先借助 SQL 本身来完成自定义投影。原因很务实把投影逻辑如大小写转换、拼接、聚合、去重、类型转换下推到数据库层意味着——结果集在网络/进程间传输前就已经裁剪完毕减少客户端无谓的数据搬运与对象分配投影逻辑声明在.sq文件里随数据库 schema 一起可审计、可测试、可复用数据库引擎的优化器可以对投影列做覆盖索引等优化。文档随后给出同款需求的 SQL 写法selectNames: SELECT upper(full_name) FROM hockeyPlayer;val selectAllNames playerQueries.selectNames() println(selectAllNames.executeAsList()) // Prints [RYAN GETZLAF, COREY PERRY]因为selectNames只返回单列编译器直接生成QueryString见 2.2 节连mapper都不需要写代码更简洁、意图更清晰。sample工程中的 Player.sq 也展示了同类实践——forTeam查询用CAST (number AS TEXT)与team.name AS teamName在 SQL 层完成列转换与重命名正是SQL 优先投影的落地样板。六、两套方案的取舍何时用 SQL、何时用 Mapper综合文档与源码可以整理出清晰的决策依据场景推荐手段理由大小写、拼接、CAST、聚合、DISTINCT、CASE 等标准 SQL 能力SQL 投影下推给数据库代码最简.sq内可读可测把多列封装成业务领域类型如Address、MoneyMapperSQL 无法表达 Kotlin 类型mapper类型安全且无需额外数据类依赖自定义列类型适配器AS SomeType的列两者皆可编译器生成的 mapper lambda 会自动先decode适配器列见第三节仅查询单列无需 mapper编译器直接返回该列类型需要澄清的一点是Mapper 只是结果行的映射钩子它不做列级别的类型转换。若某列需要从数据库类型转为 Kotlin 类型例如INTEGER存时间戳、文本存枚举应使用列类型与适配器机制参见 custom_column_types.md 与 types.md否则该列在 mapper lambda 里只会以原始 Kotlin 类型出现。七、行为验证驱动测试中的执行语义自定义投影的正确性依赖运行时对 mapper 的调用语义仓库的驱动测试直接验证了这一点。QueryTest.kt 中所有驱动共享的跨平台测试覆盖了executeAsOne()恰好一行时返回该行L65-L69零行抛 NPEL81-L84、多行抛IllegalStateExceptionL89-L95executeAsOneOrNull()零行返回nullL108-L110、多行抛异常L112-L117executeAsList()assertEquals(listOf(data1, data2), testDataQuery().executeAsList())L123-L130验证多行按序逐行映射。这些测试位于 drivers/driver-test 模块被各平台驱动Android、JVM、Native、Web复用说明 mapper 的执行语义在所有平台一致——你在 Android SQLite 上写出的自定义投影在桌面端、iOS、JS 上行为相同。八、小结SQLDelight 的类型投影提供两条互补的路径Mapper 参数为每个多列 SELECT 查询生成(col1, col2, ...) - T尾参重载用类型安全的 lambda 在 Kotlin 侧接管行到目标类型的映射适合封装领域类型与内存变换SQL 投影在.sq文件中用SELECT upper(...)、CAST(...) AS ...等把裁剪与变换下推给数据库单列结果自动简化为该列的 Kotlin 类型。两者的底层实现是同一套机制编译器生成逐列反序列化后调用 mapper的 lambda运行时由Query/ExecutableQuery在游标推进过程中逐行执行并由跨平台驱动测试保证语义一致。实战中遵循文档的指引——凡是 SQL 能表达的投影交给 SQLSQL 表达不了的 Kotlin 类型映射交给 Mapper即可写出既精简又类型安全的查询层。赞分享后端ORM【免费下载链接】sqldelightSQLDelight - Generates typesafe Kotlin APIs from SQL项目地址https://gitcode.com/gh_mirrors/sq/sqldelight点击查看免费下载相关推荐SQLDelight 自定义投影Type Projections实战指南从类型安全 Mapper 到纯 SQL 投影SQLDelight 自定义投影Type Projections实战指南从类型安全 Mapper 到纯 SQL 投影 SQLDelight 会根据 .sq后端ORMSQLDelight 自定义投影Type Projections用类型安全 Mapper 定制查询返回类型SQLDelight 自定义投影Type Projections用类型安全 Mapper 定制查询返回类型 SQLDelight 默认会为每条查询自动生成后端ORMSQLDelight 自定义投影Type Projections用 mapper 与 SQL 掌控查询返回类型SQLDelight 自定义投影Type Projections用 mapper 与 SQL 掌控查询返回类型 本文基于 SQLDelight 官方文档中后端ORM上一篇Meteor 2.11 版本升级指南MongoDB 6.x 支持、驱动升级与破坏性变更全解析下一篇Fleet 4.89.0 发布深度解析Windows 部署体验增强、Android 变量全面落地与策略自动化历史回溯创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表