面向對象的Android數據庫框架OOSqlite

喜歡看代碼的,直接上代碼
GitHub:https://github.com/kkmoving/OOSqliteApp

在Android應用中,對條目類數據的存儲通常會用到數據庫,Andriod提供sqlite來實現數據庫存儲。

應用對數據的使用通常是面向對象的,sqlite對數據的存儲并不是面向對象的。這里提供一種面向對象的sqlite數據庫框架設計OOSqlite(Object-Oriented Sqlite)。

OOSqlite借鑒J2EE中Hibernate的思想,將數據抽象為Entity,并映射為數據庫的一張表,一個Entity的實例就是數據表的一條記錄。

先來看看OOSqlite的使用
Define your entity

public class User extends OOSqliteEntity {   
  private static final int NAME_COL_ID = 1;
  private static final int AGE_COL_ID = 2; 

  @Indexing
  @ColumnTag(NAME_COL_ID)    
  String name;  

  @ColumnTag(AGE_COL_ID)
  int age;    

  @Transient    
  boolean choosen;        

  float score;        
}

Create

User.insert(user);

Retrieve

User.query(User.class, null);

Update

User.update(user);

Delete

User.delete(user);

OOSqlite設計思想

一個應用可以包含多個DB,一個DB可以包含多個Table,每個Table由若干字段組成。應用啟動時創建DB及其所包含的Table;在應用運行過程中,對Table進行CRUD(增查改刪)操作。

OOSqlite的核心是將數據抽象為Entity(實體),并將其注冊到框架中,框架根據Entity的定義進行轉換,并在DB中創建Table;進行CRUD操作時直接對Entity對象進行操作。

框架將DB抽象為OODatabase,它包含DB名稱,版本及其Table列表。同時它是與底層數據庫交互的媒介。

Table抽象為OOSqliteTable,包含Table名稱,字段列表。它提供CRUD操作接口。

字段抽象為OOColumn,包含字段名稱,字段類型,是否建索引等信息。同時OOColumn綁定到Entity的Field,用于Entity字段值的獲取和設置,便于Entity和Table之間的轉換。

數據抽象OOSqliteEntity,它包含數據庫基礎字段(如數據庫id,last_access等)。業務層數據直接繼承OOSqliteEntity,并定義業務字段。

OOSqlite實現

OOSqlite的實現包括:DB和Table的創建,CRUD操作。Table的創建涉及到Entity到Table的轉換。

DB和Table的創建

OOSqlite框架通過OOSqliteManager來進行統一管理,提供注冊DB和Table的方法,以及初始化數據庫的方法。在應用啟動時,進行DB和Table的注冊,并調用初始化方法進行框架的初始化。

OOSqliteManager在初始化時,會對業務層注冊的OODatabase進行初始化,通過實例化sqlite的SQLiteOpenHelper來完成物理層DB的創建。

OODatabase持有SQLiteOpenHelper實例引用,用于底層數據庫交互;同時OODatabase持有業務層注冊的OOSqliteTable列表,在SQLiteOpenHelper的onCreate回調中完成數據表的創建;在onUpgrade和onDowngrade中完成數據表的升級和降級。

Table創建的關鍵是獲取字段列表,字段列表的獲取是把Entity類的成員變量轉換為數據庫字段,然后組裝成sql語句,使用SQLiteOpenHelper回傳的SQLiteDatabase執行sql語句,創建物理數據表。

Table的升級和降級可能會進行表結構調整,如增加字段,同樣通過獲取Entity類的新增字段(新增字段使用注解標識,后面會講到)。

Entity到Table的轉換

Entity到Table有兩個轉換,一個是創建表時對表結構信息的轉換,一個是CRUD操作時Entity對象實例和數據庫數據的轉換。CRUD操作的轉換在CRUD操作中會具體講到,這里先說創建表時的轉換。

創建表的轉換其實就是通過反射將Entity類的成員信息轉換為數據表字段信息,這個轉換會生成一個OOColumn列表,保存在OOSqliteTable中,OOSqliteTable使用OOColumn列表生成創建表的sql語句。

最簡單的情況是,將Entity類的成員全部映射為數據表的字段,字段名為成員名(當然,可以做一些命名轉換,如轉為全小寫),字段類型由Java基本數據類型轉換為數據庫字段類型。(暫時支持Java基本數據類型,也就是說該框架僅支持基本數據類型的存儲,不涉及外鍵的關聯存儲。對于一般Android應用而言, 這其實是足夠的。)

復雜的情況是:

  1. 為字段創建索引
  2. 排除非持久化字段。業務層是直接使用Entity對象實例的,可能會在其中定義一些不需要持久化的字段,在真正的數據表中應該排除這些字段。
  3. 擴展表字段。在數據庫升級時,可能會增加表字段。
  4. 條件查詢指定列 。業務層僅僅定義了Entity的,在條件查詢時需要指定列進行條件查詢。

這些都通過注解來實現。定義4種注解:
@ Indexing:標識字段需要創建索引。
@Transient:標識字段為非持久化字段。
@TargetVersion(version):為字段綁定一個版本,在數據庫升級到相應版本或之上時,增加該字段。
@ColumnTag(col_id):為字段綁定一個col_id,在條件查詢時通過一個col_id對應到指定列。同一個Entity中,不同字段指定不用col_id。

創建表轉換時,對于標識@Indexing的字段,生成創建索引的sql語句,創建表時同時創建索引;對于標識@Transient的字段,排除在OOColumn列表之外,也就不創建在表中;對于標識@TargetVersion的字段,在Table升級到對應版本及之后時,增加到表中。

對于標識@ColumnTag的字段,OOSqliteTable會保存col_id到OOColumn的映射,這樣就可以通過指定的col_id來指定列,實現條件查詢。

CRUD

CRUD操作過程中,OODatabase主要負責向下執行,它持有SQLiteOpenHelper實例用于底層sqlite交互。OOSqliteTable主要負責向上提供面向Entity對象的CRUD操作接口,向下通過OODatabase間接與底層數據庫交互,并在數據庫數據和Entity對象實例之間進行轉換。

OOSqliteTable類似于Hibernate中EntityManager的角色,提供面向對象的CRUD操作接口。但是對于業務層來講,這個角色是不必要的,業務層只定義了業務Entity,并不關心其他的。因此,這里的實現使用一種更簡潔方便的方式,CRUD操作直接通過Entity提供靜態方法來實現

為了能夠提供靜態CRUD接口,框架保存了一個Entity的Class到OOSqliteTable的映射,CRUD操作傳入相應的Class即可映射到OOSqliteTable,進行真正的CRUD操作。

插入(Create)

public static void insert(LeSqliteEntity entity)

插入操作只需要傳入一個Entity對象。

通過對象的Class找到OOSqliteTable,在其中完成插入操作。OOSqliteTable將Entity對象轉換為ContentValues,并調用SQLiteDatabase的insert接口,插入到數據庫中。Entity對象轉換到ContentValues本質上是將Entity對應的OOColumn列表轉換到ContentValues中。

查詢(Retrieve)

public static List query(Class cls, String selection) 

查詢操作傳入Entity的Class和selection,指定要查詢的表和條件。
通過Class對應的OOSqliteTable調用SQLiteDatabase
的query接口進行查詢,并將查詢結果Cursor轉換化為OOColumn,創建Entity實例并設置Field值。
另外,Entity基類中封裝了常用的條件語句接口,可直接調用作為selection,如equalSelection,likeSelection等。

修改(Update)

 public static int update(LeSqliteEntity entity)

修改操作傳入Entity對象。
與插入操作類似,將Entity對象轉換成ContentValues后,調用SQLiteDatabase的update接口進行更新操作。不同的是修改操作需要事先判斷Entity是否為游離實體(不是從數據庫取出的實體為游離實體),如果不是游離實體才能更新到數據庫。(很好理解,如果是游離實體,應該是插入操作而不是更新操作)。

刪除(Delete)

public static int update(LeSqliteEntity entity)

刪除操作傳入Entity對象。
OOSqliteTable根據其數據庫ID,調用SQLiteDatabase的delete接口,實現刪除操作。與更新操作類似,刪除操作事先也會判斷Entity是否為游離實體。

以上就是面向對象的Android數據庫框架的設計與實現。下面講講基于這個框架,應用層如何使用數據庫。

使用

對于OOSqlite的使用,包括以下幾個方面:

  1. 定義業務Entity
  2. 注冊業務DB和Table,調用中間件初始化
  3. 調用業務Entity的CRUD操作
  4. 擴展表字段

下面舉個例子,以應用需要批量保存用戶信息為例。

定義業務Entity

首先我們需要一張表來存儲用戶信息,先定義實體User繼承自OOSqliteEntity。User字段包括:
姓名:name。通常會指定姓名進行查詢,因此name是需要創建索引的,另外需要指定name進行查詢。
年齡:age
是否選中:choosen。這是業務層需要的臨時字段,不需要持久化。
得分:score

 public class User extends OOSqliteEntity {   
      private static final int NAME_COL_ID = 1;
      private static final int AGE_COL_ID = 2; 

      @Indexing
      @ColumnTag(NAME_COL_ID)    
      String name;  

      @ColumnTag(AGE_COL_ID)
      int age;    

      @Transient    
      boolean choosen;        

      float score;        
 }

注冊業務DB和Table,中間件初始化

注冊DB,DB名稱可以隨便取,這里叫app.db,版本號定義1。中間件初始化調用LeSqliteManager的初始化接口完成,調用的時機可以在Application的onCreate。

public class DemoApplication extends Application {
    @Override    
    public void onCreate() {
        super.onCreate();        
        DatabaseManager.init(this);    
    }
}

public class DatabaseManager {    

    public static void init(Context context) {        
        OODatabase database = new OODatabase("app.db", 1); 
        database.registerEntity(User.class);

        OOSqliteManager.getInstance().register(database);
        OOSqliteManager.getInstance().initAllDatabase(context);    
    }   
}

Table的注冊直接注冊Entity的Class即可。如果需要在數據表創建、升級和降級回調中執行業務邏輯,可以實例化OOSqliteTable注冊到OODatabase 中, 在OOTableListener中完成實現業務邏輯。
比如,想要在數據表創建后即添加一個初始的用戶。

調用OODatabase的registerTable接口:

database.registerEntityWithListener(User.class, User.createListener());

實現OOTableListener,并實例化OOSqliteTable:

public static OOTableListener createListener() {
    return new OOTableListener() {

        @Override
        public void onCreate() {
            User user = new User();
            user.name = "admin";

            User.insert(user);
        }

        @Override
        public void onUpgrade(int oldVersion, int newVersion) {
            if (oldVersion < 2 && newVersion >= 2) {
                User user = new User();
                user.name = "upgrade";

                User.insert(user);
            }
        }

        @Override
        public void onDowngrade(int oldVersion, int newVersion) {

        }

        @Override
        public void onReady() {

        }
    };
}

CRUD操作

添加用戶

public static void add(String name, int age) {
    User user = new User();
    user.name = name;
    user.age = age;

    User.insert(user);
}

查詢所有用戶

同步查詢

public static List<User> queryAll() {
    return User.query(User.class, null);
}

異步查詢

public static void asyncQueryAll() {
    User.queryAsync(User.class, null, new LeQueryCallback() {
        @Override
        public void onQuerySuccess(List list) {
            
        }
    });
}

按名字查詢,并按年年齡升序排序

public static List<User> queryByNameOrderByAge(String name) {
    String selection = User.equalSelection(getNameColumn(), name);
    return User.query(User.class, selection, getAgeColumn(), true);
}

private static OOColumn getNameColumn() {
    return getColumn(User.class, NAME_COL_ID);
}

private static OOColumn getAgeColumn() {
    return getColumn(User.class, AGE_COL_ID);
}

通過@ColumnTag綁定的字段ID找到對應的OOColumn,現使用equalSelection接口生成selection,并指定年齡需要升序排序的列。

自定義查詢
查詢年齡大于30的用戶

public static List<User> queryAgeOlderThan30() {
    OOColumn ageCol = getAgeColumn();
    String selection = ageCol.mName + ">30";

    return User.query(User.class, selection);
}

更新分數

mUser.score += 1;
User.update(mUser);

刪除用戶

User.delete(mUser);

擴展表字段
前面我們已經生成的數據庫,如果需要擴展數據表的字段,比如User需要增加一個是否激活字段,那么只需要在User中再定義@TargetVersion標識的字段,TargetVersion的值定為2,表示數據庫升級版本2及之后時,增加該字段。然后,在注冊DB時,指定DB的版本為2。
定義active字段

@TargetVersion(2)
boolean active;

初始化時指定DB版本為2

OODatabase database = new OODatabase("app.db", 2);

總結

面向對象的Android數據庫中間件的設計和使用就介紹完了。它實現了簡單的面向對象數據庫操作,對于業務層數據庫存儲實現來講非常方便。這里說簡單,是因此它僅支持基礎數據類型(文本,整型,浮點數據,布爾值,二進制數據)的存儲,沒有實現外鍵的關聯存儲,也不支持Lazy加載。盡管簡單,對于大部分的Andriod應用數據庫存儲需求,已經足夠了_

說了這么多,show me the code!!!

GitHub: https://github.com/kkmoving/OOSqliteApp

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容