33.HarmonyOS App(JAVA)鸿蒙系统app数据库增删改查

news/2024/7/21 8:30:09 标签: harmonyos, java, 数据库

33.HarmonyOS App(JAVA)鸿蒙系统app数据库增删改查

关系数据库

关系对象数据库(ORM)

应用偏好数据库

分布式数据库

关系型数据库(Relational Database,RDB)是一种基于关系模型来管理数据的数据库。HarmonyOS关系型数据库基于SQLite组件提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的SQL语句来满足复杂的场景需要。HarmonyOS提供的关系型数据库功能更加完善,查询效率更加高效。

基本概念

  • 关系型数据库

    基于关系模型来管理数据的数据库,以行和列的形式存储数据。

  • 谓词

    数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数据库的操作条件。

  • 结果集

    指用户查询之后的结果集合,可以对数据进行访问。结果集提供了灵活的数据访问方式,可以更方便的拿到用户想要的数据。

  • SQLite数据库

    一款轻型的数据库,是遵守ACID的关系型数据库管理系统。它是一个开源的项目。

运作机制

HarmonyOS关系型数据库对外提供通用的操作接口,底层使用SQLite作为持久化存储引擎,支持SQLite具有的所有数据库特性,包括但不限于事务、索引、视图、触发器、外键、参数化查询和预编译SQL语句。

图1 关系型数据库运作机制

默认配置

  • 如果不指定数据库的日志模式,那么系统默认日志方式是WAL(Write Ahead Log)模式。
  • 如果不指定数据库的落盘模式,那么系统默认落盘方式是FULL模式。
  • HarmonyOS数据库使用的共享内存默认大小是2MB。

约束与限制

  • 数据库中连接池的最大数量是4个,用以管理用户的读写操作。
  • 为保证数据的准确性,数据库同一时间只能支持一个写操作

接口说明

数据库的创建和删除

关系型数据库提供了数据库创建方式,以及对应的删除接口,涉及的API如下所示。

表1 数据库创建和删除API

类名

接口名

描述

DatabaseHelper

DatabaseHelper(Context context)

DatabaseHelper是数据库操作的辅助类,当数据库创建成功后,数据库文件将存储在由上下文指定的目录里。数据库文件存储的路径会因指定不同的上下文存在差异。

  • 获取上下文参考方法:ohos.app.Context#getApplicationContext()、ohos.app.AbilityContext#getContext()。
  • 查看详细路径信息:ohos.app.Context#getDatabaseDir()。

StoreConfig.Builder

public StoreConfig builder()

数据库进行配置,包括设置数据库名、存储模式、日志模式、同步模式,是否为只读,及数据库加密。

RdbOpenCallback

public abstract void onCreate(RdbStore store)

数据库创建时被回调,开发者可以在该方法中初始化表结构,并添加一些应用使用到的初始化数据。

RdbOpenCallback

public abstract void onUpgrade(RdbStore store, int currentVersion, int targetVersion)

数据库升级时被回调。

RdbOpenCallback

public void onDowngrade(RdbStore store, int currentVersion, int targetVersion)

数据库降级时被回调。

DatabaseHelper

public RdbStore getRdbStore(StoreConfig config, int version, RdbOpenCallback openCallback, ResultSetHook resultSetHook)

根据配置创建或打开数据库

DatabaseHelper

public boolean deleteRdbStore(String name)

删除指定的数据库

数据库的加密

关系型数据库提供数据库加密的能力,在创建数据库时若指定了密钥,则会创建为加密数据库再次使用此数据库时,仍需要指定相同密钥,才能正确打开数据库

表2 数据库传入密钥接口

类名

接口名

描述

StoreConfig.Builder

public StoreConfig.Builder setEncryptKey(byte[] encryptKey)

数据库设置数据库加密密钥的配置类,创建或打开数据库时传入包含数据库加密密钥的配置类,即可创建或打开加密数据库

数据库的增删改查

关系型数据库提供对本地数据增删改查操作的能力,相关API如下所示。

  • 新增关系型数据库提供了插入数据的接口,通过ValuesBucket输入要存储的数据,通过返回值判断是否插入成功,插入成功时返回最新插入数据所在的行号,失败时则返回-1。
    表3 数据库插入API

    类名

    接口名

    描述

    RdbStore

    long insert(String table, ValuesBucket initialValues)

    数据库插入数据。

    • table:待添加数据的表名。
    • initialValues:以ValuesBucket存储的待插入的数据。它提供一系列put方法,如putString(String columnName, String values),putDouble(String columnName, double value),用于向ValuesBucket中添加数据。
  • 更新

    调用更新接口,传入要更新的数据,并通过AbsRdbPredicates指定更新条件。该接口的返回值表示更新操作影响的行数。如果更新失败,则返回0。

    表4 数据库更新API

    类名

    接口名

    描述

    RdbStore

    int update(ValuesBucket values, AbsRdbPredicates predicates)

    更新数据库表中符合谓词指定条件的数据。

    • values:以ValuesBucket存储的要更新的数据。
    • predicates:指定了更新操作的表名和条件。AbsRdbPredicates的实现类有两个:RdbPredicates和RawRdbPredicates。
      • RdbPredicates:支持调用谓词提供的equalTo等接口,设置更新条件。
      • RawRdbPredicates:仅支持设置表名、where条件子句、whereArgs三个参数,不支持equalTo等接口调用。
  • 删除

    调用删除接口,通过AbsRdbPredicates指定删除条件。该接口的返回值表示删除的数据行数,可根据此值判断是否删除成功。如果删除失败,则返回0。

    表5 数据库删除API

    类名

    接口名

    描述

    RdbStore

    int delete(AbsRdbPredicates predicates)

    删除数据。

    predicates:Rdb谓词,指定了删除操作的表名和条件。AbsRdbPredicates的实现类有两个:RdbPredicates和RawRdbPredicates。

    • RdbPredicates:支持调用谓词提供的equalTo等接口,设置更新条件。
    • RawRdbPredicates:仅支持设置表名、where条件子句、whereArgs三个参数,不支持equalTo等接口调用。
  • 查询

    关系型数据库提供了两种查询数据的方式:

    • 直接调用查询接口。使用该接口,会将包含查询条件的谓词自动拼接成完整的SQL语句进行查询操作,无需用户传入原生的SQL语句。
    • 执行原生的SQL语句进行查询操作。
    表6 数据库查询API

    类名

    接口名

    描述

    RdbStore

    ResultSet query(AbsRdbPredicates predicates, String[] columns)

    查询数据。

    • predicates:谓词,可以设置查询条件。AbsRdbPredicates的实现类有两个:RdbPredicates和RawRdbPredicates。
      • RdbPredicates:支持调用谓词提供的equalTo等接口,设置查询条件。
      • RawRdbPredicates:仅支持设置表名、where条件子句、whereArgs三个参数,不支持equalTo等接口调用。
    • columns:规定查询返回的列。

    RdbStore

    ResultSet querySql(String sql, String[] sqlArgs)

    执行原生的用于查询操作的SQL语句。

    sql:原生用于查询的sql语句。

    sqlArgs:sql语句中占位符参数的值,若select语句中没有使用占位符,该参数可以设置为null。

数据库谓词的使用

关系型数据库提供了用于设置数据库操作条件的谓词AbsRdbPredicates,其中包括两个实现子类RdbPredicates和RawRdbPredicates:

  • RdbPredicates:开发者无需编写复杂的SQL语句,仅通过调用该类中条件相关的方法,如equalTo、notEqualTo、groupBy、orderByAsc、beginsWith等,就可自动完成SQL语句拼接,方便用户聚焦业务操作。
  • RawRdbPredicates:可满足复杂SQL语句的场景,支持开发者自己设置where条件子句和whereArgs参数。不支持equalTo等条件接口的使用。
表7 数据库谓词API

类名

接口名

描述

RdbPredicates

RdbPredicates equalTo(String field, String value)

设置谓词条件,满足field字段与value值相等。

RdbPredicates

RdbPredicates notEqualTo(String field, String value)

设置谓词条件,满足field字段与value值不相等。

RdbPredicates

RdbPredicates beginsWith(String field, String value)

设置谓词条件,满足field字段以value值开头。

RdbPredicates

RdbPredicates between(String field, int low, int high)

设置谓词条件,满足field字段在最小值low和最大值high之间。

RdbPredicates

RdbPredicates orderByAsc(String field)

设置谓词条件,根据field字段升序排列。

RawRdbPredicates

void setWhereClause(String whereClause)

设置where条件子句。

RawRdbPredicates

void setWhereArgs(List<String> whereArgs)

设置whereArgs参数,该值表示where子句中占位符的值。

查询结果集的使用

关系型数据库提供了查询返回的结果集ResultSet,其指向查询结果中的一行数据,供用户对查询结果进行遍历和访问。ResultSet对外API如下所示。

表8 结果集API

类名

接口名

描述

ResultSet

boolean goTo(int offset)

从结果集当前位置移动指定偏移量。

ResultSet

boolean goToRow(int position)

将结果集移动到指定位置。

ResultSet

boolean goToNextRow()

将结果集向后移动一行。

ResultSet

boolean goToPreviousRow()

将结果集向前移动一行。

ResultSet

boolean isStarted()

判断结果集是否被移动过。

ResultSet

boolean isEnded()

判断结果集当前位置是否在最后一行之后。

ResultSet

boolean isAtFirstRow()

判断结果集当前位置是否在第一行。

ResultSet

boolean isAtLastRow()

判断结果集当前位置是否在最后一行。

ResultSet

int getRowCount()

获取当前结果集中的记录条数。

ResultSet

int getColumnCount()

获取结果集中的列数。

ResultSet

String getString(int columnIndex)

获取当前行指定列的值,以String类型返回。

ResultSet

byte[] getBlob(int columnIndex)

获取当前行指定列的值,以字节数组形式返回。

ResultSet

double getDouble(int columnIndex)

获取当前行指定列的值,以double型返回。

事务

关系型数据库提供事务机制,来保证用户操作的原子性。对单条数据进行数据库操作时,无需开启事务;插入大量数据时,开启事务可以保证数据的准确性。如果中途操作出现失败,会自动执行回滚操作。

表9 事务API

类名

接口名

描述

RdbStore

void beginTransaction()

开启事务。

RdbStore

void markAsCommit()

设置事务的标记为成功。

RdbStore

void endTransaction()

结束事务。当调用此方法前若执行markAsCommit方法,事务会提交,否则事务会自动回滚。

事务和结果集观察者

关系型数据库提供了事务和结果集观察者能力,当对应的事件被触发时,观察者会收到通知。

类名

接口名

描述

RdbStore

void beginTransactionWithObserver(TransactionObserver transactionObserver)

开启事务,并观察事务的启动、提交和回滚。

ResultSet

void registerObserver(DataObserver observer)

注册结果集的观察者。

ResultSet

void unregisterObserver(DataObserver observer)

注销结果集的观察者。

数据库的备份和恢复

用户可以将当前数据库的数据进行保存备份,还可以在需要的时候进行数据恢复。

表10 数据库备份和恢复

类名

接口名

描述

RdbStore

boolean restore(String srcName)

数据库恢复接口,从指定的非加密数据库文件中恢复数据。

RdbStore

boolean restore(String srcName, byte[] srcEncryptKey, byte[] destEncryptKey)

数据库恢复接口,从指定的数据库文件(加密和非加密均可)中恢复数据。

RdbStore

boolean backup(String destName)

数据库备份接口,备份出的数据库文件是非加密的。

RdbStore

boolean backup(String destName, byte[] destEncryptKey)

数据库备份接口,此方法经常用在备份出加密数据库场景。

开发步骤

  1. 创建数据库
    1. 配置数据库相关信息,包括数据库的名称、存储模式、是否为只读模式等。
    2. 初始化数据库表结构和相关数据。
    3. 创建数据库

    示例代码如下:

     
      
    1. // 可以通过ohos.app.Context#getApplicationContext()或ohos.app.AbilityContext#getContext()获取context。
    2. DatabaseHelper helper = new DatabaseHelper(context);
    3. StoreConfig config = StoreConfig.newDefaultConfig("RdbStoreTest.db");
    4. RdbOpenCallback callback = new RdbOpenCallback() {
    5. @Override
    6. public void onCreate(RdbStore store) {
    7. store.executeSql("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, salary REAL, blobType BLOB)");
    8. }
    9. @Override
    10. public void onUpgrade(RdbStore store, int oldVersion, int newVersion) {
    11. }
    12. };
    13. RdbStore store = helper.getRdbStore(config, 1, callback, null);
  2. 插入数据。
    1. 构造要插入的数据,以ValuesBucket形式存储。
    2. 调用关系型数据库提供的插入接口。

    示例代码如下:

     
      
    1. ValuesBucket values = new ValuesBucket();
    2. values.putInteger("id", 1);
    3. values.putString("name", "zhangsan");
    4. values.putInteger("age", 18);
    5. values.putDouble("salary", 100.5);
    6. values.putByteArray("blobType", new byte[] {1, 2, 3});
    7. long id = store.insert("test", values);
  3. 查询数据。
    1. 构造用于查询的谓词对象,设置查询条件。
    2. 指定查询返回的数据列。
    3. 调用查询接口查询数据。
    4. 调用结果集接口,遍历返回结果。

    示例代码如下:

     
      
    1. String[] columns = new String[] {"id", "name", "age", "salary"};
    2. RdbPredicates rdbPredicates = new RdbPredicates("test").equalTo("age", 25).orderByAsc("salary");
    3. ResultSet resultSet = store.query(rdbPredicates, columns);
    4. resultSet.goToNextRow();
  4. 注册结果集观察者。
    1. 注册观察者,类型为DataObserverAsyncWrapper。
    2. 设置受影响的URI

    示例代码如下:

     
      
    1. resultSet.registerObserver(dataObserverAsyncWrapper);
    2. List<Uri> uris = new ArrayList<>();
    3. uris.add(Uri.parse(String.format((Const.BASE_URI + Const.DATA_PATH), "")));
    4. resultSet.setAffectedByUris(this, uris);

package com.example.myapplication.slice;

import com.example.myapplication.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.TextField;
import ohos.data.DatabaseHelper;
import ohos.data.rdb.*;
import ohos.data.resultset.ResultSet;


import java.util.ArrayList;
import java.util.List;

public class RdbAbilitySlice extends AbilitySlice {
    TextField textField;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_main1);
        Button btn_add = (Button) findComponentById(ResourceTable.Id_btn_insert);
        textField = (TextField) findComponentById(ResourceTable.Id_text_field_info);
        btn_add.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                AddData();
                //textField.append("已插入数据\n");

            }
        });
        Button btn_batch = (Button) findComponentById(ResourceTable.Id_btn_batch_insert);
        btn_batch.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                BatchAddData();
            }
        });
        Button btn_search = (Button) findComponentById(ResourceTable.Id_btn_search);
        btn_search.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                SearchData();
            }
        });
        Button btn_update = (Button) findComponentById(ResourceTable.Id_btn_update);
        btn_update.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                UpdateData();
            }
        });
        Button btn_delete = (Button) findComponentById(ResourceTable.Id_btn_delete);
        btn_delete.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                DeleteData();
            }
        });

    }
//删除数据
    private void DeleteData()
    {
        RdbStore store2 = getTestRdbStore();
        RdbPredicates rdbPredicates = new RdbPredicates("student").equalTo("name","dongting");
        int rowNum= store2.delete(rdbPredicates);
        if(rowNum==-1)
        {
            textField.append("删除失败");
        }
        else
        {
            textField.append("删除成功,Row Id:"+rowNum+"\n");
        }
        store2.close();//关闭rdbstore对象

    }
    private void SearchData()
    {
        //查询数据库所有内容,query谓词查询法
        RdbStore store2= getTestRdbStore();
        String[] columns = new String[]{"id","name","age","sex","class"};
        RdbPredicates rdbPredicates2 = new RdbPredicates("student").orderByAsc("id");
        ResultSet resultSet2 = (ResultSet) store2.query(rdbPredicates2,columns);

        //sql语句查询法
        //ResultSet resultSet2 = store2.querySql("select * from student where "+
               // "sex = ? and age between ? and ? order by id asc", new String[]{"1","15","20",});
        while (resultSet2.goToNextRow())
        {
            int id = resultSet2.getInt(0);
            String name = resultSet2.getString(1);
            int age = resultSet2.getInt(2);
            int sex = resultSet2.getInt(3);
            String class2 = resultSet2.getString(4);
            textField.append("id:"+id+
                    "姓名:"+name+
                    "年龄:"+age+
                    "性别:"+sex+
                    "班级:"+class2);
        }
        resultSet2.close();
        store2.close();

    }
    private void UpdateData()
    {
       RdbStore store2 = getTestRdbStore();
       RdbPredicates rdbPredicates = new RdbPredicates("student").equalTo("name","dongting");
       ValuesBucket valuesBucket = new ValuesBucket();
       valuesBucket.putString("class","学前班");
       int rowNum = store2.update(valuesBucket,rdbPredicates);
       if(rowNum==-1)
       {
           textField.append("更新失败\n");
       }
       else
       {
           textField.append("更新成功"+rowNum+"\n");
       }
       store2.close();
    }
    private void  AddData()
    {
        RdbStore store2 = getTestRdbStore();
        ValuesBucket  valuesBucket = new ValuesBucket();
        valuesBucket.putString("name","dongting");
        valuesBucket.putInteger("age",18);
        valuesBucket.putInteger("sex",1);
        valuesBucket.putString("class","一班");
        long id = store2.insert("student",valuesBucket);
        if(id==-1)
        {
            textField.append("数据插入失败");
        }
        else
        {
            textField.append("插入成功,Row Id:"+id+"\n");
        }
        store2.close();//关闭rdbstore对象
    }
    //批量插入数据
    private void BatchAddData()
    {
        RdbStore store2 = getTestRdbStore();
        List<ValuesBucket> stu_list = new ArrayList<>();
        ValuesBucket  valuesBucket1 = new ValuesBucket();
        valuesBucket1.putString("name","dongliu");
        valuesBucket1.putInteger("age",19);
        valuesBucket1.putInteger("sex",1);
        valuesBucket1.putString("class","二班");
        stu_list.add(valuesBucket1);

        ValuesBucket  valuesBucket2 = new ValuesBucket();
        valuesBucket2.putString("name","dongtang");
        valuesBucket2.putInteger("age",20);
        valuesBucket2.putInteger("sex",1);
        valuesBucket2.putString("class","三班");
        stu_list.add(valuesBucket2);

        List<Long> ids = store2.batchInsertOrThrowException("student",stu_list,
                RdbStore.ConflictResolution.ON_CONFLICT_ABORT);
        String output ="";//??????
        for(Long id:ids)
        {
            if(id == -1)
            {
                textField.append("批量插入失败\n");
            }
            else
            {
                textField.append("批量插入成功id"+ id+ "\n");
            }
        }
        store2.close();

    }

    //创建和加载数据库,获取rdbstore对象,等待调佣
    private RdbStore getTestRdbStore()
    {
        DatabaseHelper help2 = new DatabaseHelper(this);
        //创建test.sqlite数据库
        StoreConfig config2 = StoreConfig.newDefaultConfig("test.sqlite");
        //获取rdbstore对象
        RdbStore store2 = help2.getRdbStore(config2, 1, new RdbOpenCallback() {
            @Override
            public void onCreate(RdbStore rdbStore) {
                //创建数据表table
                rdbStore.executeSql("CREATE TABLE IF NOT EXISTS student (" +
                        "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                        "name TEXT NOT NULL," +
                        "age INTEGER, " +
                        "sex TINYINT, " +
                        "class TEXT)");
            }

            @Override
            public void onUpgrade(RdbStore rdbStore, int i, int i1) {

            }
        });
        return store2;
    }
    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }
}

 对象关系映射数据库

 

HarmonyOS对象关系映射(Object Relational Mapping,ORM)数据库是一款基于SQLite的数据库框架,屏蔽了底层SQLite数据库的SQL操作,针对实体和关系提供了增删改查等一系列的面向对象接口。应用开发者不必再去编写复杂的SQL语句, 以操作对象的形式来操作数据库,提升效率的同时也能聚焦于业务开发。

基本概念

  • 对象关系映射数据库的三个主要组件
    • 数据库:被开发者用@Database注解,且继承了OrmDatabase的类,对应关系型数据库
    • 实体对象:被开发者用@Entity注解,且继承了OrmObject的类,对应关系型数据库中的表。
    • 对象数据操作接口:包括数据库操作的入口OrmContext类和谓词接口(OrmPredicate)等。
  • 谓词

    数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数据库的操作条件。对象关系映射数据库将SQLite数据库中的谓词封装成了接口方法供开发者调用。开发者通过对象数据操作接口,可以访问到应用持久化的关系型数据。

  • 对象关系映射数据库

    通过将实例对象映射到关系上,实现操作实例对象的语法,来操作关系型数据库。它是在SQLite数据库的基础上提供的一个抽象层。

  • SQLite数据库

    一款轻型的数据库,是遵守ACID的关系型数据库管理系统。

运作机制

对象关系映射数据库操作是基于关系型数据库操作接口完成的,实际是在关系型数据库操作的基础上又实现了对象关系映射等特性。因此对象关系映射数据库跟关系型数据库一样,都使用SQLite作为持久化引擎,底层使用的是同一套数据库连接池和数据库连接机制。

使用对象关系映射数据库的开发者需要先配置实体模型与关系映射文件。应用数据管理框架提供的类生成工具会解析这些文件,生成数据库帮助类,这样应用数据管理框架就能在运行时,根据开发者的配置创建好数据库,并在存储过程中自动完成对象关系映射。开发者再通过对象数据操作接口,如OrmContext接口和谓词接口等操作持久化数据库

对象数据操作接口提供一组基于对象映射的数据操作接口,实现了基于SQL的关系模型数据到对象的映射,让用户不需要再和复杂的 SQL语句打交道,只需简单地操作实体对象的属性和方法。对象数据操作接口支持对象的增删改查操作,同时支持事务操作等。

图1 对象关系映射数据库运作机制

默认配置

  • 如果不指定数据库的日志模式,那么系统默认日志方式是WAL(Write Ahead Log)模式。
  • 如果不指定数据库的落盘模式,那么系统默认落盘方式是FULL模式。
  • HarmonyOS数据库使用的共享内存默认大小是2MB。

约束与限制

  • 当应用使用对象关系映射数据库接口时,应用包和类的命名需要遵循典型的Java风格(小写包名,大驼峰类名)。
  • HarmonyOS对象关系映射数据库是建立在HarmonyOS关系型数据库的基础之上的,所以关系型数据库的一些约束与限制请参考约束与限制。
  • 此外当开发者建立实体对象类时,对象属性的类型可以在下表的类型中选择。不支持使用自定义类型。
表1 实体对象属性支持的类型

类型名称

描述

初始值

Integer

封装整型

null

int

整型

0

Long

封装长整型

null

long

长整型

0L

Double

封装双精度浮点型

null

double

双精度浮点型

0

Float

封装单精度浮点型

null

float

单精度浮点型

0

Short

封装短整型

null

short

短整型

0

String

字符串型

null

Boolean

封装布尔型

null

boolean

布尔型

0

Byte

封装字节型

null

byte

字节型

0

Character

封装字符型

null

char

字符型

' '

Date

日期类

null

Time

时间类

null

Timestamp

时间戳类

null

Calendar

日历类

null

Blob

二进制大对象

null

Clob

字符大对象

null

开发能力介绍

对象关系映射数据库目前可以支持数据库和表的创建,对象数据的增删改查、对象数据变化回调、数据库升降级和备份等功能。

说明

对象关系映射数据库提供的接口在ohos.data.orm包中,使用该包中的接口时,要求配置文件config.json的“app > bundleName”字段的值,不能包含大写字母。

数据库和表的创建

  1. 创建数据库。开发者需要定义一个表示数据库的类,继承OrmDatabase,再通过@Database注解内的entities属性指定哪些数据模型类属于这个数据库

    属性:

  2. 创建数据表。开发者可通过创建一个继承了OrmObject并用@Entity注解的类,获取数据库实体对象,也就是表的对象。

    属性:

    • tableName:表名。
    • primaryKeys:主键名,一个表里只能有一个主键,一个主键可以由多个字段组成。
    • foreignKeys:外键列表。
    • indices:索引列表。
    表1 注解对照表

    接口名称

    描述

    @Database

    被@Database注解且继承了OrmDatabase的类对应数据库类。

    @Entity

    被@Entity注解且继承了OrmObject的类对应数据表类。

    @Column

    被@Column注解的变量对应数据表的字段。

    @PrimaryKey

    被@PrimaryKey注解的变量对应数据表的主键。

    @ForeignKey

    被@ForeignKey注解的变量对应数据表的外键。

    @Index

    被@Index注解的内容对应数据表索引的属性。

打开数据库数据库加密

  1. 打开数据库。开发者通过getOrmContext打开数据库
    表2 打开数据库API

    类名

    接口名

    描述

    DatabaseHelper

    DatabaseHelper(Context context)

    DatabaseHelper是数据库操作的辅助类,当数据库创建成功后,数据库文件将存储在由上下文指定的目录里。

    • 获取上下文参考方法:context入参类型为ohos.app.Context,注意不要使用slice.getContext()来获取context,请直接传入slice,否则会出现找不到类的报错。
    • 查看详细路径信息:ohos.app.Context#getDatabaseDir()。

    DatabaseHelper

    public <T extends OrmDatabase> OrmContext getOrmContext(String alias, String name, Class<T> ormDatabase, OrmMigration... migrations)

    打开数据库,alias数据库别名,name数据库名称,ormDatabase数据库对应类,migrations数据库升级类。

    DatabaseHelper

    public <T extends OrmDatabase> OrmContext getOrmContext(OrmConfig ormConfig, Class<T> ormDatabase, OrmMigration... migrations)

    打开数据库,ormConfig数据库配置,ormDatabase数据库对应类,migrations数据库升级类。

    DatabaseHelper

    public OrmContext getOrmContext(String alias)

    根据别名打开数据库

  2. 数据库加密。对象关系映射数据库提供数据库加密的能力,创建加密数据库时传入指定密钥,后续打开加密数据库时,需要传入正确密钥。
    表3 数据库传入密钥API

    类名

    接口名

    描述

    OrmConfig.Builder

    public OrmConfig.Builder setEncryptKey(byte[] encryptKey)

    数据库配置类设置数据库加密密钥,创建或打开数据库时传入包含数据库加密密钥的配置类,即可创建或打开加密数据库

对象数据的增删改查

通过对象数据操作接口,开发者可以对对象数据进行增删改查操作。

表4 对象数据操作API

类名

接口名

描述

OrmContext

<T extends OrmObject> boolean insert(T object)

添加方法。

OrmContext

<T extends OrmObject> boolean update(T object)

更新方法。

OrmContext

<T extends OrmObject> List<T> query(OrmPredicates predicates)

查询方法。

OrmContext

<T extends OrmObject> boolean delete(T object)

删除方法。

OrmContext

<T extends OrmObject> OrmPredicates where(Class<T> clz)

设置谓词方法。

事务提交和回滚

对象关系型数据库提供事务机制,来保证用户操作的原子性。对单条数据进行数据库操作时,无需开启事务;插入大量数据时,开启事务可以保证数据的准确性。如果中途操作出现失败,会自动执行回滚操作。

表5 事务处理API

类名

接口名

描述

OrmContext

void beginTransaction()

开启事务。

OrmContext

void commit()

事务提交。

OrmContext

void rollback()

事务回滚。

OrmContext

boolean isInTransaction()

是否正在执行事务操作。

数据变化观察者设置

通过使用对象数据操作接口,开发者可以在某些数据上设置观察者,接收数据变化的通知。

表6 数据变化观察者API

类名

接口名

描述

OrmContext

void registerStoreObserver(String alias, OrmObjectObserver observer)

注册数据库变化回调。

OrmContext

void registerContextObserver(OrmContext watchedContext, OrmObjectObserver observer)

注册上下文变化回调。

OrmContext

void registerEntityObserver(String entityName, OrmObjectObserver observer)

注册数据库实体变化回调。

OrmContext

void registerObjectObserver(OrmObject ormObject, OrmObjectObserver observer)

注册对象变化回调。

数据库的升降级

通过调用数据库升降级接口,开发者可以将数据库切换到不同的版本。

表7 数据库升降级API

类名

接口名

描述

OrmMigration

public OrmMigration(int beginVersion, int endVersion)

数据库升降级开始版本和结束版本。

OrmMigration

public abstract void onMigrate( RdbStore store)

数据库版本升降级方法。

OrmMigration

public int getBeginVersion()

获取开始版本。

OrmMigration

public int getEndVersion()

获取结束版本。

数据库的备份恢复

开发者可以将当前数据库的数据进行备份,在必要的时候进行数据恢复。

表8 数据库备份与恢复API

类名

接口名称

描述

OrmContext

boolean backup(String destPath)

数据库备份方法。

OrmContext

boolean restore(String srcPath)

数据库恢复备份方法。

开发步骤

  1. 配置“build.gradle”文件。
    • 如果使用注解处理器的模块为“com.huawei.ohos.hap”模块,则需要在模块的“build.gradle”文件的ohos节点中添加以下配置:
       
          
      1. compileOptions{
      2. annotationEnabled true
      3. }
    • 如果使用注解处理器的模块为“com.huawei.ohos.library”模块,则需要在模块的“build.gradle”文件的“dependencies”节点中配置注解处理器。查看“orm_annotations_java.jar”、“orm_annotations_processor_java.jar” 、“javapoet_java.jar”这3个jar包在HUAWEI SDK中的Sdk/java/x.x.x.xx/build-tools/lib/目录,并将目录的这三个jar包导进来。
       
          
      1. dependencies {
      2. compile files("orm_annotations_java.jar的路径", "orm_annotations_processor_java.jar的路径", "javapoet_java.jar的路径")
      3. annotationProcessor files("orm_annotations_java.jar的路径", "orm_annotations_processor_java.jar的路径", "javapoet_java.jar的路径")
      4. }
    • 如果使用注解处理器的模块为“java-library”模块,则需要在模块的“build.gradle”文件的dependencies节点中配置注解处理器,并导入“ohos.jar”。
       
          
      1. dependencies {
      2. compile files("ohos.jar的路径","orm_annotations_java.jar的路径","orm_annotations_processor_java.jar的路径","javapoet_java.jar的路径")
      3. annotationProcessor files("orm_annotations_java.jar的路径","orm_annotations_processor_java.jar的路径","javapoet_java.jar的路径")
      4. }
  2. 构造数据库,即创建数据库类并配置对应的属性。例如,定义了一个数据库类BookStore.java数据库包含了“User”,"Book","AllDataType"三个表,版本号为“1”。数据库类的getVersion方法和getHelper方法不需要实现,直接将数据库类设为虚类即可。
     
      
    1. @Database(entities = {User.class, Book.class, AllDataType.class}, version = 1)
    2. public abstract class BookStore extends OrmDatabase {
    3. }
  3. 构造数据表,即创建数据库实体类并配置对应的属性(如对应表的主键,外键等)。数据表必须与其所在的数据库在同一个模块中。

    例如,定义了一个实体类User.java,对应数据库内的表名为“user”;indices 为“firstName”和“lastName”两个字段建立了复合索引“name_index”,并且索引值是唯一的;“ignoredColumns”表示该字段不需要添加到“user”表的属性中。

     
      
    1. @Entity(tableName = "user", ignoredColumns = {"ignoredColumn1", "ignoredColumn2"},
    2. indices = {@Index(value = {"firstName", "lastName"}, name = "name_index", unique = true)})
    3. public class User extends OrmObject {
    4. // 此处将userId设为了自增的主键。注意只有在数据类型为包装类型时,自增主键才能生效。
    5. @PrimaryKey(autoGenerate = true)
    6. private Integer userId;
    7. private String firstName;
    8. private String lastName;
    9. private int age;
    10. private double balance;
    11. private int ignoredColumn1;
    12. private int ignoredColumn2;
    13. // 需添加各字段的getter和setter方法。
    14. }

    说明

    示例中的getter & setter 的方法名为小驼峰格式,除了手写方法,IDE中包含自动生成getter和setter方法的Generate插件。

    • 当变量名的格式类似“firstName”时,getter和setter方法名应为“getFirstName”和“setFirstName”。
    • 当变量名的格式类似“mAge”,即第一个字母小写,第二个字母大写的格式时,getter和setter方法名应为“getmAge”和“setmAge”。
    • 当变量名格式类似“x”,即只有一个字母时,getter和setter方法名应为“getX”和“setX”。

    变量为boolean类型时,上述规则仍然成立,即“isFirstName”,“ismAge”,“isX”。

  4. 使用对象数据操作接口OrmContext创建数据库

    例如,通过对象数据操作接口OrmContext,创建一个别名为“BookStore”,数据库文件名为“BookStore.db”的数据库。如果数据库已经存在,执行以下代码不会重复创建。通过context.getDatabaseDir()可以获取创建的数据库文件所在的目录。

     
      
    1. // context入参类型为ohos.app.Context,注意不要使用slice.getContext()来获取context,请直接传入slice,否则会出现找不到类的报错。
    2. DatabaseHelper helper = new DatabaseHelper(this);
    3. OrmContext context = helper.getOrmContext("BookStore", "BookStore.db", BookStore.class);
  5. (可选)数据库升降级。如果开发者有多个版本的数据库,通过设置数据库版本迁移类可以实现数据库版本升降级。数据库版本升降级的调用示例如下。其中BookStoreUpgrade类也是一个继承了OrmDatabase的数据库类,与BookStore类的区别在于配置的版本号不同。
     
      
    1. OrmContext context = helper.getOrmContext("BookStore",
    2. "BookStore.db",
    3. BookStoreUpgrade.class,
    4. new TestOrmMigration32(),
    5. new TestOrmMigration23(),
    6. new TestOrmMigration12(),
    7. new TestOrmMigration21());

    TestOrmMigration12的实现示例如下:

     
      
    1. private static class TestOrmMigration12 extends OrmMigration {
    2. // 此处用于配置数据库版本迁移的开始版本和结束版本,super(startVersion, endVersion)即数据库版本号从1升到2。
    3. public TestOrmMigration12() {super(1, 2); }
    4. @Override
    5. public void onMigrate(RdbStore store) {
    6. store.executeSql("ALTER TABLE `Book` ADD COLUMN `addColumn12` INTEGER");
    7. }
    8. }

    说明

    数据库版本迁移类的开始版本和结束版本必须是连续的。

    • 如果BookStore.db的版本号为“1”,BookStoreUpgrade.class的版本号为“2”时,TestOrmMigration12类的onMigrate方法会被自动回调。
    • 如果BookStore.db的版本号为“1”,BookStoreUpgrade.class版本号为“3”时,TestOrmMigration12类和TestOrmMigration23类的onMigrate方法会依次被回调。
  6. 使用对象数据操作接口OrmContext对数据库进行增删改查、注册观察者、备份数据库等。
    • 更新或删除数据,分为两种情况:
      • 通过直接传入OrmObject对象的接口来更新数据,需要先从表中查到需要更新的User对象列表,然后修改对象的值,再调用更新接口持久化到数据库中。删除数据与更新数据的方法类似,只是不需要更新对象的值。

        例如,更新“user”表中age为“29”的行,需要先查找“user”表中对应数据,得到一个User的列表。然后选择列表中需要更新的User对象(如第0个对象),设置需要更新的值,并调用update接口传入被更新的User对象。最后调用flush接口持久化到数据库中。

         
              
        1. // 更新数据
        2. OrmPredicates predicates = context.where(User.class);
        3. predicates.equalTo("age", 29);
        4. List<User> users = context.query(predicates);
        5. User user = users.get(0);
        6. user.setFirstName("Li");
        7. context.update(user);
        8. context.flush();
        9. // 删除数据
        10. OrmPredicates predicates = context.where(User.class);
        11. predicates.equalTo("age", 29);
        12. List<User> users = context.query(predicates);
        13. User user = users.get(0);
        14. context.delete(user);
        15. context.flush();
      • 通过传入谓词的接口来更新和删除数据,方法与OrmObject对象的接口类似,只是无需flush就可以持久化到数据库中。
         
              
        1. ValuesBucket valuesBucket = new ValuesBucket();
        2. valuesBucket.putInteger("age", 31);
        3. valuesBucket.putString("firstName", "ZhangU");
        4. valuesBucket.putString("lastName", "SanU");
        5. valuesBucket.putDouble("balance", 300.51);
        6. OrmPredicates update = context.where(User.class).equalTo("userId", 1);
        7. context.update(update, valuesBucket);
    • 查询数据。在数据库的“user”表中查询lastName为“San”的User对象列表,示例如下:
       
          
      1. OrmPredicates query = context.where(User.class).equalTo("lastName", "San");
      2. List<User> users = context.query(query);
    • 注册观察者。
       
          
      1. // 定义一个观察者类。
      2. private class CustomedOrmObjectObserver implements OrmObjectObserver {
      3. @Override
      4. public void onChange(OrmContext changeContext, AllChangeToTarget subAllChange) {
      5. // 用户可以在此处定义观察者行为
      6. }
      7. }
      8. // 调用registerEntityObserver方法注册一个观察者observer。
      9. CustomedOrmObjectObserver observer = new CustomedOrmObjectObserver();
      10. context.registerEntityObserver("user", observer);
      11. // 当以下方法被调用,并flush成功时,观察者observer的onChange方法会被触发。其中,方法的入参必须为User类的对象。
      12. public <T extends OrmObject> boolean insert(T object)
      13. public <T extends OrmObject> boolean update(T object)
      14. public <T extends OrmObject> boolean delete(T object)
    • 备份数据库。其中原数据库名为“OrmTest.db”,备份数据库名为“OrmBackup.db”。
       
          
      1. OrmContext context = helper.getObjectContext("OrmTest", "OrmTest.db", BookStore.class);
      2. context.backup("OrmBackup.db");
      3. context.close();
  7. 删除数据库,例如删除OrmTest.db。
     
      
    1. helper.deleteRdbStore("OrmTest.db");

package com.example.myapplication.slice;

import com.example.myapplication.Picture;
import com.example.myapplication.ResourceTable;
import com.example.myapplication.TestDatabase;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Picker;
import ohos.agp.components.TextField;
import ohos.data.DatabaseHelper;
import ohos.data.orm.OrmContext;
import ohos.data.orm.OrmPredicates;
import ohos.data.rdb.ValuesBucket;

import java.util.Date;
import java.util.List;

public class OrmPageAbilitySlice extends AbilitySlice {
    TextField textField;
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_orm);
        textField = (TextField) findComponentById(ResourceTable.Id_text_field_info);
        Button btn_add = (Button) findComponentById(ResourceTable.Id_btn_insert);
        btn_add.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                AddData();
            }
        });
        Button btn_query = (Button) findComponentById(ResourceTable.Id_btn_search);
        btn_query.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                SearchData();
            }
        });
        Button btn_update = (Button) findComponentById(ResourceTable.Id_btn_update);
        btn_update.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                UpdateData();
            }
        });
        Button btn_delete = (Button) findComponentById(ResourceTable.Id_btn_delete);
        btn_delete.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                DeleteData();
            }
        });
    }
    private OrmContext GetTestOrmContext(){
        DatabaseHelper helper2 = new DatabaseHelper(this);
        OrmContext context2 = helper2.getOrmContext("Test","test.db", TestDatabase.class);
        return  context2;
    }
    @Override
    public void onActive() {
        super.onActive();
    }
    private void SearchData()
    {
        OrmContext context2 = GetTestOrmContext();
        OrmPredicates predicates2 = new OrmPredicates(Picture.class).orderByAsc("pictureid");
        List<Picture> pictureList = context2.query(predicates2);
        textField.append("查询结果"+pictureList.size());
        for(Picture pic : pictureList)
        {
             textField.append(pic.getName()+",\n");
            textField.append(pic.getCaptureTime()+",\n");
            textField.append(pic.getDiscription()+"\n");
        }
        context2.close();

    }
    private void AddData()
    {
        OrmContext context = GetTestOrmContext();
        Picture pic2 = new Picture();

        pic2.setName("test.png");
        pic2.setCaptureTime(new Date());
        pic2.setDiscription("test pic");
        pic2.setLongitude(112.223455);
        pic2.setLatitude(112.223455);

        boolean isOk= context.insert(pic2);
        if(!isOk)
        {
           textField.append("add orm data error\n");
           return;
        }
        isOk = context.flush();//提交操作
        if(!isOk)
        {
            textField.append("add orm data error\n");
            return;
        }
        textField.append("add orm data ok\n");
        context.close();

    }
    private void UpdateData()
    {
        OrmContext context2 = GetTestOrmContext();
        OrmPredicates ormPredicates2= new OrmPredicates(Picture.class).equalTo("name","test.png");
        ValuesBucket valuesBucket2= new ValuesBucket();
        valuesBucket2.putString("name","tt1.png");
        int count = context2.update(ormPredicates2,valuesBucket2);
        boolean isOk= context2.flush();
        textField.append("isOk:"+isOk+",更新记录:"+count);
        context2.close();
    }
    private void DeleteData()
    {
        OrmContext context2 = GetTestOrmContext();
        Picture pic2 = new Picture();
        pic2.setRowId(1);
        boolean isOk= context2.delete(pic2);
        if(!isOk)
        {
            textField.append("delete error");
        }
        isOk = context2.flush();//提交数据
        if(!isOk)
        {
            textField.append("delete error");
        }
        else
        {
            textField.append("delete operation ok\n");
        }
        context2.close();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }
}

 应用偏好数据库

package com.example.myapplication.slice;

import com.example.myapplication.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.TextField;
import ohos.data.DatabaseHelper;
import ohos.data.preferences.Preferences;
import ohos.data.rdb.RdbOpenCallback;
import ohos.data.rdb.RdbStore;
import ohos.data.rdb.StoreConfig;

public class PreferencesPageAbilitySlice extends AbilitySlice {
    TextField textField;
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_preferences);
        textField = (TextField) findComponentById(ResourceTable.Id_text_field_info);
        //注册观察者对象
        getTestPreference().registerObserver(observer);
        Button btn_save = (Button) findComponentById(ResourceTable.Id_btn_save);
        Button btn_get = (Button) findComponentById(ResourceTable.Id_btn_get_jianzhi);
        Button btn_del = (Button) findComponentById(ResourceTable.Id_btn_delete_jianzhi);
        Button btn_monitor = (Button) findComponentById(ResourceTable.Id_btn_jianzhi_count);
        btn_save.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                SaveKeyValue();
            }
        });
        btn_get.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
               GetKeyValue();
            }
        });
        btn_del.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
              DeleteValue();
            }
        });
        btn_monitor.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
             MonitorKeyValue();
            }
        });
    }
    @Override
    public void onStop()
    {
        super.onStop();
        //取消注册观察者对象
        getTestPreference().unregisterObserver(observer);
    }
    private Preferences getTestPreference()
    {
        DatabaseHelper help2 = new DatabaseHelper(this);
         Preferences preferences = help2.getPreferences("test");//test数据库,没有则自动创建



        return preferences;
    }
    private void SaveKeyValue()  //应用偏好数据库是xml文件,存储位置:/data/data/<包名>/<Ablility名字>/preferences/test.xml
    {
        Preferences preferences = getTestPreference();
        preferences.putBoolean("isAutoUpdate",true);
        preferences.putInt("version",1001);
        preferences.putString("currentUser","libai");
        preferences.flush();
        textField.append("已保存键值\n");
    }
    private void GetKeyValue()
    {
        Preferences preferences = getTestPreference();
        boolean isAutoUpdate = preferences.getBoolean("isAutoUpdate",false);
        int version = preferences.getInt("version", -1);
        String currentUser = preferences.getString("currentUser","no USER");
        textField.append("isAutoUpdate:"+isAutoUpdate+"\n");
        textField.append("version:"+version+"\n");
        textField.append("currentUser:"+currentUser+"\n");

    }
    private void DeleteValue()
    {
        Preferences preferences = getTestPreference();
        preferences.delete("version");
        textField.append("已删除version\n");
    }
    //观察者接口
    private Preferences.PreferencesObserver observer = new Preferences.PreferencesObserver() {
            @Override
            public void onChange(Preferences preferences, String s) {
                if(s =="count")
                {
                    final  int count = preferences.getInt(s,0);
                    getUITaskDispatcher().asyncDispatch(new Runnable() {
                        @Override
                        public void run() {
                            textField.append("count:"+count);
                        }
                    });
                }
            }
        };
    private void MonitorKeyValue()
    {
        Preferences preferences = getTestPreference();
        int count = preferences.getInt("count",0);
        preferences.putInt("count",count+1);
        preferences.flush();
    }



    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }
}

 分布式数据库

分布式数据服务(Distributed Data Service,DDS) 为应用程序提供不同设备间数据库数据分布式的能力。通过调用分布式数据接口,应用程序将数据保存到分布式数据库中。通过结合帐号、应用和数据库三元组,分布式数据服务对属于不同应用的数据进行隔离,保证不同应用之间的数据不能通过分布式数据服务互相访问。在通过可信认证的设备间,分布式数据服务支持应用数据相互同步,为用户提供在多种终端设备上最终一致的数据访问体验。

基本概念

  • KV数据模型

    “KV数据模型”是“Key-Value数据模型”的简称,“Key-Value”即“键-值”。它是一种NoSQL类型数据库,其数据以键值对的形式进行组织、索引和存储。

    KV数据模型适合不涉及过多数据关系和业务关系的业务数据存储,比SQL数据库存储拥有更好的读写性能,同时因其在分布式场景中降低了解决数据库版本兼容问题的复杂度,和数据同步过程中冲突解决的复杂度而被广泛使用。分布式数据库也是基于KV数据模型,对外提供KV类型的访问接口。

  • 分布式数据库事务

    分布式数据库事务支持本地事务(和传统的数据库事务概念一致)和同步事务。同步事务是指在设备之间同步数据时,以本地事务为单位进行同步,一次本地事务的修改要么都同步成功,要么都同步失败。

  • 分布式数据库一致性

    在分布式场景中一般会涉及多个设备,组网内设备之间看到的数据是否一致称为分布式数据库的一致性。分布式数据库一致性可以分为强一致性弱一致性最终一致性

    • 强一致性:是指某一设备成功增、删、改数据后,组网内设备对该数据的读取操作都将得到更新后的值。
    • 弱一致性:是指某一设备成功增、删、改数据后,组网内设备可能能读取到本次更新数据,也可能读取不到,不能保证在多长时间后每个设备的数据一定是一致的。
    • 最终一致性:是指某一设备成功增、删、改数据后,组网内设备可能读取不到本次更新数据,但在某个时间窗口之后组网内设备的数据能够达到一致状态。

    强一致性对分布式数据的管理要求非常高,在服务器的分布式场景可能会遇到。因为移动终端设备的不常在线、以及无中心的特性,分布式数据服务不支持强一致性,只支持最终一致性。

  • 分布式数据库同步

    底层通信组件完成设备发现和认证,会通知上层应用程序(包括分布式数据服务)设备上线。收到设备上线的消息后分布式数据服务可以在两个设备之间建立加密的数据传输通道,利用该通道在两个设备之间进行数据同步。

    分布式数据服务提供了两种同步方式:手动同步自动同步

    • 手动同步:由应用程序调用sync接口来触发,需要指定同步的设备列表和同步模式。同步模式分为PULL_ONLY(将远端数据拉到本端)、PUSH_ONLY(将本端数据推送到远端)和PUSH_PULL(将本端数据推送到远端同时也将远端数据拉取到本端
    • 自动同步:由分布式数据库自动将本端数据推送到远端,同时也将远端数据拉取到本端来完成数据同步,同步时机包括设备上线、应用程序更新数据等,应用不需要主动调用sync接口。
  • 单版本分布式数据库

    单版本是指数据在本地保存是以单个KV条目为单位的方式保存,对每个Key最多只保存一个条目项,当数据在本地被用户修改时,不管它是否已经被同步出去,均直接在这个条目上进行修改。同步也以此为基础,按照它在本地被写入或更改的顺序将当前最新一次修改逐条同步至远端设备。

  • 设备协同分布式数据库

    设备协同分布式数据库建立在单版本分布式数据库之上,对应用程序存入的KV数据中的Key前面拼接了本设备的DeviceID标识符,这样能保证每个设备产生的数据严格隔离,底层按照设备的维度管理这些数据,设备协同分布式数据库支持以设备的维度查询分布式数据,但是不支持修改远端设备同步过来的数据。

  • 分布式数据库冲突解决策略

    分布式数据库多设备提交冲突场景,在给提交冲突做合并的过程中,如果多个设备同时修改了同一数据,则称这种场景为数据冲突。数据冲突采用默认冲突解决策略,基于提交时间戳,取时间戳较大的提交数据,当前不支持定制冲突解决策略。

  • 数据库Schema化管理与谓词查询

    单版本数据库支持在创建和打开数据库时指定Schema,数据库根据Schema定义感知KV记录的Value格式,以实现对Value值结构的检查,并基于Value中的字段实现索引建立和谓词查询。

  • 分布式数据库备份能力

    提供分布式数据库备份能力,业务通过设置backup属性为true,可以触发分布式数据服务每日备份。当分布式数据库发生损坏,分布式数据服务会删除损坏数据库,并且从备份数据库中恢复上次备份的数据。如果不存在备份数据库,则创建一个新的数据库。同时支持加密数据库的备份能力。

运作机制

分布式数据服务支撑HarmonyOS系统上应用程序数据库数据分布式管理,支持数据在相同帐号的多端设备之间相互同步,为用户在多端设备上提供一致的用户体验,分布式数据服务包含五部分:

  • 服务接口

    分布式数据服务提供专门的数据库创建、数据访问、数据订阅等接口给应用程序调用,接口支持KV数据模型,支持常用的数据类型,同时确保接口的兼容性、易用性和可发布性。

  • 服务组件

    服务组件负责服务内元数据管理、权限管理、加密管理、备份和恢复管理以及多用户管理等、同时负责初始化底层分布式DB的存储组件、同步组件和通信适配层。

  • 存储组件

    存储组件负责数据的访问、数据的缩减、事务、快照、数据库加密,以及数据合并和冲突解决等特性。

  • 同步组件

    同步组件连结了存储组件与通信组件,其目标是保持在线设备间的数据库数据一致性,包括将本地产生的未同步数据同步给其他设备,接收来自其他设备发送过来的数据,并合并到本地设备中。

  • 通信适配层

    通信适配层负责调用底层公共通信层的接口完成通信管道的创建、连接,接收设备上下线消息,维护已连接和断开设备列表的元数据,同时将设备上下线信息发送给上层同步组件,同步组件维护连接的设备列表,同步数据时根据该列表,调用通信适配层的接口将数据封装并发送给连接的设备。

应用程序通过调用分布式数据服务接口实现分布式数据库创建、访问、订阅功能,服务接口通过操作服务组件提供的能力,将数据存储至存储组件,存储组件调用同步组件实现将数据同步,同步组件使用通信适配层将数据同步至远端设备,远端设备通过同步组件接收数据,并更新至本端存储组件,通过服务接口提供给应用程序使用。

图1 数据分布式运作示意图

约束与限制

  • 应用程序如需使用分布式数据服务完整功能,需要申请ohos.permission.DISTRIBUTED_DATASYNC权限。
  • 分布式数据服务的数据模型仅支持KV数据模型,不支持外键、触发器等关系型数据库中的功能。
  • 分布式数据服务支持的KV数据模型规格:
    • 设备协同数据库,针对每条记录,Key的长度≤896 Byte,Value的长度<4 MB。
    • 单版本数据库,针对每条记录,Key的长度≤1 KB,Value的长度<4 MB。
    • 每个应用程序最多支持同时打开16个分布式数据库
  • 分布式数据库与本地数据库的使用场景不同,因此开发者应识别需要在设备间进行同步的数据,并将这些数据保存到分布式数据库中。
  • 分布式数据服务当前不支持应用程序自定义冲突解决策略。
  • 分布式数据服务针对每个应用程序当前的流控机制:KvStore的接口1秒最大访问1000次,1分钟最大访问10000次;KvManager的接口1秒最大访问50次,1分钟最大访问500次。

接口说明

HarmonyOS系统中的分布式数据服务模块为开发者提供下面几种功能:

表1 分布式数据服务关键API功能介绍

功能分类

接口名称

描述

分布式数据库创建、打开、关闭和删除。

isCreateIfMissing()

检查数据库不存在时是否创建。

setCreateIfMissing(boolean isCreateIfMissing)

设置数据库不存在时是否创建。

isEncrypt()

获取数据库是否加密。

setEncrypt(boolean isEncrypt)

设置数据库是否加密。

getStoreType()

获取分布式数据库的类型。

setStoreType(KvStoreType storeType)

设置分布式数据库的类型。

KvStoreType.DEVICE_COLLABORATION

设备协同分布式数据库类型。

KvStoreType.SINGLE_VERSION

单版本分布式数据库类型。

getKvStore(Options options, String storeId)

根据Options配置创建和打开标识符为storeId的分布式数据库

closeKvStore(KvStore kvStore)

关闭分布式数据库

deleteKvStore(String storeId)

删除分布式数据库

分布式数据增、删、改、查。

getStoreId()

获取分布式数据库实例的标识符。

putBoolean(String key, boolean value)

putInt(String key, int value)

putFloat(String key, float value)

putDouble(String key, double value)

putString(String key, String value)

putByteArray(String key, byte[] value)

putBatch(List<Entry> entries)

插入和更新数据。

delete(String key)

deleteBatch(List<String> keys)

删除数据。

getInt(String key)

getFloat(String key)

getDouble(String key)

getString(String key)

getByteArray(String key)

getEntries(String keyPrefix)

查询数据。

分布式数据谓词查询。

select()

reset()

equalTo(String field, int value)

equalTo(String field, long value)

equalTo(String field, double value)

equalTo(String field, String value)

equalTo(String field, boolean value)

notEqualTo(String field, int value)

notEqualTo(String field, long value)

notEqualTo(String field, boolean value)

notEqualTo(String field, String value)

notEqualTo(String field, double value)

greaterThan(String field, int value)

greaterThan(String field, long value)

greaterThan(String field, double value)

greaterThan(String field, String value)

lessThan(String field, int value)

lessThan(String field, long value)

lessThan(String field, double value)

lessThan(String field, String value)

greaterThanOrEqualTo(String field, int value)

greaterThanOrEqualTo(String field, long value)

greaterThanOrEqualTo(String field, double value)

greaterThanOrEqualTo(String field, String value)

lessThanOrEqualTo(String field, int value)

lessThanOrEqualTo(String field, long value)

lessThanOrEqualTo(String field, double value)

lessThanOrEqualTo(String field, String value)

isNull(String field)

orderByDesc(String field)

orderByAsc(String field)

limit(int number, int offset)

like(String field, String value)

unlike(String field, String value)

inInt(String field, List<Integer> valueList)

inLong(String field, List<Long> valueList)

inDouble(String field, List<Double> valueList)

inString(String field, List<String> valueList)

notInInt(String field, List<Integer> valueList)

notInLong(String field, List<Long> valueList)

notInDouble(String field, List<Double> valueList)

notInString(String field, List<String> valueList)

and()

or()

对于Schema数据库谓词查询数据。

订阅分布式数据变化。

subscribe(SubscribeType subscribeType, KvStoreObserver observer)

订阅数据库中数据的变化。

分布式数据同步。

sync(List<String> deviceIdList, SyncMode mode)

在手动模式下,触发数据库同步。

开发步骤

以单版本分布式数据库为例,说明开发步骤。

  1. 在config.json中添加permisssion权限。
     
      
    1. // 添加在abilities同一目录层级
    2. "reqPermissions": [
    3. {
    4. "name": "ohos.permission.DISTRIBUTED_DATASYNC"
    5. }
    6. ]

    应用启动时,需要弹出授权提示框,请求用户进行授权。

  2. 根据配置构造分布式数据库管理类实例。
    1. 根据应用上下文创建KvManagerConfig对象。
    2. 创建分布式数据库管理器实例。
    以下为创建分布式数据库管理器的代码示例:
     
      
    1. Context context = getApplicationContext();
    2. ...
    3. KvManagerConfig config = new KvManagerConfig(context);
    4. KvManager kvManager = KvManagerFactory.getInstance().createKvManager(config);
  3. 获取/创建单版本分布式数据库
    1. 声明需要创建的单版本分布式数据库ID描述。
    2. 创建单版本分布式数据库,默认开启组网设备间自动同步功能,如果应用对性能比较敏感建议设置关闭自动同步功能setAutoSync(false),主动调用sync接口同步。
    以下为创建单版本分布式数据库的代码示例:
     
      
    1. try {
    2. Options options = new Options();
    3. options.setCreateIfMissing(true).setEncrypt(false).setKvStoreType(KvStoreType.SINGLE_VERSION);
    4. String storeId = "testApp";
    5. SingleKvStore singleKvStore = kvManager.getKvStore(options, storeId);
    6. } catch (KvStoreException e) {
    7. HiLog.warn(LABEL_LOG, "getKvStore:" + e.getKvStoreErrorCode());
    8. }

    说明

    组网设备间同步数据的场景,建议在应用启动时打开分布式数据库,获取数据库的句柄。在该句柄(如上例中的singleKvStore)的生命周期内无需重复创建数据库,可直接使用句柄对数据库进行数据的插入等操作。

    如果没有调用kvManager.closeKvStore()方法关闭句柄,则该句柄会一直存在直到程序结束。

  4. 订阅分布式数据变化。

    1. 客户端需要实现KvStoreObserver接口。

    2. 构造并注册KvStoreObserver实例。

    以下为订阅单版本分布式数据库所有(本地及远端)数据变化通知的代码示例:
     
      
    1. class KvStoreObserverClient implements KvStoreObserver {
    2. @Override
    3. public void onChange(ChangeNotification notification) {
    4. List<Entry> insertEntries = notification.getInsertEntries();
    5. List<Entry> updateEntries = notification.getUpdateEntries();
    6. List<Entry> deleteEntries = notification.getDeleteEntries();
    7. }
    8. }
    9. KvStoreObserver kvStoreObserverClient = new KvStoreObserverClient();
    10. singleKvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_ALL, kvStoreObserverClient);
  5. 将数据写入单版本分布式数据库
    1. 构造需要写入单版本分布式数据库的Key(键)和Value(值)。
    2. 将键值数据写入单版本分布式数据库

    以下为将字符串类型键值数据写入单版本分布式数据库的代码示例:

     
      
    1. try {
    2. String key = "todayWeather";
    3. String value = "Sunny";
    4. singleKvStore.putString(key, value);
    5. } catch (KvStoreException e) {
    6. HiLog.warn(LABEL_LOG, "putString:" + e.getKvStoreErrorCode());
    7. }

    如果应用对性能比较敏感,建议使用批量写入接口putBatch。

  6. 查询单版本分布式数据库数据。
    1. 构造需要从单版本分布式数据库快照中查询的Key(键)。
    2. 从单版本分布式数据库快照中获取数据,需要捕获KvStoreException。
    以下为从单版本分布式数据库中查询字符串类型数据的代码示例:
     
      
    1. try {
    2. String key = "todayWeather";
    3. String value = singleKvStore.getString(key);
    4. } catch (KvStoreException e) {
    5. HiLog.warn(LABEL_LOG, "getString:" + e.getKvStoreErrorCode());
    6. }
  7. 同步数据到其他设备。

    1. 获取已连接的设备列表。

    2. 选择同步方式进行数据同步。

    以下为单版本分布式数据库进行数据同步的代码示例,其中同步方式为PUSH_ONLY:
     
      
    1. List<DeviceInfo> deviceInfoList = kvManager.getConnectedDevicesInfo(DeviceFilterStrategy.NO_FILTER);
    2. List<String> deviceIdList = new ArrayList<>();
    3. for (DeviceInfo deviceInfo : deviceInfoList) {
    4. deviceIdList.add(deviceInfo.getId());
    5. }
    6. singleKvStore.sync(deviceIdList, SyncMode.PUSH_ONLY);
  8. 关闭单版本分布式数据库。如果组网设备间不再需要同步数据并且本地也不再访问,就可以执行关闭数据库的操作。以下为关闭单版本分布式数据库的代码示例:
     
      
    1. try {
    2. kvManager.closeKvStore(singleKvStore);
    3. } catch (KvStoreException e) {
    4. HiLog.warn(LABEL_LOG, "closeKvStore:" + e.getKvStoreErrorCode());
    5. }
  9. 删除单版本分布式数据库。以下为删除单版本分布式数据库的代码示例:
     
      
    1. try {
    2. kvManager.deleteKvStore(storeId);
    3. } catch (KvStoreException e) {
    4. HiLog.warn(LABEL_LOG, "deleteKvStore:" + e.getKvStoreErrorCode());
    5. }

package com.example.myapplication.slice;

import com.example.myapplication.ResourceTable;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.TextField;
import ohos.agp.render.render3d.Entity;
import ohos.data.distributed.common.*;
import ohos.data.distributed.user.SingleKvStore;
import ohos.data.rdb.ValuesBucket;
import ohos.utils.zson.ZSONObject;

import java.util.ArrayList;
import java.util.List;

public class DistributedPageAbilitySlice extends AbilitySlice {
    TextField textField;
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_Distributed);
        textField = (TextField) findComponentById(ResourceTable.Id_text_field_info);
        Button btn_write = (Button) findComponentById(ResourceTable.Id_btn_write);
        btn_write.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                WriteKeyValue();
            }
        });
        Button btn_get_key = (Button) findComponentById(ResourceTable.Id_btn_get_jianzhi);
        Button btn_del_key = (Button) findComponentById(ResourceTable.Id_btn_delete_jianzhi);
        Button btn_insert = (Button) findComponentById(ResourceTable.Id_btn_insert_data);
        Button btn_query = (Button) findComponentById(ResourceTable.Id_btn_search_data);
        Button btn_update = (Button) findComponentById(ResourceTable.Id_btn_update_data);
        Button btn_delete_schema = (Button) findComponentById(ResourceTable.Id_btn_del_data);
        btn_get_key.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                GetKeyValue();
            }
        });
        btn_del_key.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                DeleteKeyValue();
            }
        });
        btn_insert.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                AddData();
            }
        });
        btn_query.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                SearchDataByAll();
            }
        });
        btn_update.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                UpdateDataBySchema();
            }
        });
        btn_delete_schema.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                DeleteDataBySchema();
            }
        });
    }
    //通过Schema定义数据的结果,json格式
    private Schema generateStudentSchema()
    {
       Schema schema2 = new Schema();
       schema2.setSchemaMode(SchemaMode.COMPATIBLE);//兼容模式
        FieldNode nameNode2 = new FieldNode("name");
        nameNode2.setType(FieldValueType.STRING);
        schema2.getRootFieldNode().appendChild(nameNode2);

        FieldNode ageNode2 = new FieldNode("age");
        ageNode2.setType(FieldValueType.INTEGER);
        schema2.getRootFieldNode().appendChild(ageNode2);

        FieldNode sexNode2 = new FieldNode("sex");
        sexNode2.setType(FieldValueType.INTEGER);
        schema2.getRootFieldNode().appendChild(sexNode2);
        FieldNode classNode2 = new FieldNode("class");
        classNode2.setType(FieldValueType.STRING);
        schema2.getRootFieldNode().appendChild(classNode2);
        return schema2;
    }
    private SingleKvStore getMySingleKvStore()
    {
        KvManagerConfig config2 = new KvManagerConfig(this);
        KvManager kvManager2 = KvManagerFactory.getInstance().createKvManager(config2);
        Options options2 = new Options().setCreateIfMissing(true).setEncrypt(false)
                .setAutoSync(true)
                .setBackup(true)
                .setKvStoreType(KvStoreType.SINGLE_VERSION);//不加密,自动同步,自动备份,单版本
        SingleKvStore singleKvStore2 = kvManager2.getKvStore(options2,"myStore");
        return singleKvStore2;
    }
    //创建用户存储学生信息的单版本分布式数据库
    private SingleKvStore getMyStudentKvStore()
    {
        KvManagerConfig config2 = new KvManagerConfig(this);
        KvManager kvManager2 = KvManagerFactory.getInstance().createKvManager(config2);
        Options options2 = new Options().setCreateIfMissing(true).setEncrypt(false)
                .setAutoSync(true)
                .setBackup(true)
                .setKvStoreType(KvStoreType.SINGLE_VERSION)//不加密,自动同步,自动备份,单版本
                .setSchema(generateStudentSchema());
        SingleKvStore singleKvStore2 = kvManager2.getKvStore(options2,"students");
        return singleKvStore2;
    }
    private void AddData()
    {
        SingleKvStore singleKvStore2 = getMyStudentKvStore();
        //构建ZSON对象
        ZSONObject obj = new ZSONObject();
        obj.put("name","tscott");
        obj.put("age",18);
        obj.put("sex",1);
        obj.put("class","中班");
        String key = "student_20230001";
        singleKvStore2.putString(key,obj.toString());
        textField.append("以增加数据\n");
    }
    //根据键值对查询数据
    private void SearchData()
    {
        SingleKvStore singleKvStore2 = getMyStudentKvStore();
        String jsonStr = singleKvStore2.getString("student_20230001");
        ZSONObject obj = ZSONObject.stringToZSON(jsonStr);
        textField.append("name:"+obj.getString("name")+"\n");
    }
    //通过前缀查询数据
    private void SearchDataByQianzhui()
    {
        SingleKvStore kvStore2 = getMyStudentKvStore();
        List<Entry> entries2 = kvStore2.getEntries("student_");
        for(Entry entry:entries2)
        {
            String key2= entry.getKey();
            String jsonStr = entry.getValue().getString();
            ZSONObject obj2 = ZSONObject.stringToZSON(jsonStr);
            textField.append("name:"+obj2.getString("name")+"\n");
            textField.append("age:"+obj2.getString("age")+"\n");
        }
    }
    //获取结果集
    private void SearchDataByAll()
    {
        SingleKvStore kvStore2 = getMyStudentKvStore();
        Query query2 = Query.select().equalTo("$.class","中班");
        List<Entry> entries2 = kvStore2.getEntries(query2);
        for(Entry entry:entries2)
        {
            String key = entry.getKey();
            ZSONObject obj2= ZSONObject.stringToZSON(entry.getValue().getString());
            String name = obj2.getString("name");
            int age = obj2.getInteger("age");
            int sex = obj2.getInteger("sex");
            String _class= obj2.getString("class");
            textField.append("name:"+name+",age"+age+",sex"+sex+",_class"+_class+"\n");
        }
    }
    //更新数据
    private void UpdateDataBySchema()
    {
        SingleKvStore kvStore2 = getMyStudentKvStore();
        String jsonStr = kvStore2.getString("student_20230001");
        ZSONObject obj2 = ZSONObject.stringToZSON(jsonStr);
        obj2.put("class","中班");
        kvStore2.putString("student_20230001",obj2.toString());
        textField.append("已更新数据\n");
    }
    //删除数据by Schema
    private  void DeleteDataBySchema()
    {
        SingleKvStore kvStore2 = getMyStudentKvStore();
        kvStore2.delete("student_20230001");
        textField.append("已删除数据\n");
    }

    //通过谓词查询
    private void SearchDataByWeici()
    {
        SingleKvStore kvStore2 = getMyStudentKvStore();
        Query query2 = Query.select().equalTo("$.class","中班").orderByAsc("name");
        List<Entry> entries2 = kvStore2.getEntries(query2);
    }
    


    private void WriteKeyValue()
    {
        SingleKvStore singleKvStore2 = getMySingleKvStore();
        singleKvStore2.putFloat("floatValue",0.6f); //单个写入
        List<Entry> entryList2 = new ArrayList<>();  //批量写入
        Entry entry2 = new Entry("intValue",Value.get(2));
        entryList2.add(entry2);
        Entry entry2a = new Entry("stringValue", Value.get("测试"));
        entryList2.add(entry2a);
        singleKvStore2.putBatch(entryList2);
        textField.append("已写入键值\n");

    }
    private  void GetKeyValue()
    {
        SingleKvStore singleKvStore2= getMySingleKvStore();
        try {
            float floatValue = singleKvStore2.getFloat("floatValue");
            textField.append("floatValue:"+floatValue+"\n");
            int intValue = singleKvStore2.getInt("intValue");
            textField.append("intValue:"+intValue+"\n");
            String stringValue = singleKvStore2.getString("stringValue");
            textField.append("stringValue:"+stringValue+"\n");



        }catch (KvStoreException ex)
        {
            textField.append("kvstore获取错误\n");
        }
    }
    private void DeleteKeyValue()
    {
        SingleKvStore singleKvStore2 = getMySingleKvStore();
        singleKvStore2.delete("floatValue");
        textField.append("已删除键值\n");
    }


    @Override
    public void onActive() {
        super.onActive();
    }


    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }
}


http://www.niftyadmin.cn/n/5450464.html

相关文章

LGBM算法 原理

简介 GBDT (Gradient Boosting Decision Tree) 是机器学习中一个长盛不衰的模型&#xff0c;其主要思想是利用弱分类器&#xff08;决策树&#xff09;迭代训练以得到最优模型&#xff0c;该模型具有训练效果好、不易过拟合等优点。GBDT不仅在工业界应用广泛&#xff0c;通常被…

SQL Server 实验二:数据库视图的创建和使用

目录 第一关 相关知识 什么是表 操作数据表 创建数据表 插入数据 修改表结构 删除数据表 编程要求 第一关实验代码&#xff1a; 第二关 相关知识 视图是什么 视图的优缺点 视图的优点 视图的缺点 操作视图 创建视图 通过视图向基本表中插入数据 通过视图修改基本表的…

工业智能物联网关如何助力工业防震减灾

地震灾害难以预料&#xff0c;一旦发生往往就损失重大。对于工业领域而言&#xff0c;地震灾害的影响不仅仅是对人员安全的威胁&#xff0c;还包括对生产设施的破坏、生产进程的中断以及伴生的持续性经济损失。 随着5G、大数据、物联网技术的发展&#xff0c;面向工业领域构建一…

ffmpeg把一个平面视频,做成左右平面视频

要使用FFmpeg将单个平面视频转换为左右&#xff08;或称为并排&#xff09;3D格式的视频&#xff0c;你可以使用FFmpeg的filter_complex功能来实现。这种类型的视频通常用于3D视觉效果&#xff0c;其中同一画面的两个版本并排放置&#xff0c;每个版本略有不同的视角&#xff0…

双进程交互实现App自动重启

背景 你可能会好奇&#xff0c;有些手游&#xff08;比如王者荣耀&#xff09;是怎么实现资源更新后自动重启的&#xff1f; 这个体验确实不错&#xff0c;因为不需要用户手动点击桌面图标重启App&#xff0c;在一些数据恢复备份的场景中&#xff0c;很实用。比如&#xff0c…

Sequelize一个易用且基于 promise 的 Node.js ORM 工具

Sequelize中文文档|Sequelize中文网 Sequelize 是一个易用且基于 promise 的 Node.js ORM 工具 适用于 Postgres, MySQL, MariaDB, SQLite, DB2, Microsoft SQL Server, Snowflake, Oracle DB 和 Db2 for IBM i. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功…

车辆平面坐标系转换

如图所示&#xff0c;将 waypoint 坐标从主车坐标系转换到世界坐标系。 [ x ′ y ′ z ′ ] [ x y z ] [ c o s θ s i n θ 0 − s i n θ c o s θ 0 0 0 1 ] \begin{bmatrix} x&y&z \end{bmatrix}\begin{bmatrix} x&y&z \end{bmatrix}\begin{bmatrix} \m…

CI/CD 搭建jenkins基础测试环境构建项目(一)

Jenkins是一个开源的持续集成工具&#xff0c;可以帮助开发团队自动化构建、测试和部署他们的软件项目。通过Jenkins&#xff0c;开发团队可以实现快速、高效地交付软件&#xff0c;并及时发现和解决问题&#xff0c;从而提高团队的生产力和软件质量。持续集成/持续交付&#x…