【鴻蒙開發】開發筆記-對象關系映射數據庫
一、前言
剛學習了對象關系映射數據庫,覺得這個數據庫很實用,所以想把學習到的記錄下來,通過文章讓自己的學習記憶更深刻。社區里很多關于這個的文章,但是我還是想寫一個我自己的。把我自己的理解寫出來,讓社區的大佬們看一下我有沒有哪些理解錯誤,可以批評指正。
二、概念
HarmonyOS對象關系映射(Object Relational Mapping,ORM)數據庫是一款基于SQLite的數據庫框架,屏蔽了底層SQLite數據庫的SQL操作,針對實體和關系提供了增刪改查等一系列的面向對象接口。應用開發者不必再去編寫復雜的SQL語句, 以操作對象的形式來操作數據庫,提升效率的同時也能聚焦于業務開發。
以上是官方文檔對這個數據庫的描述,我的理解是,這是一個可以存對象的數據庫。在關系型數據庫的基礎上,再增加對象這種類型。關系型數據庫只能存基礎類型,在實際開發中,我們經常會與服務器進行交互。服務器返回json字符串,之前Android有工具可以轉成實體類,鴻蒙暫時沒看到,但是可以在網上搜索json轉實體類,就可以進行json轉實體類。


在沒有學習到對象關系映射數據庫之前,我要把一個服務器返回的實體類信息進行存儲,就需要把返回的實體類再分解為一個個字段,比如下面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 {
- }
這里有個小小的坑,有些同學會出現如下問題:

這里報錯提示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:索引列表。

在上面提到的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為數據庫的路徑。那對象關系映射數據庫通過什么通知更新呢?通過注冊觀察者。
通過使用對象數據操作接口,開發者可以在某些數據上設置觀察者,接收數據變化的通知。

- // 定義一個觀察者類。
- 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。對象映射關系數據庫主要是在于實體類,比較符合我們和服務器交互的需要。