鸿蒙应用开发—数据持久化之SQLite

文章目录

    • SQLite简介
    • 创建数据库
    • 添加数据
    • 查询数据
    • 更新数据
    • 删除数据
    • 升级数据库
    • 使用事务
    • 参考

SQLite简介

SQLite是一个轻量级关系数据库,占用资源很少,只有几百KB的大小,无需服务器支撑,是一个零配置、事务性的SQL数据库引擎。

相对于首选项Preferences,SQLite更适合存储大量复杂的关系型数据,首选项则适合于保存一些简单的键值对数据;比如IM应用的聊天会话信息的本地存储,用首选项存储是明显是不合适,因为其数据量是极大的,数据关系结构也很复杂,在这方面首选项是明显是不合适的,SQLite则可以很轻松存储操作这些数据。那么SQLite在鸿蒙中是如何使用的,下面会一一讲解。

创建数据库

在@kit.ArkData方舟数据管理模块中,为开发者提供数据存储、数据管理和数据同步能力,而SQLite的服务则在这个模块中,专门提供了一个relationalStore来辅助创建数据库。我们以一个用户信息为例,创建一个名称是user.db的数据库,首先创建DBUtils类来管理数据库的行为操作。

import { relationalStore } from '@kit.ArkData'
import AppUtils from './AppUtils'
export default class DBUtils {
  private static rdbStore?: relationalStore.RdbStore

  private constructor() {
  }

  static init(callback: Function = (state: boolean, msg?: string) => {
  }) {
    const context = AppUtils.getContext()
    // 数据库配置
    const STORE_CONFIG: relationalStore.StoreConfig = {
      name: 'user.db', // 数据库名称
      securityLevel: relationalStore.SecurityLevel.S1, // 数据库安全级别
      encrypt: false, // 可选参数,指定数据库是否加密,默认不加密
    } as relationalStore.StoreConfig

    // 数据库文件的默认存储路径,可通过 customDir修改路径
    console.log(`${TAG} db dir: `, context.databaseDir)

    // 1、获取RdbStore实例,用于操作数据库
    relationalStore.getRdbStore(context, STORE_CONFIG).then((r) => {
      DBUtils.rdbStore = r
      console.log(TAG, 'db create success')
      callback(true)
    }).catch((err: Error) => {
      console.error(`${TAG} db create error: `, err.message)
      callback(false, err.message)
    })

  }
}

上面代码是配置和初始化数据库相关的配置,主要步骤:

  • 创建一个STORE_CONFIG的对象,包含了数据库配置的信息,有数据库名称、安全级别和加密状态。name是数据库文件名称,值是user.db,安全级别是relationalStore.SecurityLevel.S1,表示数据库的安全级别为低级别,当数据泄露时会产生较低影响,是不加密的状态。
  • 通过relationalStore获取RdbStore实例,这是操作数据库的接口,通过调用relationalStore.getRdbStore 函数并传入上下文和配置对象来实现。在创建数据库成功后,会执行then代码块,接着将RdbStore实例赋值给DBUtils.rdbStore,这样使得这个实例可以被 DBUtils 类的其他方法使用。
  • 最后在外部触发调用DBUitls的init()方法就完成了数据库的创建。当在控制台有打印db create success日志则表示数据库文件创建成功了。

数据库的安全级别除了S1,还有S2、S3、S4,如下:

属性概述
S11表示数据库的安全级别为低级别,当数据泄露时会产生较低影响。例如,包含壁纸等系统数据的数据库。
S22表示数据库的安全级别为中级别,当数据泄露时会产生较大影响。例如,包含录音、视频等用户生成数据或通话记录等信息的数据库。
S33表示数据库的安全级别为高级别,当数据泄露时会产生重大影响。例如,包含用户运动、健康、位置等信息的数据库。
S44表示数据库的安全级别为关键级别,当数据泄露时会产生严重影响。例如,包含认证凭据、财务数据等信息的数据库。

上面AppUtils是一个简单的工具类,用于存储全局context实例,代码如下所示:

import { common } from '@kit.AbilityKit'

export default class AppUtils {
  private constructor() {
  }

  private static context: common.UIAbilityContext

  static init(context: common.UIAbilityContext) {
    AppUtils.context = context
  }

  static getContext(): common.UIAbilityContext {
    if (!AppUtils.context) {
      throw new Error('在EntryAbility类的onCreate()方法中调用init()方法完成初始化')
    }
    return AppUtils.context
  }
}

通常会在EntryAbility的onCreate()方法中初始化,代码如下所示:

export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    AppUtils.init(this.context)
}

如果我们需要查看数据库文件在设备中的位置,可以通过context上下文获取数据库文件目录,代码如下所示:

// 数据库文件的默认存储路径,可通过 customDir修改路径
const context = AppUtils.getContext()
console.log(`${TAG} db dir: `, context.databaseDir)

应用创建的数据库与其上下文(Context)有关,即使使用同样的数据库名称,但不同的应用上下文,会产生多个数据库,例如每个UIAbility都有各自的上下文。

在控制台我们可以看到打印数据库文件的默认存储路径,使用 console.log 来展示数据库目录的路径。如下:

DBUtils db dir:  /data/storage/el2/database/entry

从日志可知数据库文件默认位置是/data/storage/el2/database/entry,但在DevEco Studio 5.0.3.400 API12 上,发现没有data目录下没有storage目录,反而在app目录下可以找到对应的数据库文件,完整的文件路径是/data/app/el2/database/entry。
我们可以在DevEco Studio编译器中的Device File Browser工具栏中可以查看到数据库文件。如下图所示:

在这里插入图片描述

如果希望移动数据库文件到其它地方使用查看,则需要同时移动这些以-wal和-shm结尾的临时文件。

在创建数据库文件后,此时就要创建数据表来描述数据,这里以创建一个USER表为例,user表包括了id,name、age、sex 、height、weight属性,然后通过RdbStore实例的executeSql()方法执行创建数据表的SQL语句,代码如下所示:

 static createTable() {
    // 创建USER表的SQL语句
    const CREATE_TABLE_USER =
      'CREATE TABLE IF NOT EXISTS USER(ID INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL, sex INTEGER NOT NULL, height REAL, weight REAL)'

    // 2、创建表
    DBUtils.rdbStore?.executeSql(CREATE_TABLE_USER).then(() => {
      console.log(TAG, 'create table done')
    }).catch((err: BusinessError) => {
      console.error(TAG, err.message)
    })
  }

DBUtils.rdbStore对象实例还记得是如何获取的吗,在创建数据库文件时通过relationalStore.getRdbStore()来获取的,这里把RdbStore实例赋值给了DBUtils类的rdbStore属性。如果数据表创建异常则会执行catch代码块,创建成功则执行then代码块。

在完成创建数据USER表后,如何可视化查看其内容呢,除了将user.db数据库文件导出通过SQLite Studio查看外,我们还可以借助于IDE的Database Navigator插件,目前在DevEco Studio的Setting -> Plugins 是无法搜索到该插件,但在其他IDE(Android Studio、intellij idea)是正常能搜索安装的,既然在线无法安装,我们可以去JetBrains 插件应用市场手动下载后,离线安装。

打开网址https://plugins.jetbrains.com/idea 搜索下载

在这里插入图片描述

下载后是一个压缩包无需解压,然后在DevEco Studio -> Settings -> Pulgins 安装离线包,选择 Install plugin from Disk选项,选择下载的压缩包进行安装,完成安装后需要重启DevEco Studio才会生效,如下图所示:

在这里插入图片描述

安装成功后会在DevEco Studio的左侧边栏,现在应该多出了一个DB Browser工具,然后接着回到Device File Browser工具栏打开数据库user.db、user.db-shm和user.db-wal三个文件,右击→Save As,将它从移动设备导出到你的计算机的任意位置。在DB Browser中选择SQLite,如下图所示:

在这里插入图片描述

最后在弹出窗中选择刚才导出的user.db数据库文件,然后点击OK完成配置。

在这里插入图片描述

完成配置后,在IDE左侧的DB Browser工具栏可以USER表的信息,如下图所示:

在这里插入图片描述

添加数据

对数据库的操作无非就是CRUD,C代表添加(create),R代表查询(retrieve),U代表更新(update),D代表删除(delete);每个操作都有对应的SQL语句,其中添加数据是insert,查询数据是select,更新数据是update,删除数据是delete。

使用RdbStore的insert()方法添加参数,第一个参数是表名,第二参数是要插入到表中的数据,是ValuesBucket对象,需要将表中每一列设置对应的值,是一个异步方法,插入成功则返回的数据在表中的行数,插入失败则返回-1,代码所下所示:

static insert() {
    let item: relationalStore.ValuesBucket = {
      name: 'lili',
      age: 18,
      sex: 0,
      height: 160,
      weight: 45,
    };
    let item2: relationalStore.ValuesBucket = {
      name: 'hzw',
      age: 28,
      sex: 1,
      height: 180,
      weight: 60,
    };
    // 插入数据
    DBUtils.rdbStore?.insert(DBUtils.tableName, item).then((r) => {
      console.log(TAG, 'insert success: ', r);
      DBUtils.rdbStore?.commit()
    }).catch((e: Error) => {
      console.log(TAG, 'insert err: ', e.message);
    });
    
    DBUtils.rdbStore?.insert(DBUtils.tableName, item2).then((r) => {
      console.log(TAG, 'insert success: ', r);
      DBUtils.rdbStore?.commit()
    }).catch((e: Error) => {
      console.log(TAG, 'insert err: ', e.message);
    });

}

在上面代码添加了两条数据,首先创建ValuesBucket对象,对表中的每一列赋值,你会发现我们并没有给id赋值,这是因为在创建USER表时我们将id设置了自增,然后通过DBUtils.rdbStore对象的insert方法,用来将数据item插入到名为DBUtils.tableName的表中,DBUtils.rdbStore对象是前面已提及到了,是rdbStore的实例,而DBUtils.tableName的值是USER。

接下来测试添加数据的insert()方法,给布局添加插入数据的按钮,如下图所示:

@Entry
@Component
struct Index {
  build() {
    Column() {
      Scroll() {
        Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceEvenly }) {
           Button('创建数据库')
            .btnStyle(OperateType.CREATE_DB)
          Button('创建User表')
            .btnStyle(OperateType.CREATE_TABLE)
          Button('插入数据')
            .btnStyle(OperateType.INSERT)
        }
        .width('100%')
        .height('30%')
      }

    }
    .height('100%')
    .width('100%')
  }
}

@Extend(Button)
function btnStyle(type: number, call?: (r: string) => void) {
  .margin({ top: '12vp' })
  .onClick(() => {
    switch (type) {
      case OperateType.CREATE_DB:
        DBUtils.init()
        break
      case OperateType.CREATE_TABLE:
        DBUtils.createTable()
        break
      case OperateType.INSERT:
        // 添加数据
        DBUtils.insert()
        break
    }
  })
}

class OperateType {
  static readonly CREATE_DB: number = 0
  static readonly CREATE_TABLE: number = 1
  static readonly INSERT: number = 2
  static readonly DELETE: number = 3
  static readonly UPDATE: number = 4
  static readonly QUERY: number = 5
}

上面底代码运行程序后,如下图所示:

在这里插入图片描述

点击“插入数据”按钮,会调用 DBUtils.insert()方法,将两条数据便会添加到USER数据表中,在insert()异步方法中的then代码块中会返回数据在表中行数,则表示数据添加成功。另外我们也通过DB Browser查看,将user.db等三个数据库文件重新导出到指定目录,由于之前已连接数据库,重新导出覆盖后,点击数据USER表自动刷新重载。如下图所示:

在这里插入图片描述

从上图可知,我们已成功添加了两条数据到USER表中。

查询数据

在添加数据案例中,我们是通过DB Browser工具查看数据的,在实际开发中通常会通过SQL语句来查询数据,在鸿蒙中RdbStore实例提供相关query()查询数据的方法。代码如下所示:

  static query() {
    // 创建RdbPredicates实例
    let predicates = new relationalStore.RdbPredicates(DBUtils.tableName);
    //equalTo 方法的第一个参数是列名,第二个参数是列值
    // 查询name=lili的数据
    predicates.equalTo("name", "lili")
    DBUtils.rdbStore?.query(predicates).then((r) => {
      let items: Array<relationalStore.ValuesBucket> = []
      // 遍历查询结果
      while (r.goToNextRow()) {
        // 获取当前行的数据
        const row = r.getRow()
        items.push(row)
      }
      console.log(TAG, 'query success: ', JSON.stringify(items, null, 2));
      // 关闭查询结果集
      r.close()

    }).catch((e: Error) => {
      console.log(TAG, 'query err: ', e.message);
    })

    // 执行SQL语句 查询USER表的所有数据
    DBUtils.rdbStore?.querySql(`SELECT * FROM ${DBUtils.tableName}`).then((r) => {
      let items: Array<relationalStore.ValuesBucket> = []
      // 遍历查询结果
      while (r.goToNextRow()) {
        // 获取当前行的数据
        const row = r.getRow()
        items.push(row)
      }
      console.log(TAG, 'querySql success: ', JSON.stringify(items, null, 2));
      // 关闭查询结果集
      r.close()
    }).catch((e: Error) => {
      console.log(TAG, 'querySql err: ', e.message);
    })

  }

上面代码中query()和querySql()两个不同方法来查询数据,query()方法需要接收RdbPredicates实例,在RdbPredicates的构造函数设置表名USER,通过predicates对象来设置查询的条件,equalTo()方法的第一个参数是列名,第二个参数是列值,则查询name是lili值的数据。在then代码块中通过ResultSet遍历查询每个行的数据,当查询完毕后,ResultSet会调用close()方法释放所有的资源。querySql()方法的参数则是SQL语句,上面是查询所有的数据,then代码块的逻辑与query()方法的then是类似的。

下面我们在布局添加一个“查询数据”的按钮,点击按钮时通过DBUtils调用静态query()方法,代码如下所示:

Button('查询数据')
     .btnStyle(OperateType.QUERY)

@Extend(Button)
function btnStyle(type: number, call?: (r: string) => void) {
  .margin({ top: '12vp' })
  .onClick(() => {
    switch (type) {
      case OperateType.CREATE_DB:
        DBUtils.init()
        break
      case OperateType.CREATE_TABLE:
        DBUtils.createTable()
        break
      case OperateType.INSERT:
        DBUtils.insert()
        break
      case OperateType.QUERY:
        // 查询数据
        DBUtils.query()
        break
    }
  })
}     
     
            

运行上面的程序后,如下图所示:

在这里插入图片描述

点击“查询数据”按钮后,便会执行查询,在控制台会输出查询结果,如下所示:

在这里插入图片描述

查询是一个相对复杂的操作,equalTo()方法只是RdbPredicates其中一个,系统还提供的其他查询条件的API,如下所示:

在这里插入图片描述

上面的例子只是一个简单的案例,在实际开发中要靠自己去慢慢摸索。

更新数据

在学习完添加和查询数据后,更新和删除的数据变更就可以通过查询方式来观察变化,就不需要将数据库文件导出这样繁琐操作了。RdbStore提供了update()方法来更新数据。代码如下所示:

  static update() {
    // 设置更新的列值,这里设置了age列的值为25
    let valueBucket: relationalStore.ValuesBucket = {age: 25}
    // 创建RdbPredicates实例
    let predicates = new relationalStore.RdbPredicates(DBUtils.tableName);
    // 设置查询的条件,name列的值为lili的数据
    predicates.equalTo("name", "lili")
    // 执行更新操作
    DBUtils.rdbStore?.update(valueBucket, predicates).then((r: number) => {
      DBUtils.rdbStore?.commit()    
      // 打印更新的行数
      console.log(TAG, 'update success: ', r)
    }).catch((e: Error) => {
      console.log(TAG, 'update err: ', e.message)
    })
  }

上面代码是将lili名称的age值由原来的18改成25,接着我们在布局添加一个“更新数据”的按钮,点击按钮时通过DBUtils调用静态update()方法,代码如下所示:

Button('更新数据')
.btnStyle(OperateType.UPDATE)

@Extend(Button)
function btnStyle(type: number, call?: (r: string) => void) {
  .margin({ top: '12vp' })
  .onClick(() => {
    switch (type) {
      case OperateType.UPDATE:
        DBUtils.update()
        break

    }
  })
}

运行上面的程序后,如下图所示:

在这里插入图片描述

点击“更新数据”按钮后,便会执行更新对应的数据,然后点击查询更新后的数据,在控制台会输出更新后的结果,如下所示:

在这里插入图片描述

从日志可知,在执行update()更新操作后,我们更新值是生效了,age值由原来的18变成了25.

删除数据

在知道了查询数据后,删除数据则相对很简单了,RdbStore提供了delete()方法来删除数据,只接收一个参数RdbPredicates,前面已经使用过多次了,已经熟能生巧了就不多讲了,直接上代码了。

  static delete() {
    // 创建RdbPredicates实例,用于设置查询条件 ,指定查询的表名
    let predicates = new relationalStore.RdbPredicates(DBUtils.tableName);
    // 设置删除的条件,name列的值为hzw的数据
    predicates.equalTo("name", "hzw")
    DBUtils.rdbStore?.delete(predicates).then((r: number) => {
       DBUtils.rdbStore?.commit()    
      // 打印删除的行数
      console.log(TAG, 'delete success: ', r)
    }).catch((e: Error) => {
      console.log(TAG, 'delete err: ', e.message)
    })
  }
 

上面代码是删除name值为hzw的数据,接着我们在布局添加一个“删除数据”的按钮,点击按钮时通过DBUtils调用静态delete()方法,代码如下所示:

 Button('删除数据')
            .btnStyle(OperateType.DELETE)
@Extend(Button)
function btnStyle(type: number, call?: (r: string) => void) {
  .margin({ top: '12vp' })
  .onClick(() => {
    switch (type) {
      case OperateType.DELETE:
        DBUtils.delete()
        break
    }
  })
}

运行上面的程序后,如下图所示:

在这里插入图片描述

点击“删除数据”按钮后,便会删除对应的数据,然后点击查询删除后的数据,在控制台会输出删除后的结果,如下所示:

在这里插入图片描述

从日志可知,在执行delete()方法执行删除操作后,name为hzw的这条数据记录已经被删除了,不存在USER表中了。

升级数据库

什么情况下需要升级升级库呢?比如我们的应用1.0版本已成功上线了,产品在规划2.0版本时,用户信息新增一个staffId字段,接着在3.0版本时又删除一个weight字段,此时数据库就要升级,确保在应用版本升级的过程中本地数据库的数据不会丢失。

在初始化数据库配置的getRdbStore()方法的then代码块中进行数据库版本升级。当数据库创建时,数据库默认版本是0,此时通常会创建需要的表,同时将数据库版本设为1,相当于从0升级到1,代码如下:

 // 数据库版本号是0时,创建数据表语句的SQL语句
const CREATE_TABLE_USER =
  'CREATE TABLE IF NOT EXISTS USER(ID INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL, sex INTEGER NOT NULL, height REAL, weight REAL)'

relationalStore.getRdbStore(context, STORE_CONFIG).then((store: relationalStore.RdbStore) => {
  DBUtils.rdbStore = store
  console.log(TAG, 'db ver: ',store.version)
  // 升级数据库
  // 当数据库创建时,数据库默认版本为0
  if (store.version == 0) {
      store.executeSql(CREATE_TABLE_USER)
      // 将版本设置为1 相当于版本号从0升级到1
      store.version = 1
  }

}).catch((err: Error) => {
  console.error(`${TAG} db create error: `, err.message)
})


此时随着应用版本迭代升级,USER表新增了一个staffId字段,创建USER表的SQL语句则需要增加一个staffId字段,代码如下所示:

 //  新增staffId字段,创建数据表语句的SQL语句
const CREATE_TABLE_USER_1 =
  'CREATE TABLE IF NOT EXISTS USER(ID INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL, sex INTEGER NOT NULL, height REAL, weight REAL, staffId INTEGER)'
  
relationalStore.getRdbStore(context, STORE_CONFIG).then((store: relationalStore.RdbStore) => {
  DBUtils.rdbStore = store
  console.log(TAG, 'db ver: ',store.version)
  // 升级数据库

  const oldVersion = store.version
  // 当数据库创建时,数据库默认版本为0
  if (store.version == 0) {
      store.executeSql(CREATE_TABLE_USER_1)
      // 设置数据库最高版本2 ,这里始终设置成最高版本号
      store.version = 2
  }

  // 如果是数据库版本从1 升级到 2,则需要新增staffId字段  
  if (store.version == 1) {
    store.executeSql('alter table USER add column staffId integer')
    // 数据库版本升级为2
    store.version = 2 
  }


}).catch((err: Error) => {
  console.error(`${TAG} db create error: `, err.message)
})  

如果是一个新用户初次安装应用,数据库默认版本号是0,会调用executeSql()方法按照最新的SQL语句(CREATE_TABLE_USER_1)创建USER表,同时将数据库版本号设置成最高版本2,这样就不会执行后面if升级逻辑,如果这个用户的数据库版本是1,则会通过executeSql()方法执行alter table USER add column staffId integerSQL语句新增staffId字段,同时将数据库版本号升级为2。

当数据库由版本1升级成2时,我们去查询数据,会发现数据表中有staffId字段了,如下:

DBUtils query success:  [{
    "ID": 1,
    "age": 18,
    "height": 160,
    "name": "lili",
    "sex": 0,
    "staffId": null,
    "weight": null
}]

接着后面产品又说,USER表中不需要weight字段了,开发者此时从中表中去除,就需要修改创建USER表的语句,这样数据库又要升级,由版本2升级为3,代码如下:

//  去除weight字段,创建数据表语句的SQL语句
const CREATE_TABLE_USER_2 =
  'CREATE TABLE IF NOT EXISTS USER(ID INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL, sex INTEGER NOT NULL, height REAL, staffId INTEGER)'


relationalStore.getRdbStore(context, STORE_CONFIG).then((store: relationalStore.RdbStore) => {
  DBUtils.rdbStore = store
  console.log(TAG, 'db ver: ',store.version)
  // 升级数据库

  const oldVersion = store.version
  // 当数据库创建时,数据库默认版本为0
  if (store.version == 0) {
      store.executeSql(CREATE_TABLE_USER_2)
      // 这里始终设置为最高版本 3
      store.version = 3
  }

  // 如果是数据库版本从1 升级到 2,则需要新增staffId字段
  if (store.version == 1) {
    store.executeSql('alter table USER add column staffId integer')
    store.version = 2
  }

  // 如果是数据库版本从2 升级到 3,则需要去除weight字段
  if (store.version == 2) {
    store.executeSql('alter table USER drop column weight')
    store.version = 3
  }

}).catch((err: Error) => {
  console.error(`${TAG} db create error: `, err.message)
})

这里的升级逻辑与升级到版本2的逻辑是类似的,就不多述了。当版本升级为3时我们去查询数据,weight字段则不存在了,如下所示:

DBUtils query success:  [{
    "ID": 1,
    "age": 18,
    "height": 160,
    "name": "lili",
    "sex": 0,
    "staffId": null,
}]

使用这种if方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据完全不会丢失。

使用事务

SQLite数据库是支持事务的,事务是指一系列操作,要么全部完成,那么全部不完成,是原子性操作。比如我们常用的转账功能,A账户向B账户转账,可以分为两个步骤,从A账户扣钱,然后再往B账户打入等量的金额,这两个动作是独立的操作,可能存在一个成功,一个失败,比如A账户扣钱成功了,B账户没有收到钱,出现这种情况是很危险的,如何确保两个独立操作要么全部失败,要么全部成功,当某个失败时,就回滚到初始状态,此时事务就派上用场了。

在鸿蒙中如何使用事务,rdbStore提供了beginTransaction() 和rollBack()方法来保证事务,确保操作时原子性。下面以一个简单案例为例,代码如下:

static async transaction(isError: boolean = true){
    // 开启事务
    DBUtils.rdbStore?.beginTransaction()
    try {
      // 删除hzw的数据
      let predicates = new relationalStore.RdbPredicates(DBUtils.tableName);
      predicates.equalTo("name", "hzw")
      let rowNum = await DBUtils.rdbStore?.delete(predicates)
      DBUtils.rdbStore?.commit()
      console.log(TAG, 'delete success: ', rowNum)
      if (isError) {
          // 制造一个异常,让事务失败
         throw new Error('error')
      }
      let xml: relationalStore.ValuesBucket = {
        name: 'xml',
        age: 28,
        sex: 0,
        height: 165,
      };
      const num = await DBUtils.rdbStore?.insert(DBUtils.tableName, xml)
      DBUtils.rdbStore?.commit()
      console.log(TAG, 'insert success: ', num)
    } catch (e) {
      // 回滚
      console.log(TAG, '回滚');
      DBUtils.rdbStore?.rollBack()
    }
}

上面代码的原子性逻辑是先删除hzw的数据,然后添加xml名称的数据。

首先在执行SQL前通过beginTransaction()方法开启事务,接着删除hzw的数据,此时已经执行删除的SQL语句,但当isError为true时,这人为制造一个异常中断整个流程,导致事务的失败,但添加数据的操作还未执行。不过由于在catch代码块中,调用了rollBack()方法回滚到开启事务处,因此hzw数据是删除不了的。

参考

  • https://blog.csdn.net/K346K346/article/details/114085663

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/984844.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

应急响应--流量分析

&#xff08;一&#xff09;Cobalt Strike流量特征分析 1.HTTP特征 源码特征&#xff1a; 在流量中&#xff0c;通过http协议的url路径&#xff0c;在checksum8解密算法计算后&#xff0c;32位的后门得到的结果是92&#xff0c;64位的后门得到的结果是93&#xff0c;该特征符…

初始化E9环境,安装Sqlserver数据库

title: 初始化E9环境,安装Sqlserver数据库 date: 2025-03-10 19:27:19 tags: E9SqlServer初始化E9环境,安装Sqlserver数据库 安装E9本地环境安装Sql server 数据库1、检查SQL Server服务是否开启2、检查SQL Server网络网络配置是否开启创建一个ecology数据库点击初始化数据库…

自然语言处理:无监督朴素贝叶斯模型

介绍 大家好&#xff0c;博主又来和大家分享自然语言处理领域的知识了&#xff0c;今天给大家介绍的是无监督朴素贝叶斯模型。 在自然语言处理这个充满挑战又极具魅力的领域&#xff0c;如何从海量的文本数据中挖掘有价值的信息&#xff0c;一直是研究者们不断探索的课题。无…

API调试工具的无解困境:白名单、动态IP与平台设计问题

引言 你是否曾经在开发中遇到过这样的尴尬情形&#xff1a;你打开了平台的API调试工具&#xff0c;准备一番操作&#xff0c;结果却发现根本无法连接到平台&#xff1f;别急&#xff0c;问题出在调试工具本身。今天我们要吐槽的就是那些神奇的开放平台API调试工具&#xff0c;…

VSCode 2025最新前端开发必备插件推荐汇总(提效指南)

&#x1f31f;前言: 如果你是一名前端开发工程师&#xff0c;合适的开发工具能大大提高工作效率。Visual Studio Code (VSCode) 凭借其轻量级、高扩展性的特点&#xff0c;已成为众多前端开发者在win系电脑的首选IDE。 名人说&#xff1a;博观而约取&#xff0c;厚积而薄发。—…

小程序事件系统 —— 33 事件传参 - data-*自定义数据

事件传参&#xff1a;在触发事件时&#xff0c;将一些数据作为参数传递给事件处理函数的过程&#xff0c;就是事件传参&#xff1b; 在微信小程序中&#xff0c;我们经常会在组件上添加一些自定义数据&#xff0c;然后在事件处理函数中获取这些自定义数据&#xff0c;从而完成…

初阶数据结构(C语言实现)——4.2队列

目录 2.队列2.1队列的概念及结构2.2队列的实现2.2.1 初始化队列2.2.2 销毁队列2.2.3 队尾入队列2.2.4 队头出队列2.2.5获取队列头部元素2.2.6 获取队列队尾元素2.2.7获取队列中有效元素个数2.2.8 检测队列是否为空&#xff0c;如果为空返回非零结果&#xff0c;如果非空返回0 3…

linux 命令 cat

cat 是 Linux 中用于查看、创建和合并文件的常用命令&#xff0c;全称 concatenate&#xff08;连接&#xff09;。其核心功能是将文件内容输出到终端或重定向到其他文件/命令中。以下是详细用法及场景示例&#xff1a; 基本语法 cat [选项] [文件1] [文件2] ... 选项…

TON基金会确认冠名赞助2025香港Web3嘉年华,并将于4月8日重磅呈现“TON生态日”

近日&#xff0c;由万向区块链实验室与HashKey Group联合推出的Web3年度盛典——2025香港Web3嘉年华正式宣布&#xff0c;TON基金会确认成为本届嘉年华的冠名赞助商&#xff0c;并将于4月8日在主会场特别举办“TON生态日”专题Side Event&#xff0c;集中展现TON生态的最新技术…

【Java代码审计 | 第七篇】文件上传漏洞成因及防范

未经许可&#xff0c;不得转载。 文章目录 文件上传漏洞漏洞成因未验证文件类型和扩展名未限制文件上传路径 防范验证文件类型和扩展名验证文件内容限制文件上传路径使用安全的文件上传库 标准代码 文件上传漏洞 文件上传漏洞是指攻击者通过上传恶意文件&#xff08;如可执行脚…

【无人机路径规划】基于麻雀搜索算法(SSA)的无人机路径规划(Matlab)

效果一览 代码获取私信博主基于麻雀搜索算法&#xff08;SSA&#xff09;的无人机路径规划&#xff08;Matlab&#xff09; 一、算法背景与核心思想 麻雀搜索算法&#xff08;Sparrow Search Algorithm, SSA&#xff09;是一种受麻雀群体觅食行为启发的元启发式算法&#xff0…

狮子座大数据分析(python爬虫版)

十二星座爱情性格 - 星座屋 首先找到一个星座网站&#xff0c;作为基础内容&#xff0c;来获取信息 网页爬取与信息提取 我们首先利用爬虫技术&#xff08;如 Python 中的 requests 与 BeautifulSoup 库&#xff09;获取页面内容。该页面&#xff08;xzw.com/astro/leo/&…

DeepSeek教我写词典爬虫获取单词的音标和拼写

Python在爬虫领域展现出了卓越的功能性&#xff0c;不仅能够高效地抓取目标数据&#xff0c;还能便捷地将数据存储至本地。在众多Python爬虫应用中&#xff0c;词典数据的爬取尤为常见。接下来&#xff0c;我们将以dict.cn为例&#xff0c;详细演示如何编写一个用于爬取词典数据…

AI智能导航站HTML5自适应源码帝国cms7.5模板

源码名称&#xff1a;AI导航站HTML5自适应源码帝国cms7.5模板 开发环境&#xff1a;帝国cms 7.5 安装环境&#xff1a;phpmysql var code "4d33ef8e-9e38-43b9-b37b-38f75944ecc9" 带软件采集&#xff0c;可以挂着自动采集发布&#xff0c;无需人工操作&#xff0…

【贪心算法】将数组和减半的最小操作数

1.题目解析 2208. 将数组和减半的最少操作次数 - 力扣&#xff08;LeetCode&#xff09; 2.讲解算法原理 使用当前数组中最大的数将它减半&#xff0c;&#xff0c;直到数组和减小到一半为止&#xff0c;从而快速达到目的 重点是找到最大数&#xff0c;可以采用大根堆快速达到…

Apache XTable:在数据湖仓一体中推进数据互作性

Apache XTable 通过以多种开放表格式提供对数据的访问&#xff0c;在增强互作性方面迈出了一大步。移动数据很困难&#xff0c;在过去&#xff0c;这意味着在为数据湖仓一体选择开放表格式时&#xff0c;您被锁定在该选择中。一个令人兴奋的项目当在数据堆栈的这一层引入互作性…

hive面试题--left join的坑

student 表&#xff1a; 课程表course: 1、key为null, 不关联 select * from student s left join course c on s.id c.s_id;2、on中过滤条件 与 where 过滤条件区别 on and c.id<>‘1001’ 先过滤右表数据&#xff0c;然后与左表关联 select * from student s le…

2路模拟量同步输出卡、任意波形发生器卡—PCIe9100数据采集卡

品牌&#xff1a;阿尔泰科技 型号&#xff1a; PCIe9100、PCIe9101、PXIe9100、PXIe9101 产品系列&#xff1a;任意波形发生器 支持操作系统&#xff1a;XP、Win7、Win8、Win10 简要介绍&#xff1a; 910X 系列是阿尔泰科技公司推出的 PCIe、PXIe 总线的任意波形发生器&…

elementUI改样式失败问题——DatePicker 日期选择器

今天做一个vue2的项目时&#xff0c;发现使用deep对时间选择器的选择控件不生效&#xff0c;因为elementUI官方文档里写了&#xff1a; popper-classDatePicker 下拉框的类名 并且通过浏览器可以发现&#xff0c;选择控件是直接挂在body下的&#xff0c;所以解决方法是直接找到…

C++ 链表List使用与实现:拷贝交换与高效迭代器细致讲解

目录 list的使用&#xff1a; 构造与赋值 元素访问 修改操作 容量查询 链表特有操作 拼接&#xff08;Splice&#xff09; C11 新增方法 注意&#xff1a; stl_list的模拟实现&#xff1a; 一、链表节点设计的艺术 1.1 结构体 vs 类的选择 二、迭代器实现的精髓 2…