鸿蒙 韶非 UI 系列:关系数据库 @ohos.data.relationalStore,鸿蒙 SQLite 封装,结构化数据存取入门
写在前面如果你写过鸿蒙 ArkUI 应用大概率遇到过这个场景你写了个记账应用,每笔账有「金额/类型/时间/备注」四个字段。你想「用文件存 JSON 数组」——结果改一笔账要读全表、改、写全表,10 笔账卡得飞起。你想「用 Preferences 存」——Preferences 是键值对,不能按条件查「上个月吃饭花了多少」。你查文档发现「鸿蒙有 relationalStore,是 SQLite 封装」——你点进去发现getRdbStore拿实例、executeSql执行 DDL、insert(table, ValuesBucket)插数据、querySql返回ResultSet遍历、beginTransaction/commit/rollBack管事务——API 一脸懵。这是「键值对存」和「关系型存」的分水岭。鸿蒙给的结构化存取答案是ohos.data.relationalStore——getRdbStore拿数据库实例、executeSql执行建表/DDL、insert/ValuesBucket安全插入、querySql/ResultSet查询遍历、beginTransaction/commit/rollBack事务保证原子性。本文就用一个真机可跑的「建库 建表 插入 查询 事务」demo,把关系数据库从「听名字一脸懵」讲到「下个项目直接抄」。代码托管在 AtomGit,文末有链接,真机实拍截图作证。这是韶非 UI 系列第五篇,接续上四篇 HTTP 网络栈 文件 IO 能力调用 后台任务。适合人群:写过鸿蒙应用、被「记账应用用啥存」折磨过的同学。不适合人群:还在学State的同学——出门左转看我的入门篇。一、先讲清楚:关系数据库到底是啥一句话:关系数据库是鸿蒙给应用存「结构化数据」的原生机制,管「建库 建表 增删改查 事务」全流程。你之前写前端localStorage/IndexedDB是浏览器宿主 API——鸿蒙不是浏览器环境,没有这种。关系数据库是鸿蒙专门给结构化存取的原生机制,底层是 SQLite,能力对标前端的「IndexedDB SQL.js」但更精细可控。核心 API 一览:API作用一句话理解relationalStore.getRdbStore(context, config)拿 RdbStore 实例「告诉系统我要用数据库」rdbStore.executeSql(sql)执行 DDL/原始 SQL「建表/索引/原始 SQL」rdbStore.insert(table, ValuesBucket)插入数据「键值对插入,防 SQL 注入」rdbStore.querySql(sql)查询数据「SELECT 返回 ResultSet」rdbStore.beginTransaction/commit/rollBack事务管理「保证原子性,要么全成要么全回滚」记住这五个,往下看。二、动手:一个建库 建表 插入 查询 事务的 demo2.1 import 拿 UIAbilityContextimportrelationalStorefromohos.data.relationalStoreimportcommonfromohos.app.ability.commonEntryComponentstruct Index{privatecontext:common.UIAbilityContextgetContext(this)ascommon.UIAbilityContextprivaterdbStore:relationalStore.RdbStore|nullnullprivatedbName:stringarkts_demo.dbprivatetableName:stringUSER// ...}三个细节:import relationalStore from ohos.data.relationalStore——relationalStore是关系数据库的入口模块context: common.UIAbilityContext——RDB 是应用沙箱内建库,需 UIAbility 上下文定位沙箱rdbStore存拿到的 RdbStore 实例,后续所有操作都走它2.2getRdbStoreexecuteSql:初始化 建表asyncinitRdb():Promisevoid{this.stateLog初始化 RDB 中...try{constconfig:relationalStore.StoreConfig{name:this.dbName,securityLevel:relationalStore.SecurityLevel.S1}// getRdbStore 拿 RdbStore 实例沙箱内建库this.rdbStoreawaitrelationalStore.getRdbStore(this.context,config)// 建表 SQLCREATE TABLE IF NOT EXISTSconstcreateSqlCREATE TABLE IF NOT EXISTS${this.tableName}( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, ts INTEGER )awaitthis.rdbStore.executeSql(createSql)this.stateLogRDB 已初始化,数据库 ${this.dbName},表 ${this.tableName}}catch(e){this.stateLog初始化失败:${e.message}this.rdbStorenull}}getRdbStore三个关键点:①StoreConfig配置:数据库名 安全级别constconfig:relationalStore.StoreConfig{name:this.dbName,// 数据库文件名沙箱内securityLevel:relationalStore.SecurityLevel.S1// 安全级别}SecurityLevel是鸿蒙定义的数据库安全级别:SecurityLevel含义用途S1低级公开数据普通应用S2中级个人数据记账/笔记S3高级敏感数据隐私相册S4最高严格机密金融/医疗S1 是默认选择。如果存用户隐私数据,选 S2/S3。②executeSql执行 DDL/原始 SQLawaitthis.rdbStore.executeSql(CREATE TABLE IF NOT EXISTS USER (...))executeSql执行建表/索引/原始 SQL,返回Promisevoid。注意:executeSql不返回查询结果——查询要走querySql。2.3insertValuesBucket:安全插入asyncinsertData():Promisevoid{if(!this.rdbStore){this.stateLog尚未初始化 RDB,无法插入return}try{constnow:numberDate.now()// ValuesBucket 键值对插入,比 raw SQL 安全防注入constbucket:relationalStore.ValuesBucket{name:user_${this.insertCount1},age:18(this.insertCount%30),ts:now}// insert 返回 rowIdconstrowId:numberawaitthis.rdbStore.insert(this.tableName,bucket)this.insertCountthis.stateLog第${this.insertCount}次插入成功,rowId ${rowId}}catch(e){this.stateLog插入失败:${e.message}}}insert三个关键点:①ValuesBucket键值对插入防 SQL 注入constbucket:relationalStore.ValuesBucket{name:user_${this.insertCount1},// 列名 - 值age:18(this.insertCount%30),ts:now}awaitthis.rdbStore.insert(this.tableName,bucket)ValuesBucket是「列名 - 值」的键值对。鸿蒙内部用参数化查询拼接,自动防 SQL 注入。比手写INSERT INTO ... VALUES (...)安全得多——手写 raw SQL 拼字符串,用户输入含就炸。②insert返回rowIdconstrowId:numberawaitthis.rdbStore.insert(this.tableName,bucket)rowId是新插入行的主键自增 ID,后续更新/删除可用它索引。2.4querySqlResultSet:查询遍历asyncqueryData():Promisevoid{if(!this.rdbStore){this.stateLog尚未初始化 RDB,无法查询return}try{// querySql 执行 SELECT,返回 ResultSetconstresultSet:relationalStore.ResultSetawaitthis.rdbStore.querySql(SELECT id, name, age, ts FROM${this.tableName}ORDER BY id DESC LIMIT 5)constrows:string[][]// ResultSet 遍历: goToFirstRow / goToNextRow / getColumnIndex / getString/getLongfor(leti0;resultSet.goToNextRow();i){constid:numberresultSet.getLong(resultSet.getColumnIndex(id))constname:stringresultSet.getString(resultSet.getColumnIndex(name))constage:numberresultSet.getLong(resultSet.getColumnIndex(age))rows.push(id${id}, name${name}, age${age})}// resultSet 用完必须 close 释放resultSet.close()this.queryCountthis.lastQueryResultrows.length0?rows.join(\n):(表为空)this.stateLog第${this.queryCount}次查询成功,返回${rows.length}行}catch(e){this.stateLog查询失败:${e.message}}}querySqlResultSet三个关键点:①querySql执行 SELECT 返回ResultSetconstresultSetawaitthis.rdbStore.querySql(SELECT ... FROM ...)ResultSet是游标,不是数组——初始指向「第一行之前」,要主动goToNextRow推进。②ResultSet遍历:游标推进 列索引取值for(leti0;resultSet.goToNextRow();i){constidresultSet.getLong(resultSet.getColumnIndex(id))constnameresultSet.getString(resultSet.getColumnIndex(name))// ...}goToNextRow():推进游标,返回false表示遍历完getColumnIndex(列名):拿列索引数字getLong/getDouble/getString(列索引):按类型取值③ResultSet用完必须close()resultSet.close()ResultSet持有底层 SQLite 游标资源,不 close 会内存泄漏。这是新手最容易忘的坑。2.5beginTransaction/commit/rollBack:事务保证原子性asyncrunTransaction():Promisevoid{if(!this.rdbStore){this.stateLog尚未初始化 RDB,无法跑事务return}try{// beginTransaction 开启事务this.rdbStore.beginTransaction()try{constnow:numberDate.now()// 批量插入 3 行for(leti0;i3;i){constbucket:relationalStore.ValuesBucket{name:tx_user_${now}_${i},age:20i,ts:now}awaitthis.rdbStore.insert(this.tableName,bucket)}// commitTransaction 提交事务this.rdbStore.commit()this.txLog事务提交成功,批量插入 3 行this.stateLog事务跑完了}catch(innerE){// rollBack 回滚事务this.rdbStore.rollBack()this.txLog事务回滚:${innerE.message}}}catch(e){this.stateLog事务失败:${e.message}}}事务三个关键点:①beginTransaction开启事务this.rdbStore.beginTransaction()// ← 之后所有 SQL 在同一事务内②commit提交 /rollBack回滚try{// ... 批量 SQL ...this.rdbStore.commit()// 全成,提交}catch(e){this.rdbStore.rollBack()// 失败,回滚所有}③ 事务保证原子性:要么全成要么全回滚事务最核心的价值是原子性——比如转账,扣 A 100 加 B 100 必须同时成,中间崩溃要么全成要么全回滚。没事务,扣 A 完崩溃,B 没加,钱凭空消失。三、真机实拍:建库 插入 查询 事务全跑通我把这个 demo 装到真机上跑鸿蒙 6.1.1.125, API 24依次点 ① 初始化 RDB ② 插入数据 ×2 ③ 查询数据 ④ 跑事务,下面两张都是真机实拍,没有任何 P 图。初始态:关系数据库 Demo 标题 状态区「尚未初始化 RDB」 ① 初始化 RDB / ② 插入数据 / ③ 查询数据 / ④ 跑事务四按钮 插入次数/查询次数指标区 最近查询结果区 事务日志区 关键 API 说明区:点 ① 初始化 ② 插入 ×2 ③ 查询 ④ 事务后状态:状态「RDB 已初始化,数据库 arkts_demo.db,表 USER」 插入次数 2 查询次数 1 最近查询结果显示 5 行(id/name/age) 事务日志「事务提交成功,批量插入 3 行」:重点看第二张:状态显示「RDB 已初始化,数据库 arkts_demo.db,表 USER」——getRdbStoreexecuteSql真建库建表了;插入次数 2 最近查询结果显示 5 行——insertquerySql真增真查了;事务日志「事务提交成功,批量插入 3 行」——beginTransaction/commit真事务跑了。这是 RDB 五大 API 全跑通的真机证明。四、relationalStorevs 前端「IndexedDB SQL.js」:啥差异新手最容易纠结的问题:既然前端IndexedDB那么标准,鸿蒙为啥要造关系数据库?维度前端「IndexedDB SQL.js」relationalStore运行环境浏览器宿主鸿蒙原生运行环境底层引擎IndexedDB / SQLite-WASMSQLite 原生编译API 风格异步回调 对象存储Promise SQL 风格安全模型同源策略鸿蒙沙箱 SecurityLevel事务 APItransaction隐式beginTransaction/commit显式性能JS-WASM 桥接,慢原生 SQLite,快一句话决策:鸿蒙应用存结构化数据必须用relationalStore,不能用IndexedDB不存在/localStorage容量小 不能条件查。鸿蒙不是浏览器,这套原生 SQLite 封装更安全可控、性能更高。五、常见坑都是血泪坑症状解法用localStorage/IndexedDB编译报错「找不到」鸿蒙用relationalStore,没浏览器宿主 APIexecuteSql拼 SQL 字符串SQL 注入风险查询用querySql,插入用insert ValuesBucketResultSet忘close()游标泄漏 后续查询卡用完务必resultSet.close()事务忘rollBack异常时数据不一致try/catch 内 catch 调rollBack跨应用共享数据库拿不到对方 RdbStore鸿蒙沙箱隔离,跨应用要 ohos.permissionSecurityLevel 选 S4普通应用拿不到 S4S4 限严格机密应用,普通用 S1/S2querySql期望返回数组编译报错类型不匹配返回ResultSet,要遍历六、relationalStore安全模型鸿蒙关系数据库受安全约束——沙箱隔离 SecurityLevel 分级:安全机制含义沙箱隔离数据库文件在应用沙箱内,其他应用默认访问不到SecurityLevel S1-S4数据库分级,S4 最严,限严格机密应用跨应用共享要ohos.permission权限 主动registerStoreObserver这是鸿蒙安全模型的硬约束——比浏览器IndexedDB同源策略严,但比 iOS Keychain 松鸿蒙沙箱可控粒度更细。七、完整代码仓库本文所有代码都已托管到AtomGit,欢迎 clone、提 issue、点 star:仓库地址:https://atomgit.com/JaneConan/arkui-rdb仓库包含:完整的「建库 建表 插入 查询 事务」demo 工程Index.ets主页面getRdbStoreexecuteSqlinsert/ValuesBucketquerySql/ResultSetbeginTransaction/commit/rollBack五姿势StoreConfig配置 SecurityLevel分级说明可直接用 DevEco Studio 打开运行真机装普通应用必能跑八、下一步该学什么?跑通这个 demo 之后,你的鸿蒙结构化存取就入门了。这是韶非 UI 系列第五篇,后续按这个顺序往下:WebSocketohos.net.webSocket下一篇:长连接、推送、实时通讯,聊天应用必学媒体访问ohos.file.photoAccessHelper:访问相册、扫描媒体文件,应用调系统相册必学推送通知ohos.notificationManager:通知栏展示、点击拉起,离线触达必学动画ohos.arkui.animation:属性动画、转场动画,UI 进阶必学相机ohos.multimedia.camera:预览、拍照、录像,相机应用必学写在最后relationalStore的本质,是**「鸿蒙给应用存结构化数据的原生 SQLite 封装」**——不是浏览器IndexedDB,是鸿蒙专门给关系型存取的原生机制,能力对标「IndexedDB SQL.js」但更安全可控、性能更高。代价是ValuesBucket键值对插入多一步、ResultSet游标遍历多一步。一旦你开始用关系数据库思维写结构化存取,你会发现大部分「记账应用按月查开销」「笔记应用按标签筛笔记」「待办按截止时间排」的需求,都是getRdbStoreexecuteSqlinsert/ValuesBucketquerySql/ResultSet的自然结果。代码量比localStorage多两行,结构化查询能力高九成。代码已经给你了,仓库链接在上面。现在关掉这篇文章,打开 DevEco Studio,把 demo 跑起来,亲手点建库建表插入查询事务五大姿势感受下结构化存取。跑通了,回来评论区打个「1」,我看看有多少人真的动手了。作者:JaneConan仓库:https://atomgit.com/JaneConan/arkui-rdb协议:Apache-2.0,随便用,别告我

相关新闻

ZFX山海证券:从公开信息出发,盘点服务体系与风险提示

ZFX山海证券:从公开信息出发,盘点服务体系与风险提示

外汇市场信息更新频繁,平台口碑的形成更依赖长期一致性:入口是否好找、说明是否前后一致、提示是否稳定出现。围绕ZFX山海证券,下面从稳定体验与信息呈现等角度做一次正面观察。外汇相关信息更新频繁,平台将关键提示与解释呈现得更…

2026/7/22 3:43:09 阅读更多 →
海外红队面试经验分享

海外红队面试经验分享

互联网公司A 老牌头部互联网公司,Top 10 职位:高级红队操作员 (Senior Red Team Operator) 流程 简历筛选 招聘人员电话面试: 背景、沟通能力、项目经验概述、对他们公司技术栈的初解 在线评估 : 基础编码/脚本能力测试 核心安全概念问答 (网络、操作系统、加密、认证) 重…

2026/7/22 3:42:09 阅读更多 →
总结 7.21

总结 7.21

今天学了线代的行列式和矩阵。行列式学了插型,剪头型还有ab型,ab型的计算公式。还有使用升阶法求行列式,把它化成剪型。还有范德蒙德,注意范德蒙德的阶数和为最高次数减一,然后递乘就行了,然后是算行列式的…

2026/7/22 3:42:09 阅读更多 →

最新新闻

信息学竞赛实战:PKUWC与WC双赛经验分享

信息学竞赛实战:PKUWC与WC双赛经验分享

1. 赛事背景与个人准备2019年初的冬天,我带着两个保温杯和半箱红牛踏上了前往北京的高铁。作为信息学竞赛的长期参与者,这次同时参加PKUWC(北京大学冬令营)和WC(全国青少年信息学奥林匹克冬令营)的经历&…

2026/7/22 4:29:30 阅读更多 →
大语言模型提示技术:从零样本到多轮对话实战指南

大语言模型提示技术:从零样本到多轮对话实战指南

1. 提示技术概述:从零样本到新对话的演进路径在自然语言处理领域,提示技术(Prompting Techniques)已成为连接预训练模型与下游任务的核心桥梁。过去三年,随着GPT-3、ChatGPT等大语言模型的崛起,提示工程从边…

2026/7/22 4:29:30 阅读更多 →
机械合金化技术:颠覆传统的金属冷加工新工艺

机械合金化技术:颠覆传统的金属冷加工新工艺

1. 项目概述:颠覆传统的金属冶炼新思路当我在材料实验室第一次看到这种新型金属加工技术时,整个人都愣住了——没有传统冶炼车间的高温熔炉,没有刺鼻的化学气体,取而代之的是一台看似简单的机械装置,通过精确控制的冲击…

2026/7/22 4:29:30 阅读更多 →
C++循环结构实战:从猜数字游戏掌握do...while与输入验证

C++循环结构实战:从猜数字游戏掌握do...while与输入验证

1. 项目概述:从“猜数字”窥探C循环结构的实战魅力 “猜数字”这个小游戏,估计是每个C初学者在接触循环结构时都会遇到的经典案例。它看似简单,一个随机数,几次猜测,对了就结束。但如果你只把它当作一个简单的语法练习…

2026/7/22 4:29:30 阅读更多 →
PHP与C/C++混合编程:性能优化与系统扩展实战指南

PHP与C/C++混合编程:性能优化与系统扩展实战指南

1. 项目概述:为什么PHP开发者需要了解C/C?在Web开发的世界里,PHP无疑是王者之一,它简单、快速,是构建动态网站和Web应用的利器。但当你深耕PHP一段时间,尤其是处理高并发、复杂计算密集型任务,或…

2026/7/22 4:29:30 阅读更多 →
嵌入式系统异常与中断:内忧外患的底层处理机制与实战设计

嵌入式系统异常与中断:内忧外患的底层处理机制与实战设计

1. 从“内忧外患”说起:理解系统运行的两种扰动做嵌入式或者底层系统开发的朋友,对“异常”和“中断”这两个词一定不陌生。它们就像是系统运行过程中遇到的两种“意外事件”,一个来自内部,一个来自外部,共同构成了我们…

2026/7/22 4:28:29 阅读更多 →

日新闻

TI DSP系统配置模块SYSCFG详解:中断机制与主设备优先级配置实战

TI DSP系统配置模块SYSCFG详解:中断机制与主设备优先级配置实战

1. 项目概述与SYSCFG模块的核心价值在嵌入式系统,尤其是像TI C6000系列这样的高性能DSP开发中,我们常常会与芯片手册里那些密密麻麻的寄存器打交道。很多开发者可能更关注算法实现、内存优化或者外设驱动,但对于一个稳定、高效的系统而言&…

2026/7/22 0:00:26 阅读更多 →
微信Server酱:高到达率的应急通知方案实践

微信Server酱:高到达率的应急通知方案实践

1. 为什么我们需要"最次"的通知方案? 在数字化协作环境中,消息通知系统的重要性不言而喻明。但现实情况是,企业级通知方案往往需要复杂的API对接(如企业微信、钉钉、飞书),个人开发者的小项目又经…

2026/7/22 0:00:26 阅读更多 →
甲方要的“简洁“PPT,到底是简洁还是省事?

甲方要的“简洁“PPT,到底是简洁还是省事?

甲方说"简洁一点",乙方听到的是"少做几页"。甲方说"不要太复杂",乙方理解成"别放图表了"。结果交过去,甲方说"我说的简洁不是这个意思"。"简洁"这个词在PPT语境里,是…

2026/7/22 0:00:26 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/21 8:48:31 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/21 5:34:47 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/21 8:25:39 阅读更多 →

月新闻