#星光计划1.0#【鸿蒙开发】开发笔记-对象关系映射数据库 原创 精华

甜甜爱开发
发布于 2021-11-9 14:42
浏览
4收藏

本文正在参与51CTO HarmonyOS技术社区创作者激励-星光计划1.0

一、前言

刚学习了对象关系映射数据库,觉得这个数据库很实用,所以想把学习到的记录下来,通过文章让自己的学习记忆更深刻。社区里很多关于这个的文章,但是我还是想写一个我自己的。把我自己的理解写出来,让社区的大佬们看一下我有没有哪些理解错误,可以批评指正。

二、概念

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

以上是官方文档对这个数据库的描述,我的理解是,这是一个可以存对象的数据库。在关系型数据库的基础上,再增加对象这种类型。关系型数据库只能存基础类型,在实际开发中,我们经常会与服务器进行交互。服务器返回json字符串,之前Android有工具可以转成实体类,鸿蒙暂时没看到,但是可以在网上搜索json转实体类,就可以进行json转实体类。
#星光计划1.0#【鸿蒙开发】开发笔记-对象关系映射数据库-鸿蒙开发者社区
#星光计划1.0#【鸿蒙开发】开发笔记-对象关系映射数据库-鸿蒙开发者社区

在没有学习到对象关系映射数据库之前,我要把一个服务器返回的实体类信息进行存储,就需要把返回的实体类再分解为一个个字段,比如下面Person类

public class Person {
    Integer id;
    String name;
    String gender;
    String age;
}

如果按关系型数据库来写的话是这样的:

    private static final String DB_COLUMN_PERSON_ID = "id";

    private static final String DB_COLUMN_NAME = "name";

    private static final String DB_COLUMN_GENDER = "gender";

    private static final String DB_COLUMN_AGE = "age";

如果类字段少还好,如果多就会很费时间,开发讲究效率。那如果是对象型数据库是怎么写呢,接下来我把这个例子通过对比的方式,让大家看看对象关系映射数据库的实用之处。

三、创建数据库

1.开发数据库,首先要创建数据库,如果是关系型数据库,那创建的时候是这么写的:

a.配置数据库相关信息,包括数据库的名称、存储模式、是否为只读模式等。

    private static final String DB_NAME = "persondataability.db";//数据库名称

    private static final String DB_TAB_NAME = "person";//表的名称

    private static final String DB_COLUMN_PERSON_ID = "id";//字段id

    private static final String DB_COLUMN_NAME = "name";//字段name

    private static final String DB_COLUMN_GENDER = "gender";//字段gender

    private static final String DB_COLUMN_AGE = "age";//字段age

    private static final int DB_VERSION = 1;//数据库版本号

    private StoreConfig storeConfig = StoreConfig.newDefaultConfig(DB_NAME);//配置数据库名称
    private RdbStore rdbStore;

b.初始化数据库表结构和相关数据。

    private RdbOpenCallback rdbOpenCallback = new RdbOpenCallback() {
        @Override
        public void onCreate(RdbStore rdbStore) {
            //数据库创建时被回调,开发者可以在该方法中初始化表结构,并添加一些应用使用到的初始化数据。
            rdbStore.executeSql("create table if not exists "
                    + DB_TAB_NAME + " ("
                    + DB_COLUMN_PERSON_ID +" integer primary key,"
                    + DB_COLUMN_NAME + " text not null,"
                    + DB_COLUMN_GENDER + " text not null,"
                    + DB_COLUMN_AGE + " integer)");
        }

        @Override
        public void onUpgrade(RdbStore rdbStore, int i, int i1) {
           //数据库升级时被回调
        }
    };

c.创建数据库。

DatabaseHelper databaseHelper = new DatabaseHelper(this);//DatabaseHelper是数据库操作的辅助类,当数据库创建成功后,
// 数据库文件将存储在由上下文指定的目录里。数据库文件存储的路径会因指定不同的上下文存在差异。
rdbStore = databaseHelper.getRdbStore(storeConfig,DB_VERSION,rdbOpenCallback,null);//根据配置创建或打开数据库。

2.如果是对象关系型数据库,写法就简单很多,如下:

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

@Database(entities = {Person.class},version = 1)
public abstract class PersonOrm extends OrmDatabase {
}

这里有个小小的坑,有些同学会出现如下问题
#星光计划1.0#【鸿蒙开发】开发笔记-对象关系映射数据库-鸿蒙开发者社区
这里报错提示Cannot resolve symbol ‘Database’,那是因为我们没有添加一个配置,在当前模块进行配置

如果使用注解处理器的模块为“com.huawei.ohos.hap”模块,则需要在模块的“build.gradle”文件的ohos节点中添加以下配置:

注意,这里必须添加在当前模块的build.gradle文件,如果是项目的build.gradle文件就没效果,添加后要进行同步,同步按钮在右上角Sync Now

compileOptions{        
    annotationEnabled true    
} 

这样就不会报错了

b.创建数据表。开发者可通过创建一个继承了OrmObject并用@Entity注解的类,获取数据库实体对象,也就是表的对象。
属性:
tableName:表名。
primaryKeys:主键名,一个表里只能有一个主键,一个主键可以由多个字段组成。
foreignKeys:外键列表。
indices:索引列表。
#星光计划1.0#【鸿蒙开发】开发笔记-对象关系映射数据库-鸿蒙开发者社区
在上面提到的Person.class,其实就是我们本来用来解析服务器json数据所创建的实体类,如果要用来存储数据库,作为数据库的表,就需要进行一定的修改,修改如下:

@Entity(tableName = "person")//设置表的名称
public class Person extends OrmObject {
    @PrimaryKey()//把id作为主键
    Integer id;
    String name;
    String gender;
    String age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

以上是简单版本的实体类修改为数据库实体对象,也就是表的对象,鸿蒙还提供了几个实用的技巧,比如:
1.我们服务器返回的字段,不一定要存入数据库,可以用"ignoredColumns"表示该字段不加入表的属性.
2.我们有时候需要两个字段合起来进行查询,比如姓和名进行复合索引,那么可以用"indices"建立复合索引.
3.设置自增的主键,这个也是数据库一般都需要的.
在本文例子中,我们添加几个字段用来展示上面说的技巧,示例如下:

@Entity(tableName = "person",//设置表的名称
        ignoredColumns = {"address"},//设置不加入表的属性字段
        indices = {@Index(value = {"firstName","lastName"},name = "name_index",unique = true)})
        //indices 为“firstName”和“lastName”两个字段建立了复合索引“name_index”,并且索引值是唯一的
public class Person extends OrmObject {
    @PrimaryKey(autoGenerate = true)//把id设为了自增的主键。注意只有在数据类型为包装类型时,自增主键才能生效
    Integer id;
    String name;
    String gender;
    String age;
    String firstName;
    String lastName;
    String address;//不加入表的属性

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

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

        // context入参类型为ohos.app.Context,注意不要使用slice.getContext()来获取context,请直接传入slice,否则会出现找不到类的报错。
        DatabaseHelper databaseHelper = new DatabaseHelper(this);
        //数据库别名为“PersonStore”,数据库文件名称为“PersonStore.db”,最后传入的是继承OrmDatabase的数据库类,这里我一开始写错了,以为是实体类。希望没人跟我一样笨
        OrmContext context = databaseHelper.getOrmContext("PersonStore","PersonStore.db", PersonOrm.class);

以上就是对象关系映射数据库的创建过程,是不是比关系型数据库要简单一点呢。数据库创建好了,就需要进行增删改查的操作了,接下来我把关系型数据库和对象关系映射数据库的增删改查都对比一下。为了方便大家对比和学习,大家可以通过目录快速查找你想看的内容哦。

四、增加数据

1.关系型数据库

封装了一个插入数据的方法,用于调用

 /***
     * 向数据库插入数据。
     * @param uri  数据库的路径,传入路径,用于判断是否是正确的数据库
     * @param value 以ValuesBucket存储的待插入的数据。它提供一系列put方法,如putString(String columnName, String values),putDouble(String columnName, double value),用于向ValuesBucket中添加数据。
     * @return
     */
    @Override
    public int insert(Uri uri, ValuesBucket value) {
        HiLog.info(LABEL_LOG, "DataAbility insert");
        String path = uri.getLastPath();
        if (!"person".equals(path)){
            HiLog.info(LABEL_LOG,"DataAbility insert path is not matched");
            return -1;
        }
        ValuesBucket valuesBucket = new ValuesBucket();
        valuesBucket.putInteger(DB_COLUMN_PERSON_ID,value.getInteger(DB_COLUMN_PERSON_ID));
        valuesBucket.putString(DB_COLUMN_NAME, value.getString(DB_COLUMN_NAME));
        valuesBucket.putString(DB_COLUMN_GENDER,value.getString(DB_COLUMN_GENDER));
        valuesBucket.putInteger(DB_COLUMN_AGE,value.getInteger(DB_COLUMN_AGE));
        int index = (int) rdbStore.insert(DB_TAB_NAME,valuesBucket);//插入数据,第一个参数是数据库的表名,第二个是数据
        DataAbilityHelper.creator(this,uri).notifyChange(uri);//通知数据库更新
        return index;
    }

2.对象关系映射数据库

当数据库有变化时,关系型数据库通过DataAbilityHelper.creator(this,uri).notifyChange(uri);通知数据库更新,uri为数据库的路径。那对象关系映射数据库通过什么通知更新呢?通过注册观察者

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

#星光计划1.0#【鸿蒙开发】开发笔记-对象关系映射数据库-鸿蒙开发者社区

     // 定义一个观察者类。
    private class CustomedOrmObjectObserver implements OrmObjectObserver {
        @Override
        public void onChange(OrmContext changeContext, AllChangeToTarget subAllChange) {
            // 用户可以在此处定义观察者行为
        }
    }

用法:

        CustomedOrmObjectObserver observer = new CustomedOrmObjectObserver();
        context.registerEntityObserver("Person", observer); // 调用registerEntityObserver方法注册一个观察者observer
        context.close();//不需要的时候及时关闭
// 当以下方法被调用,并flush成功时,观察者observer的onChange方法会被触发。其中,方法的入参必须为Person类的对象。
public <T extends OrmObject> boolean insert(T object)
public <T extends OrmObject> boolean update(T object)
public <T extends OrmObject> boolean delete(T object)

回到增加数据上来,对象关系映射数据库增加数据方法如下:

private void insert(){
        Person person = new Person();
        person.setName("lili");
        person.setAge("12");
        person.setGender("女");
        OrmContext ormContext = databaseHelper.getOrmContext(DB_ALIAS,DB_NAME,PersonOrm.class);
        if (ormContext.insert(person)){
            //说明插入成功
        }else {
            //说明插入失败
        }
        ormContext.registerContextObserver(ormContext,observer);
        ormContext.flush();
        ormContext.close();
    }

这个增加数据是不是比关系型数据库要简单一点呢?而且也比较符合我们的代码习惯,直接把实体对象赋值,传入就可以了。

五、查询数据

1.关系型数据库

a.构造用于查询的谓词对象,设置查询条件。
b.指定查询返回的数据列。
c.调用查询接口查询数据。
d.调用结果集接口,遍历返回结果
这里官网给的例子是以下这样的

        String[] columns = new String[] {"id", "name", "age", "salary"};  //构造用于查询的谓词对象
        RdbPredicates rdbPredicates = new RdbPredicates("test").equalTo("age", 25).orderByAsc("salary");//设置查询条件,指定查询返回的数据列
        ResultSet resultSet = store.query(rdbPredicates, columns);//调用查询接口查询数据
        resultSet.goToNextRow();//调用结果集接口,遍历返回结果

但是实际应用中,我们一般把关系型数据库的代码写在DataAbility里面,所以这边查询封装的代码如下:

/***
     *
     * @param uri   数据库的地址
     * @param columns 构造用于查询的谓词对象
     * @param predicates  查询条件
     * @return  返回结果集
     */
    @Override
    public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) {
        RdbPredicates rdbPredicates = DataAbilityUtils.createRdbPredicates(predicates,DB_TAB_NAME);//构造查询条件
        ResultSet resultSet = rdbStore.query(rdbPredicates,columns);//调用查询接口
        if (resultSet == null){
            HiLog.info(LABEL_LOG,"resultSet is null");
        }
        return resultSet;
    }

那我们要怎么调用呢?调用如下:

    private void query() {
        String[] columns = new String[] {DB_COLUMN_PERSON_ID,
                DB_COLUMN_NAME, DB_COLUMN_GENDER, DB_COLUMN_AGE};
        // 构造查询条件
        DataAbilityPredicates predicates = new DataAbilityPredicates();
        predicates.between(DB_COLUMN_AGE, 15, 40);//构造查询条件
        try {
            ResultSet resultSet = databaseHelper.query(Uri.parse(BASE_URI + DATA_PATH),
                    columns, predicates);//调用封装方法
            if (resultSet == null || resultSet.getRowCount() == 0) {
                HiLog.info(LABEL_LOG, "query: resultSet is null or no result found");
                return;
            }
            resultSet.goToFirstRow();//回到结果第一行
            do {//开始遍历结果
                int id = resultSet.getInt(resultSet.getColumnIndexForName(DB_COLUMN_PERSON_ID));
                String name = resultSet.getString(resultSet.getColumnIndexForName(DB_COLUMN_NAME));
                String gender = resultSet.getString(resultSet.getColumnIndexForName(DB_COLUMN_GENDER));
                int age = resultSet.getInt(resultSet.getColumnIndexForName(DB_COLUMN_AGE));
                HiLog.info(LABEL_LOG, "query: Id :" + id + " Name :" + name + " Gender :" + gender + " Age :" + age);
            } while (resultSet.goToNextRow());
        } catch (DataAbilityRemoteException | IllegalStateException exception) {
            HiLog.error(LABEL_LOG, "query: dataRemote exception | illegalStateException");
        }
    }

2.对象关系映射数据库

private void query() {
        OrmContext ormContext = databaseHelper.getOrmContext(DB_ALIAS,DB_NAME,PersonOrm.class);
        OrmPredicates query = ormContext.where(Person.class).equalTo("name", "San");
        List<Person> persons = ormContext.query(query);
        ormContext.flush();
        ormContext.close();
        if (persons.size() == 0) {
            return;
        }
        for (Person person : persons) {
            //循环输出结果
        }
    }

对比起来,对象关系映射数据库是不是更简单呢?有种SQL语句的感觉哈哈

六、更新数据

1.关系型数据库

    @Override
    public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {
        RdbPredicates rdbPredicates = DataAbilityUtils.createRdbPredicates(predicates,DB_TAB_NAME);
        int index = rdbStore.update(value,rdbPredicates);
        HiLog.info(LABEL_LOG,"update:"+ index);
        DataAbilityHelper.creator(this,uri).notifyChange(uri);
        return index;
    }

怎么调用呢?

    private void update() {
        DataAbilityPredicates predicates = new DataAbilityPredicates();
        predicates.equalTo(DB_COLUMN_PERSON_ID, 102);
        ValuesBucket valuesBucket = new ValuesBucket();
        valuesBucket.putString(DB_COLUMN_NAME, "ZhangSanPlus");
        valuesBucket.putInteger(DB_COLUMN_AGE, 28);
        try {
            if (databaseHelper.update(Uri.parse(BASE_URI + DATA_PATH), valuesBucket, predicates) != -1) {
                HiLog.info(LABEL_LOG, "update successful");
            }
        } catch (DataAbilityRemoteException | IllegalStateException exception) {
            HiLog.error(LABEL_LOG, "update: dataRemote exception | illegalStateException");
        }
    }

2.对象映射关系型数据库

    private void update() {
        OrmContext ormContext = databaseHelper.getOrmContext(DB_ALIAS,DB_NAME,PersonOrm.class);
        OrmPredicates predicates = ormContext.where(Person.class);
        predicates.equalTo("age", 29);
        List<Person> persons = ormContext.query(predicates);
        if (persons.size() == 0) {
            new ToastDialog(this).setText("no data not update").show();
            return;
        }
        Person user = persons.get(0);
        ormContext.registerObjectObserver(user, observer);
        user.setFirstName("Li");
        if (ormContext.update(user)) {
           //更新成功
        } else {
            //更新失败
        }
        ormContext.flush();
        ormContext.close();
        ormContext.unregisterObjectObserver(user, observer);
    }

上面例子提供了两种更新的应用,根据查询条件进行更新,或是指定某个数据项进行更新。
对比还是觉得对象映射关系型数据库要简单易懂一点。

七、删除数据

1.关系型数据库

 @Override
    public int delete(Uri uri, DataAbilityPredicates predicates) {
        RdbPredicates rdbPredicates = DataAbilityUtils.createRdbPredicates(predicates,DB_TAB_NAME);
        int index = rdbStore.delete(rdbPredicates);
        HiLog.info(LABEL_LOG,"delete"+index);
        DataAbilityHelper.creator(this,uri).notifyChange(uri);
        return index;
    }

调用方法:

private void delete() {
        DataAbilityPredicates predicates = new DataAbilityPredicates()
                .equalTo(DB_COLUMN_PERSON_ID, 100);
        try {
            if (databaseHelper.delete(Uri.parse(BASE_URI + DATA_PATH), predicates) != -1) {
                HiLog.info(LABEL_LOG, "delete successful");
            }
        } catch (DataAbilityRemoteException | IllegalStateException exception) {
            HiLog.error(LABEL_LOG, "delete: dataRemote exception | illegalStateException");
        }
    }

2.对象映射关系数据库

private void delete() {
        OrmContext ormContext = databaseHelper.getOrmContext(DB_ALIAS,DB_NAME,PersonOrm.class);
        OrmPredicates predicates = ormContext.where(Person.class);
        predicates.equalTo("age", 29);
        List<Person> persons = ormContext.query(predicates);
        if (persons.size() == 0) {
            new ToastDialog(this).setText("no data not delete").show();
            return;
        }
        Person person = persons.get(0);
        if (ormContext.delete(person)) {
            new ToastDialog(this).setText("delete success").show();
        } else {
            new ToastDialog(this).setText("delete fail").show();
        }
        ormContext.flush();
        ormContext.close();
    }

八、小结

以上就是关于两种数据库类型的增删改查,对比一下,对象映射关系型数据库相对简单易懂一些。关系型数据库我觉得两个重要的,一个是ValuesBucket 用来构建数据 value, 一个是DataAbilityPredicates 用来构建条件predicates。对象映射关系数据库主要是在于实体类,比较符合我们和服务器交互的需要。
本文正在参与51CTO HarmonyOS技术社区创作者激励-星光计划1.0

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2021-11-9 14:42:00修改
4
收藏 4
回复
举报
1条回复
按时间正序
/
按时间倒序
mb621b106093124
mb621b106093124

对比着来学就很清楚,点赞o( ̄▽ ̄)d

回复
2022-2-27 13:48:56
回复
    相关推荐