1. 程式人生 > >Android ORM——greenDAO 3及使用greenDAO 3前應該掌握的一些知識點(一)

Android ORM——greenDAO 3及使用greenDAO 3前應該掌握的一些知識點(一)

引言

總所周知,SQLite——內嵌於Android中一個佔用記憶體極小的關係型,作為我們Android儲存領域中重要的一員 ,或多或少都曾接觸到資料庫。即使Android系統中提供了許多操作SQLite的API,但是在業務開發中還是需要手動去編寫原生SQL語句,這不僅複雜、不好維護,更重要的是不高效,常常會因為SQL語句寫錯而增加了開發成本,於是便出現了ORM(物件關係對映)框架,簡化了編寫SQL語句的開發工作,其中比較著名的有GreenDao、OrmLite、Litepal等。

一、greenDAO 3概述

greenDAO 3是基於註解來定義 schemasentities,而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

DaoSession莫屬,正所謂紅花還需綠葉襯,除了三大主角還包含其他角色SchemaPropertiesEntity等共同完成,接下來接單介紹下他們的功能和聯絡:
###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