ARTICLE DETAIL

资讯详情

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

SQLDelight JVM 版 H2/HSQL 方言:列类型映射、自定义列类型与乐观锁实战指南

SQLDelight JVM 版 H2/HSQL 方言:列类型映射、自定义列类型与乐观锁实战指南 后端ORM【免费下载链接】sqldelightSQLDelight - Generates typesafe Kotlin APIs from SQL项目地址https://gitcode.com/gh_mirrors/sq/sqldelight点击查看免费下载SQLDelight 会根据你编写的 SQL 表结构自动生成类型安全的 Kotlin 数据类与查询接口而在 JVM 上使用 H2/HSQL 方言时数据库列类型到 Kotlin 类型的映射规则、自定义列类型ColumnAdapter、值类型VALUE与乐观锁LOCK是保证代码生成结果正确性的关键。本文以 docs/jvm_h2/types.md 为核心骨架结合本仓库dialects/hsql方言模块与runtime运行时源码完整讲解 H2 列类型映射表、自定义列类型、枚举适配、值类型与乐观锁的声明方式、配置示例及底层实现原理读完即可在项目中准确声明表结构并处理复杂列类型。H2/HSQL 列类型与 Kotlin 类型映射表SQLDelight 中的列定义与标准 H2 列定义完全一致唯一额外支持的是一个列约束extra column constraint用于指定该列在生成的接口中的 Kotlin 类型其语法见下文 自定义列类型 一节。下面这张完整的映射表来自 docs/jvm_h2/types.md它覆盖了 H2/HSQL 方言下几乎所有常见 SQL 类型的默认 Kotlin 映射CREATE TABLE some_types ( some_tiny_int TINYINT, -- Retrieved as Byte some_small_int SMALLINT, -- Retrieved as Short some_integer INTEGER, -- Retrieved as Int some_int INT, -- Retrieved as Int some_big_int BIGINT, -- Retrieved as Long some_decimal DECIMAL(6,5), -- Retrieved as Int some_dec DEC(6,5), -- Retrieved as Int some_numeric NUMERIC(6,5), -- Retrieved as Int some_float FLOAT(6), -- Retrieved as Double some_real REAL, -- Retrieved as Double some_double DOUBLE, -- Retrieved as Double some_double_precision DOUBLE PRECISION, -- Retrieved as Double some_boolean BOOLEAN, -- Retrieved as Boolean some_date DATE, -- Retrieved as String some_time TIME, -- Retrieved as String some_timestamp2 TIMESTAMP(6), -- Retrieved as String some_char CHAR, -- Retrieved as String some_character CHARACTER(6), -- Retrieved as String some_char_varying CHAR VARYING(6), -- Retrieved as String some_longvarchar LONGVARCHAR, -- Retrieved as String some_character_varying CHARACTER VARYING(6), -- Retrieved as String some_varchar VARCHAR(16), -- Retrieved as String some_clo CHARACTER LARGE OBJECT(16), -- Retrieved as String some_clob clob(16 M CHARACTERS), -- Retrieved as String some_binary BINARY, -- Retrieved as ByteArray some_binary2 BINARY(6), -- Retrieved as ByteArray some_longvarbinary LONGVARBINARY, -- Retrieved as ByteArray some_longvarbinary2 LONGVARBINARY(6), -- Retrieved as ByteArray some_binary_varying BINARY VARYING(6), -- Retrieved as ByteArray some_varbinary VARBINARY(8), -- Retrieved as ByteArray some_uuid UUID, -- Retrieved as ByteArray some_blob BLOB, -- Retrieved as ByteArray some_blo BINARY LARGE OBJECT(6), -- Retrieved as ByteArray some_bit BIT, -- Retrieved as ByteArray some_bit2 BIT(6), -- Retrieved as ByteArray some_bit_varying BIT VARYING(6), -- Retrieved as ByteArray some_interval INTERVAL YEAR TO MONTH, -- Retrieved as ByteArray some_interval2 INTERVAL YEAR(3), -- Retrieved as ByteArray some_interval3 INTERVAL DAY(4) TO HOUR, -- Retrieved as ByteArray some_interval4 INTERVAL MINUTE(4) TO SECOND(6), -- Retrieved as ByteArray some_interval5 INTERVAL SECOND(4,6) -- Retrieved as ByteArray );将上述内容整理为速查表便于在声明表结构时快速对照H2/HSQL SQL 类型生成的 Kotlin 类型说明TINYINTByte8 位有符号整数SMALLINTShort16 位有符号整数INTEGER/INTInt32 位有符号整数BIGINTLong64 位有符号整数DECIMAL(p,s)/DEC(p,s)/NUMERIC(p,s)Int定点数fixed-point映射为整数FLOAT(p)/REAL/DOUBLE/DOUBLE PRECISIONDouble近似浮点数approximate numericBOOLEANBoolean布尔值DATE/TIME/TIMESTAMP(p)String日期、时间、时间戳以字符串形式取出CHAR/CHARACTER(n)/CHAR VARYING(n)/VARCHAR(n)/LONGVARCHAR/CHARACTER VARYING(n)/CHARACTER LARGE OBJECT(n)/CLOBString字符字符串character string与大型字符对象BINARY/BINARY(n)/LONGVARBINARY/BINARY VARYING(n)/VARBINARY(n)/UUID/BLOB/BINARY LARGE OBJECT(n)/BIT/BIT(n)/BIT VARYING(n)ByteArray二进制字符串、位串、UUID 与大型二进制对象INTERVAL如YEAR TO MONTH、DAY TO HOUR、MINUTE TO SECOND等ByteArray时间段类型需要特别说明两点原文档标题为 “MySQL Types”但从内容看它列出的TINYINT、LONGVARCHAR、INTERVAL等均为 H2/HSQL 的列类型且文档开头明确写道 “SQLDelight column definitions are identical to regular H2 column definitions”因此该小节实际描述的是 H2 方言的类型映射属于文档标题的历史遗留问题阅读时以正文内容为准。表中DECIMAL/NUMERIC等定点数默认映射为Int如果精度超出整数范围应配合下文的自定义列类型将其映射为更合适的 Kotlin 类型。类型映射在方言模块中的实现上述映射并非硬编码在文档里而是由本仓库dialects/hsql方言模块中的类型解析器驱动。查看 HsqlTypeResolver.kt 可以看到definitionType根据 PSI 语法树中的类型节点分类返回IntermediateTypeapproximateNumericDataTypeFLOAT/REAL/DOUBLE等→PrimitiveType.REAL对应 KotlinDoublebinaryStringDataType与bitStringDataType→PrimitiveType.BLOB对应ByteArraydateDataType→PrimitiveType.TEXT对应StringfixedPointDataTypeDECIMAL/NUMERIC→PrimitiveType.INTEGERcharacterStringDataType→PrimitiveType.TEXTintervalDataType→PrimitiveType.BLOB其余类型节点则落到方言自定义的 HsqlType.kt 枚举TINY_INT→Byte、SMALL_INT→Short、INTEGER→Int、BIG_INT→Long、BOOL→Boolean。其中BOOL的读写实现也很有代表性decode将驱动返回的1L转换为truevalue 1Lencode将Boolean写回1L/0L且所有整数类列含BOOL在 JDBC 绑定与游标读取时统一走bindLong/getLong见 HsqlType.kt。理解这一点有助于排查“为什么 TINYINT 在驱动层是 Long 而生成的 Kotlin 类型是 Byte”之类的疑问——转换由生成的代码完成。此外HsqlTypeResolver还处理了COALESCE/IFNULL/GREATEST/LEAST/MAX/MIN等函数的返回类型推导以及length等字符串函数返回BIG_INTLong的规则见 HsqlTypeResolver.kt。自定义列类型Custom Column Types内置映射只覆盖“SQL 类型 → 基础 Kotlin 类型”这一层。如果希望把列读成更贴合业务的自定义类型可以在列定义时通过AS Kotlin 类型显式指定这一语法正是文档开头提到的“额外列约束”。该小节内容与仓库共享文档 docs/common/custom_column_types.md 一致被 H2、SQLite、MySQL 等多个方言文档共同引用。以“把逗号分隔的字符串列读成ListString”为例import kotlin.String; import kotlin.collections.List; CREATE TABLE hockeyPlayer ( cup_wins TEXT AS ListString NOT NULL );注意.sq文件顶部需要用import语句引入用到的 Kotlin 类型含包名这样生成的代码才能正确引用。声明了自定义类型后创建Database时必须提供一个ColumnAdapter负责在数据库类型与自定义类型之间做双向映射val listOfStringsAdapter object : ColumnAdapterListString, String { override fun decode(databaseValue: String) if (databaseValue.isEmpty()) { listOf() } else { databaseValue.split(,) } override fun encode(value: ListString) value.joinToString(separator ,) } val queryWrapper: Database Database( driver driver, hockeyPlayerAdapter hockeyPlayer.Adapter( cup_winsAdapter listOfStringsAdapter ) )ColumnAdapter的接口定义位于运行时 ColumnAdapter.kt它是一个双泛型接口ColumnAdapterT : Any, S其中S必须是数据库侧支持的类型之一Long、Double、String、ByteArraydecode负责把数据库值S解码成业务类型Tencode负责把T编码回S。生成代码在查询时调用decode在写入/更新时调用encode从而把“类型转换”集中收敛到这一个适配器里。枚举列内置的 EnumColumnAdapter作为便利设施SQLDelight 运行时自带一个把枚举以字符串形式存储的ColumnAdapter无需手写样板代码。先在 SQL 中把列声明为某个枚举类型import com.example.hockey.HockeyPlayer; CREATE TABLE hockeyPlayer ( position TEXT AS HockeyPlayer.Position )构造Database时传入运行时提供的EnumColumnAdapter()val queryWrapper: Database Database( driver driver, hockeyPlayerAdapter HockeyPlayer.Adapter( positionAdapter EnumColumnAdapter() ) )EnumColumnAdapter的实现位于 EnumColumnAdapter.ktdecode按枚举名name从enumValues中查找对应枚举常量encode直接返回value.name即数据库里存的是枚举常量名。它通过inline fun reified T : EnumT EnumColumnAdapter()工厂函数配合enumValues()自动获取枚举常量数组所以调用处无需传任何参数。值类型Value Types除了自定义类型SQLDelight 还支持为列生成一个值类型value type——一个包装底层数据库类型的 Kotlin 类型。声明方式是在列上追加AS VALUECREATE TABLE hockeyPlayer ( id INT AS VALUE );从编译器源码看这一约束由 ColumnTypeMixin.kt 识别当列类型节点的子节点中出现VALUE或LOCK关键字时会为对应列生成带VALUE修饰符的 Kotlin 包装类型。值类型的引入让列不再是裸的Int/String而是有明确语义的领域类型例如后续章节的乐观锁列就是值类型的一个典型应用。乐观锁Optimistic Locking值类型之上SQLDelight 提供了一个开箱即用的并发控制机制把某一列声明为LOCK编译器会为该列生成值类型强制所有UPDATE语句必须正确使用该锁进行更新否则编译报错。声明方式与约束示例如下与共享文档 docs/common/types_server_migrations.md 一致CREATE TABLE hockeyPlayer( id INT AS VALUE, version_number INT AS LOCK, name VARCHAR(8) ); -- This will fail (and the IDE plugin will suggest rewriting to the below) updateName: UPDATE hockeyPlayer SET name ?; -- This will pass compilation updateNamePassing: UPDATE hockeyPlayer SET name ? version_number :version_number 1 WHERE version_number :version_number;其背后的校验逻辑实现在编译器的 OptimisticLockValidator.kt 中规则可以归纳为三条SET 子句必须包含锁列更新语句的SET中必须出现锁列否则报 “This statement is missing the optimistic lock in its SET clause.”L69-L80锁必须自增 1SET中锁列的表达必须严格形如lock :lock 1绑定参数自增或lock lock 1列自增否则报 “The optimistic lock must be set exactly like ...”L91-L103列自增selfIncrements时无需再校验 WHERE 子句WHERE 子句必须校验锁对于绑定参数自增的形式还必须满足WHERE lock :lock形式的相等比较否则报 “The optimistic lock must be queried exactly like ...”L113-L143。也就是说updateNamePassing这种写法把“读取版本号 → 版本号 1 写回 → 按旧版本号过滤影响行数”的乐观锁流程固化进了编译期约束任何绕过锁的UPDATE都无法通过编译。同时编译器的QueryGenerator也会识别LOCK列并参与查询代码生成见 QueryGenerator.kt而 IDE 插件会在违反约束时提供快速修复建议quickFix见 OptimisticLockValidator.kt。自定义类型与迁移Migrations当迁移文件.sqm作为 schema 的权威来源时同样可以在ALTER TABLE中为新增列指定暴露给 Kotlin 的列类型import kotlin.String; import kotlin.collection.List; ALTER TABLE my_table ADD COLUMN new_column VARCHAR(8) AS ListString;注意示例中导入的是kotlin.collection.List原文档写法实际使用时请导入正确的包名kotlin.collections.List。该语法与建表语句中的AS Kotlin 类型完全一致因此迁移后新增的列也会在生成的数据类/查询接口中呈现为自定义类型并且同样需要为Database提供对应的ColumnAdapter。小结与使用建议默认映射优先H2/HSQL 下绝大多数列都可以直接命中 docs/jvm_h2/types.md 中的默认映射表无需额外配置业务语义列使用自定义类型需要把列读成List、枚举、自定义数据类时用AS Kotlin 类型ColumnAdapter组合转换逻辑集中在适配器内生成代码保持简洁枚举优先用EnumColumnAdapter()运行时内置实现已覆盖“枚举 ↔ 字符串”这一最常见场景并发更新使用LOCK乐观锁声明LOCK列后编译器与 IDE 会在编译期强制校验UPDATE的写法将乐观锁约定固化为代码约束迁移同样支持自定义类型schema 以迁移为权威时ALTER TABLE ADD COLUMN ... AS ...同样生效。以上类型声明、适配器实现与校验规则均可在本仓库对应源码中验证方言映射见 HsqlTypeResolver.kt 与 HsqlType.kt运行时适配器见 ColumnAdapter.kt 与 EnumColumnAdapter.kt乐观锁校验见 OptimisticLockValidator.kt。共享文档 docs/common/custom_column_types.md 与 docs/common/types_server_migrations.md 还给出了跨方言通用的写法可供迁移到其他方言时对照参考。赞分享后端ORM【免费下载链接】sqldelightSQLDelight - Generates typesafe Kotlin APIs from SQL项目地址https://gitcode.com/gh_mirrors/sq/sqldelight点击查看免费下载相关推荐Kubernetes Goat 场景 4 实战特权容器逃逸至宿主机并夺取节点级凭据Kubernetes Goat 场景 4 实战特权容器逃逸至宿主机并夺取节点级凭据 本篇基于 Kubernetes Goat 场景 4Container后端ORMSQLDelight PostgreSQL 类型映射实战从 SQL 列类型到 Kotlin 类型系统SQLDelight PostgreSQL 类型映射实战从 SQL 列类型到 Kotlin 类型系统 本篇技术指南以 docs/jvm_postgresql/后端ORMSQLDelight MySQL 类型映射全指南从 SQL 列类型到 Kotlin 类型的自动转换与自定义适配SQLDelight MySQL 类型映射全指南从 SQL 列类型到 Kotlin 类型的自动转换与自定义适配 导读 本文聚焦 SQLDelight 在 JV后端ORM上一篇ScrollableLayout最佳实践解决Android开发中的滚动冲突问题下一篇Get Shit Done进阶技巧自定义工作流代理提升开发效率创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表