喜歡看代碼的,直接上代碼
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應用而言, 這其實是足夠的。)
復雜的情況是:
- 為字段創建索引
- 排除非持久化字段。業務層是直接使用Entity對象實例的,可能會在其中定義一些不需要持久化的字段,在真正的數據表中應該排除這些字段。
- 擴展表字段。在數據庫升級時,可能會增加表字段。
- 條件查詢指定列 。業務層僅僅定義了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的使用,包括以下幾個方面:
- 定義業務Entity
- 注冊業務DB和Table,調用中間件初始化
- 調用業務Entity的CRUD操作
- 擴展表字段
下面舉個例子,以應用需要批量保存用戶信息為例。
定義業務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!!!