Android ORM——greenDAO 3及使用greenDAO 3前應該掌握的一些知識點(一)
引言
總所周知,SQLite——內嵌於Android中一個佔用記憶體極小的關係型,作為我們Android儲存領域中重要的一員 ,或多或少都曾接觸到資料庫。即使Android系統中提供了許多操作SQLite的API,但是在業務開發中還是需要手動去編寫原生SQL語句,這不僅複雜、不好維護,更重要的是不高效,常常會因為SQL語句寫錯而增加了開發成本,於是便出現了ORM(物件關係對映)框架,簡化了編寫SQL語句的開發工作,其中比較著名的有GreenDao、OrmLite、Litepal等。
一、greenDAO 3概述
greenDAO 3是基於註解來定義 schemas 和entities,而greenDAO 3之前的版本則要求開發人員有一個單獨的生成器Java專案來定義的。
如上圖所示,greenDAO 是一個將物件對映到 SQLite 資料庫中的輕量且快速的 ORM 解決方案。(greenDAO is a light & fast ORM solution that maps objects to SQLite databases.)它效能最大化,可能是Android平臺上最快的ORM框架 易於使用的API 最小的記憶體開銷 依賴體積小 支援資料庫加密 強大的社群支援
二、greenDAO的重大角色
D for Data,A for Access,O for Object(如果沒理解錯的話)。 所以最重要的三大主角肯肯定非:DAOs、DaoMaster
###1、DAOs
DAOs是負責直接實現增刪改查資料庫的,直接對映著我們要操作的JeanBean,所以預設情況下命名為XxxDAO。簡而言之,要想操作資料庫,都得先通過DaoSession的getXxxDAO系方法獲得對應的DAO,再呼叫對應的方法, DAOs裡生成資料庫語句, 將成員變數與表裡的引數繫結對應起來, 並且在它的父類AbstractDao 裡實現了相對DaoSession的操作資料的方法(也可以從DaoSession 父類的原始碼裡發現DaoSession 的那些方法本質上就是DAOs的封裝),其實只需看看我們自己的DAO程式碼即可猜到他的角色了。
// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
/**
* DAO for table "USER".
*/
public class UserDao extends AbstractDao<User, Long> {
public static final String TABLENAME = "USER";
/** Creates the underlying database table. */
public static void createTable(Database db, boolean ifNotExists) {
String constraint = ifNotExists? "IF NOT EXISTS ": "";
db.execSQL("CREATE TABLE " + constraint + "\"USER\" (" + //
"\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ," + // 0: _id
"\"NAME\" TEXT," + // 1: name
"\"AVATAR_URL\" TEXT," + // 2: avatarUrl
"\"IS_VERFYED\" INTEGER NOT NULL );"); // 3: isVerfyed
}
/** Drops the underlying database table. */
public static void dropTable(Database db, boolean ifExists) {
String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"USER\"";
db.execSQL(sql);
}
@Override
protected final void bindValues(DatabaseStatement stmt, User entity) {
stmt.clearBindings();
stmt.bindLong(1, entity.get_id());
String name = entity.getName();
if (name != null) {
stmt.bindString(2, name);
}
String avatarUrl = entity.getAvatarUrl();
if (avatarUrl != null) {
stmt.bindString(3, avatarUrl);
}
stmt.bindLong(4, entity.getIsVerfyed() ? 1L: 0L);
}
@Override
public User readEntity(Cursor cursor, int offset) {
User entity = new User( //
cursor.getLong(offset + 0), // _id
cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1), // name
cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // avatarUrl
cursor.getShort(offset + 3) != 0 // isVerfyed
);
return entity;
}
...
}
2、DaoMaster
DaoMaster 它掌管著資料庫物件(SQLiteDatabase) 並且它還管理所有的 DAO 和DAOSession。首先通過AbstractDaoMaster的registerDaoClass方法建立DaoConfig物件,註冊一個實體類,並新增到DAOMaster的daoConfigMap物件中,以Map的形式儲存資訊,表示該實體類需要持久化,待為其建立資料表。具體的過程與原生的有所不同,使用過SQLite 原生SQL方式的,應該都知道在Android系統中為了便於對SQLiteDatabase做資料庫操作提供了SQLiteOpenHelper類,它裡面有兩個抽象方法分別是onCreate()和onUpdate()分別在資料庫第一次建立和升級的時候被呼叫。而greenDAO則是在DAOMaster中添加了OpenHelper繼承了DatabaseOpenHelper(其中DatabaseOpenHelper繼承了SQLiteOpenHelper),並在onCreate()方法中呼叫createAllTables(db, false)建立所有的資料表;而DevOpenHelper則繼續繼承OpenHelper並在onUpdate()在方法中刪除所有資料表並重新建立,createAllTables和dropAllTables方法比較簡單,只是簡單地調動DAOs的資料表建立和刪除方法。最後,再通過newSession系列方法初始化DaoSession。
// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
/**
* Master of DAO (schema version 1): knows all DAOs.
*/
public class DaoMaster extends AbstractDaoMaster {
public static final int SCHEMA_VERSION = 1;
/** Creates underlying database table using DAOs. */
public static void createAllTables(Database db, boolean ifNotExists) {
UserDao.createTable(db, ifNotExists);
}
/** Drops underlying database table using DAOs. */
public static void dropAllTables(Database db, boolean ifExists) {
UserDao.dropTable(db, ifExists);
}
/**
* WARNING: Drops all table on Upgrade! Use only during development.
* Convenience method using a {@link DevOpenHelper}.
*/
public static DaoSession newDevSession(Context context, String name) {
Database db = new DevOpenHelper(context, name).getWritableDb();
DaoMaster daoMaster = new DaoMaster(db);
return daoMaster.newSession();
}
public DaoSession newSession(IdentityScopeType type) {
return new DaoSession(db, type, daoConfigMap);
}
public DaoMaster(Database db) {
super(db, SCHEMA_VERSION);
registerDaoClass(UserDao.class);
}
/**
* Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} -
*/
public static abstract class OpenHelper extends DatabaseOpenHelper {
public OpenHelper(Context context, String name) {
super(context, name, SCHEMA_VERSION);
}
public OpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory, SCHEMA_VERSION);
}
@Override
public void onCreate(Database db) {
Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
createAllTables(db, false);
}
}
/** WARNING: Drops all table on Upgrade! Use only during development. */
public static class DevOpenHelper extends OpenHelper {
public DevOpenHelper(Context context, String name) {
super(context, name);
}
public DevOpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
dropAllTables(db, true);
onCreate(db);
}
}
...
}
3、DaoSession
DaoSession 管理著所有的DAO 物件主要用於提供獲取DAOs的介面,每一個DaoMaster持有一個數據庫連線,通過DaoMaster的newSession()方法可以例項化多個Session,這些Session對應同一個資料庫連線,但是系統會為每一個Session分配記憶體,在這片記憶體中會為實體進行快取。每一個Session對應一個Identity scope。一個新的Session就代表一個會話,通過同一個會話中的DAOs進行的資料庫操作,greenDAO會對其優化,例如查詢操作會對結果快取之類的機制等等。
public class AbstractDaoSession {
private final Database db;
private final Map<Class<?>, AbstractDao<?, ?>> entityToDao;
public AbstractDaoSession(Database db) {
this.db = db;
this.entityToDao = new HashMap<Class<?>, AbstractDao<?, ?>>();
}
protected <T> void registerDao(Class<T> entityClass, AbstractDao<T, ?> dao) {
entityToDao.put(entityClass, dao);
}
/** Convenient call for {@link AbstractDao#queryRaw(String, String...)}. */
public <T, K> List<T> queryRaw(Class<T> entityClass, String where, String... selectionArgs) {
@SuppressWarnings("unchecked")
AbstractDao<T, K> dao = (AbstractDao<T, K>) getDao(entityClass);
return dao.queryRaw(where, selectionArgs);
}
/** Convenient call for {@link AbstractDao#queryBuilder()}. */
public <T> QueryBuilder<T> queryBuilder(Class<T> entityClass) {
@SuppressWarnings("unchecked")
AbstractDao<T, ?> dao = (AbstractDao<T, ?>) getDao(entityClass);
return dao.queryBuilder();
}
public AbstractDao<?, ?> getDao(Class<? extends Object> entityClass) {
AbstractDao<?, ?> dao = entityToDao.get(entityClass);
if (dao == null) {
throw new DaoException("No DAO registered for " + entityClass);
}
return dao;
}
...
}
4、Entity
Entity本質上就是添加了@Entity註解的JavaBean,並且與資料庫對應的表建立了對映關係
5、Properties
Properties是DAOs裡的一個靜態內部類,把Entity裡的屬性封裝為Property物件,負責建立、刪除實體對應的表,真正執行SQL語句,簡而言之就是每一個Property物件對應著Entity裡第一個資料成員變數,同時也對應著資料表的的一個列元素。
public class UserDao extends AbstractDao<User, Long> {
public static final String TABLENAME = "USER";
/**
* Properties of entity User.<br/>
* Can be used for QueryBuilder and for referencing column names.
*/
public static class Properties {
public final static Property _id = new Property(0, long.class, "_id", true, "_id");
public final static Property Name = new Property(1, String.class, "name", false, "NAME");
public final static Property AvatarUrl = new Property(2, String.class, "avatarUrl", false, "AVATAR_URL");
public final static Property IsVerfyed = new Property(3, boolean.class, "isVerfyed", false, "IS_VERFYED");
}
...
}
6、Schema
Schema模式、架構的意思,其實作用和SQL Server資料庫裡的大同小異,都是為了提升資料庫自身的效能,從我們應用greenDAO方面來說,也可以不必深究,就拿SQL Server 2000來說,對應的物件命名是“伺服器.資料庫.使用者名稱.物件”,但2000之後版本的物件命名改為“伺服器.資料庫.Schema.物件”。這讓規劃資料庫物件命名時更有彈性。 架構是形成單個名稱空間的資料庫實體的集合。名稱空間是一個集合,其中每個元素的名稱都是唯一的。
三、引入並配置greeDAO
1、在Project的build.gradle指令碼下引入
必須配置:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
//新增greeDAO start
repositories {
mavenCentral()
}
dependencies {
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.0'
}
//新增greeDAO end
}
可選配置:
//與
allprojects {
repositories {
jcenter()
// 使用資料庫升級輔助GreenDaoUpgradeHelper時新增
maven { url "https://jitpack.io" }
}
}
2、配置module下的build.gradle
必須配置:
apply plugin: ‘org.greenrobot.greendao’
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testCompile 'junit:junit:4.12'
//greeDAO start
compile 'org.greenrobot:greendao:3.2.0'
// 資料庫加密時新增
compile 'net.zetetic:android-database-sqlcipher:3.5.1'
// 使用資料庫升級輔助GreenDaoUpgradeHelper時新增
compile 'com.github.yuweiguocn:GreenDaoUpgradeHelper:v1.2.0'
//greeDAO end
}
可選配置:
//與dependencies平級,主要用於配置資料庫相關資訊的及自動生成的“DAO”資源的目錄
greendao {
schemaVersion 1//資料庫版本號
daoPackage 'crazymo.src.greendao'//設定DaoMaster、DaoSession、Dao包名
targetGenDir 'src/main/java'//設定DaoMaster、DaoSession、Dao目錄
//targetGenDirTest:設定生成單元測試目錄
//generateTests:設定自動生成單元測試用例
}
完成以上配置工作之後,建立一個實體Bean,再點選build,則可以完成DAO相關資原始檔的建立。
四、使用greeDAO 3的步驟及基本術語
1、建立實體Entity
所謂實體,本質上它就是一個普通的JavaBean,特殊之處在於它被添加了greenDAO的@Entity註解來表達持久化的儲存資料模型,與SQlite資料庫中的表建立了對映關係,簡單來說,一個JavaBean的例項對應資料庫表記錄中的一行,JavaBean中的資料成員變數對應著表中的列頭值。
/**/
public class User {
private long _id;
private String name;
private String avatarUrl;
private boolean isVerfyed;
}
2、greeDAO 3 註解的基本語法
greenDAO 3是支援多種註解的,可以分為:實體@Entity註解、基本屬性註解、索引註解、關係註解。
@Entity(
// schema 名,多個 schema 時設定關聯實體。外掛產生不支援,需使用產生器
// schema = "myschema",
// 標記一個實體是否處於活動狀態,活動實體有 update、delete、refresh 方法。預設為 false
active = false,
// 表名,預設為類名
nameInDb = "AWESOME_USERS",
// 定義多列索引
indexes = {
@Index(value = "name DESC", unique = true)
},
// 標記是否建立表,預設 true。多實體對應一個表或者表已建立,不需要 greenDAO 建立時設定 false
createInDb = true,
// 是否產生所有引數構造器。預設為 true。無參構造器必定產生
generateConstructors = true,
// 如果沒有 get/set 方法,是否生成。預設為 true
generateGettersSetters = true
)
public class User {
// 主鍵,autoincrement設定自增
@Id(autoincrement = true)
private Long id;
// 唯一,預設索引
@Unique
private Long userId;
// 列名,預設使用變數名。變化:customName --> CUSTOM_NAME
@Property(nameInDb = "USERNAME")
private String name;
// 索引,unique設定唯一,name設定索引別名
@Index(unique = true)
private long fk_dogId;
// 非空
@NotNull
private String horseName;
// 忽略,不持久化,可用關鍵字transient替代
@Transient
private int tempUsageCount;
// 對一,實體屬性與外聯實體中的ID相對應。預設自動自動生成。fk和物件聯動,同時改變。物件懶載入
@ToOne(joinProperty = "fk_dogId")
private Dog dog;
// 對多,referencedJoinProperty 指定實體中與外聯實體屬性相對應的外來鍵
@ToMany(referencedJoinProperty = "fk_userId")
private List<cat> cats;
// 對多,@JoinProperty註解:name 實體中的屬性;referencedName 外聯實體中的屬性。
@ToMany(joinProperties = {
@JoinProperty(name = "horseName", referencedName = "name")
})
private List<horse> horses;
// 對多,@JoinEntity註解:entity 中間表;sourceProperty 實體屬性;targetProperty 外鏈實體屬性
@ToMany
@JoinEntity(
entity = JoinSheepToUser.class,
sourceProperty = "uId",
targetProperty = "sId"
)
private List<sheep> sheep;
}
@Entity
public class JoinSheepToUser {
@Id
private Long id;
private Long uId;
private Long sId;
}
@Entity
public class Sheep {
@Id
private Long id;
private String name;
}
2.1、實體@Entity註解
@Entity註解可以表明該類是需要持久化的類。
@Entity(
// If you want to have more than one schema, you can tell greenDAO
// to which schema an entity belongs (pick any string as a name).
schema = "myschema",
// Flag to make an entity "active": Active entities have update,
// delete, and refresh methods.
active = true,
// Specifies the name of the table in the database.
// By default, the name is based on the entities class name.
nameInDb = "AWESOME_USERS",
// Define indexes spanning multiple columns here.
indexes = {
@Index(value = "name DESC", unique = true)
},
// Flag if the DAO should create the database table (default is true).
// Set this to false, if you have multiple entities mapping to one table,
// or the table creation is done outside of greenDAO.
createInDb = false
)
-
schema:設定當前實體所屬的schema
-
active:標記一個實體處於活躍狀態,活動實體有更新、刪除和重新整理方法,預設false
-
nameInDb:在資料庫中使用的表名,預設使用的是實體的類名
-
indexes:定義索引,可以跨越多個列
-
createInDb:標記是否建立表,預設 true。當多實體對應一個表或者表已建立,或不需要 greenDAO 建立時設定 false
-
generateGettersSetters:,自動生成getter和setter預設true
-
generateConstructors:,自動生成構造方法,預設true
2.2、基本屬性註解
-
@Id:主鍵 long 型,可以通過@Id(autoincrement = true)設定自增長
-
@Property:可以自定義該屬性在資料表的列名,預設是使用欄位名,例:@Property(nameInDb = “name”)
-
@NotNull:設定資料庫表當前列不能為空
-
@Transient:不對該屬性持久化,即新增此標記後不會在資料表中建立對應的列
2.3、索引註解
-
@Index:為一個屬性來建立一個索引(即在這個屬性對應的列名上建立索引),通過name設定索引別名,也可以通過unique給索引新增約束,有name和unique兩個引數,name可以為索引指定名稱,unique與資料表中建立索引的unique含義一致
-
@Unique:向資料庫添加了一個唯一的約束
2.4、關係註解
在建立資料庫時,每一個實體類會建立一張資料表,代表一個關係,而不同實體之間必然存在一定的關係,反映到資料表上也需要建立關係。
2.4.1、@ToOne:定義與另一個實體(一個實體物件)的關係1:1
比如說在我們資料庫中存在User使用者表和Avatar表分別負責儲存使用者資訊和圖片資訊,每一條User資訊都對應這一個頭像的圖片資訊,很明顯User表和Avatar存在著1:1的關係(即所謂的外來鍵),就可以通過**@ToOne(joinProperty =** “avatarId”)來定義,那麼最終在資料表中則會有 avatarId 這一列作為外來鍵,與Picture資料表建立聯絡,如果你沒有指定 avatarId , greenDAO也會在資料表中生成一列屬性其作用與指定的avatarId相同,程式碼如下:
@Entity
public class User {
@Id
private Long id;
private String name;
private Long avatarId;//
@ToOne(joinProperty = "avatarId")
private Avatar Avatar;
....
}
@Entity
public class Avatar {
@Id
private long avatarId;
...
}
2.4.2、 @ToMany:定義與多個實體物件的關係1:N
一