安卓GreenDao 3.0使用和源碼分析

(一)GreenDao簡介

GreenDao是一個對象關系映射(ORM)的開源框架鲁僚,目前最主流的安卓數(shù)據(jù)庫操作框架。

  • 首先說說什么是對象關系映射ORM:Object Relational Mapping裁厅,是一種將對象層次結構映射成關系型結構的方法冰沙。
Object:即對象,java是一門面向對象的編程語言执虹,對開發(fā)者而言倦淀,更習慣以對象的角度看待某個問題,或
           者說通過面向對象的方式處理某個問題声畏,對java程序員來說更能理解撞叽。
Relational:關系姻成,SQLite是一種關系型數(shù)據(jù)庫,其關系模型就是指二維表格模型,因而一個關系型數(shù)據(jù)庫
           就是由二維表及其之間的聯(lián)系組成的一個數(shù)據(jù)組織愿棋,它是從數(shù)學理論發(fā)展而來的(百度百科的定
           義)科展,這種關系型數(shù)據(jù)庫與面向對象的思想是沖突的。開發(fā)人員需要時時去面對表單和數(shù)據(jù)庫的
           操作糠雨,特別是當表結構復雜時才睹,會在這些數(shù)據(jù)的處理上花費大量時間。
Mapping:映射(可以通過Map來理解)甘邀,一種對應關系琅攘,用面向對象的方式來處理關系型結構的數(shù)據(jù)庫。簡單
           的理解是一張表按照統(tǒng)一規(guī)則映射成一個java實體類松邪,對表的操作可以轉換對成開發(fā)者更熟悉
           的對實體對象的操作坞琴。
  • 通過上面的簡單分析,來說說ORM框架的優(yōu)缺點:
優(yōu)點:
     1逗抑,開發(fā)起來簡單剧辐,ORM框架將我們的對象模型轉化為SQL語句,只需要掌握一些api就能夠操作數(shù)據(jù)庫邮府,
        不用親自處理sql語句了(下面greendao和原生Sqlite開發(fā)案例可以對比)荧关。
     2,當面對一個復雜的程序時褂傀,其內部較多的數(shù)據(jù)處理忍啤,sql語句大量的硬編碼,會讓代碼顯得混亂和不
        易維護仙辟,ORM框架能讓結構更清晰同波。

缺點:
     1,雖然ORM框架開發(fā)起來簡單欺嗤,但是我們需要掌握的東西卻更多了,框架需要去學習卫枝,SQL原生操作需
        要去掌握煎饼。
     2嚎杨,在一些復雜的數(shù)據(jù)庫操作(如多表關聯(lián)查詢)時俐筋,ORM語法會變得十分復雜谈竿。直接用SQL語句會更清晰
        光羞,更直接拄丰。

  • 給出GreenDao的ORM體現(xiàn)圖:


    GreenDao的ORM體現(xiàn).png

(二)GreenDao的簡單使用

1秃励,版本說明:作為演示擦剑,本文中會用到目前最新的版本greenDao3.2.2蜡坊,更多的信息可以去github看看greenDao

2浑测,在build.grade中配置插件信息并引入依賴包

  • 2.1翅阵,聲明添加的腳本類型和設置腳本運行環(huán)境
apply plugin: 'org.greenrobot.greendao'

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
    }
}
  • 2.2歪玲,添加依賴庫
compile 'org.greenrobot:greendao:3.2.2'
  • 2.3,自定義greendao的版本和路徑
greendao {
    schemaVersion 1
    daoPackage 'com.example.android_db_biz.greedao'
    targetGenDir 'src/main/java'
}
  • 2.4掷匠,編譯項目滥崩,引入Greendao

最后給出完整的build.gradle文件,里面有詳細說明讹语,這里在編譯時會出現(xiàn)一個問題钙皮,具體解決辦法可以借鑒這篇博文greenDao3.2.2配置出現(xiàn)的問題,這里面需要下載的文件我會在結束時給出顽决。

apply plugin: 'com.android.library'

//1短条,聲明添加的插件類型
apply plugin: 'org.greenrobot.greendao'

//2,設置腳本的運行環(huán)境(如果用在app啟動模塊,直接加3,4步,在lib模塊整個需要加進去)
buildscript {

    repositories {
        //3才菠,支持java 依賴庫管理(maven/ivy),用于項目的依賴茸时。
        mavenCentral() // add repository
    }

    dependencies {
        //4,依賴包的定義鸠儿。支持maven/ivy屹蚊,遠程,本地庫进每,也支持單文件
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
    }
}

//6汹粤,自定義Greendao版本和生成路徑
greendao {
    //數(shù)據(jù)庫版本號,數(shù)據(jù)庫修改后這里一定要記得修改田晚,否則會報錯no such table
    schemaVersion 1
    //通過gradle插件生成的數(shù)據(jù)庫相關文件的包名嘱兼,默認為你的entity所在的包名
    daoPackage 'com.example.android_db_biz.greendao'
    //這就是我們上面說到的自定義生成數(shù)據(jù)庫文件的目錄了,可以將生成的文件放到我們的java目錄中
    贤徒,而不是build中芹壕,這樣就不用額外的設置資源目錄了
    targetGenDir 'src/main/java'
}

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"

    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt')
            , 'proguard-rules.pro'
        }
    }
}

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'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    //5,添加依賴庫
    compile 'org.greenrobot:greendao:3.2.2'
    testCompile 'junit:junit:4.12'
}

3接奈,創(chuàng)建一個實體類踢涌,實體類添加@Entity注解

@Entity
public class Song {

    @Id(autoincrement = true)
    private Long id;

    private String songName;

    private Integer songId;

    private String songDesc;

    private String cover;

    private Integer singerCode;

    private String singerName;

    private String createTime;

    private String updateTime;
}

這里自增長的id類型一定是Long/long類型,否則會報如下錯誤:

Error:Execution failed for task ':android-db-biz:greendao'.
> Can't add field `Variable(type=VariableType(name=int, isPrimitive=true
, originalName=int, typeArguments=null), name=id)` for entity Song due 
to: AUTOINCREMENT is only available to primary key properties of 
type long/Long

4序宦,build(Build->Make Project)項目
會自動生成一些數(shù)據(jù)庫相關類睁壁,這些類在build.gradle里設置的目錄下,而且實體類里面也會自動生成get/set方法

image.png

這里有多少個@Entity注釋的實體類就會生成多少個相關的XXXDao類互捌,XXXDao類里提供對實體類對應的表單的CRUD的操作方法潘明,即ORM里提供以面向對象的方式來處理關系型數(shù)據(jù)庫,不需要我們去寫sql語句秕噪。

5钳降,greenDao的簡單使用

  • 首先獲取操作類DaoSession,默認數(shù)據(jù)庫表存儲在內存里腌巾,看看下面的DaoSessionManager :
public class DaoSessionManager {

    private final String DB_NAME = "android.db";
    private DaoMaster daoMaster;
    private DaoSession daoSession;

    private DaoSessionManager() {
    }

    public static DaoSessionManager mInstance = new DaoSessionManager();

    public static DaoSessionManager getInstace() {

        return mInstance;
    }

    public DaoMaster getDaoMaster(Context mContext) {

        DaoMaster.DevOpenHelper mHelper = new DaoMaster
                                   .DevOpenHelper(mContext, DB_NAME, null);
        daoMaster = new DaoMaster(mHelper.getWritableDatabase());
        return daoMaster;
    }

    public DaoSession getDaoSession(Context mContext) {

        if (daoSession == null) {

            if (daoMaster == null) {
                getDaoMaster(mContext);
            }
            daoSession = daoMaster.newSession();
        }
        return daoSession;
    }
}
  • 通過DaoSession獲取上面實體類Song對應的表單操作類SongDao遂填,并進行簡單的CURD操作铲觉,具體復雜的SQL操作(如多表關聯(lián)),這里就不說明了:
//獲取Song這張表的操作類SongDao
DaoSession daoSession = DaoSessionManager.getInstace()
                                         .getDaoSession(getApplicationContext());
SongDao songDao = daoSession.getSongDao();

//創(chuàng)建一個對象
Song song = new Song();
song.setSingerCode(111);


//增加
songDao.insert(song);

//改
song.setSingerName("miss08");
songDao.update(song);

//查
Song query = songDao.queryBuilder().where(SongDao.Properties.SingerCode.eq(111))
        .list().get(0);

//刪
songDao.delete(song);

可以看出城菊,在進行增刪改查的操作并不需要我們去寫相應的sql語句备燃,只需要調用songDao的相關API就行。

6凌唬,數(shù)據(jù)庫設置存儲為本地路徑

  • 我現(xiàn)在調試用的小米手機并齐,無法root所以沒辦法看到data/data目錄下的數(shù)據(jù)情況,所有現(xiàn)在把數(shù)據(jù)庫表單存儲在手機本地目錄下客税,方便查看數(shù)據(jù)庫數(shù)據(jù)况褪,對上面的DaoSessionManager的getDaoMaster方法進行修改(記得添加文件的讀寫權限)
//本地存儲目錄
private final String DB_PATH = "AndroidDevelopment/nc/miss08/database";

public DaoMaster getDaoMaster(Context mContext, final String path) {

    DaoMaster.DevOpenHelper mHelper = new VersionChangeHelper
        (new ContextWrapper(mContext) {

        @Override
        public SQLiteDatabase openOrCreateDatabase(String name, int mode, 
                                     SQLiteDatabase.CursorFactory factory) {
            return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null);
        }

        @Override
        public SQLiteDatabase openOrCreateDatabase(String name, int mode, 
        SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
            return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null);
        }

        @Override
        public File getDatabasePath(String name) {
            File file = FileUtils.buildDataBasePath(path, name);
            return file != null ? file : super.getDatabasePath(name);
        }
    }, DB_NAME);
    daoMaster = new DaoMaster(mHelper.getWritableDatabase());
    return daoMaster;
}
  • 修改后,運行項目更耻,在指定目錄找到數(shù)據(jù)庫文件android.db:


    greendao的數(shù)據(jù)庫文件.png
  • 執(zhí)行一次insert操作后测垛,打開數(shù)據(jù)庫查看Song表數(shù)據(jù)

sqlite數(shù)據(jù)庫.png

7,數(shù)據(jù)庫的版本升級

  • 在上面基礎上秧均,不更新數(shù)據(jù)庫的表單食侮,只增加數(shù)據(jù)庫的版本號,會發(fā)現(xiàn)數(shù)據(jù)庫里所有數(shù)據(jù)被清空目胡,下面通過源碼分析下锯七。


    只修改版本信息.png

    在基本數(shù)據(jù)庫操作里,Sqlite的數(shù)據(jù)庫更新是在SQLiteOpenHelper里的onUpgrade里進行的誉己,在GreenDao框架里肯定會繼承它來定制自己框架的需求眉尸,在DaoMaster里我們找到了GreenDao用來處理版本升級的類DevOpenHelper

    /** WARNING: Drops all table on Upgrade! Use only during development. */
    public static class DevOpenHelper extends OpenHelper {
         
        ...
        @Override       //數(shù)據(jù)庫版本升級會觸發(fā)這個方法
        public void onUpgrade(Database db, int oldVersion, int newVersion) {
            dropAllTables(db, true);
            onCreate(db);
        }
    }

在其onUpgrade方法里會通過dropAllTables方法刪除項目里所有的數(shù)據(jù)庫,通過所有表單操作類XXXDao來刪除所有表單巨双。

    /** Drops underlying database table using DAOs. */
    public static void dropAllTables(Database db, boolean ifExists) {
       //項目里所有實體類操作Dao噪猾,都會在此刪除表單,當前項目里只有一張表
        SongDao.dropTable(db, ifExists);
    }

    //找到SongDao的dropTable方法筑累,就是執(zhí)行一條刪除表單的sql語句
    public static void dropTable(Database db, boolean ifExists) {
        String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"SONG\"";
        db.execSQL(sql);
    }

刪除完成后袱蜡,調用onCreate(db)方法創(chuàng)建所有表單,onCreate方法存在于DaoMaster里的OpenHelper類里慢宗,它是上面DevOpenHelper的父類

    public static abstract class OpenHelper extends DatabaseOpenHelper {
        ...
        @Override    //數(shù)據(jù)庫創(chuàng)建時觸發(fā)的方法
        public void onCreate(Database db) {
            Log.i("greenDAO"
            , "Creating tables for schema version " + SCHEMA_VERSION);
            createAllTables(db, false);
        }
    }

    //所有Dao都在此創(chuàng)建
    public static void createAllTables(Database db, boolean ifNotExists) {
       
        SongDao.createTable(db, ifNotExists);
    }

故坪蚁,GreenDao默認在版本升級時會刪除所有表單然后再創(chuàng)建,如果用戶想自己控制版本升級的情況婆廊,就需要自己實現(xiàn)OpenHelper迅细。

  • 項目每次編譯運行時巫橄,DaoMaster里的內容都會恢復成默認狀態(tài)淘邻,所以不要在DaoMaster的DevOpenHelper里進行業(yè)務操作。

  • 數(shù)據(jù)庫升級需要用戶自定義DaoMaster.OpenHelper湘换,在其onUpgrade實現(xiàn)方法里進行版本對比更新宾舅。這里借用一個開源解決辦法MigrationHelper類统阿,來完成數(shù)據(jù)庫的升級,有現(xiàn)成的我們就直接拿來用筹我,里面代碼也挺清晰的扶平,如果不想用也可以自己通過DaoMaster來處理,或者更直接的使用sql語句來處理蔬蕊。

  • MigrationHelper在我的demo里與greenDao不兼容结澄,故直接把源碼拿來用,其github目錄為MigrationHelper解決greenDao版本升級岸夯,大家可以直接去這里找符合你greenDao的版本麻献,這里先給出用法,后面會進行分析猜扮。

通過MigrationHelper進行版本升級
  • 1勉吻,還是結合上面的例子,我們刪除之前的本地數(shù)據(jù)庫目錄旅赢,并在之前的表單基礎上再添加一張歌單表MenuInfo齿桃,這樣項目中就存在兩張表。
@Entity
public class MenuInfo {

    @Id(autoincrement = true)
    private Long id;

    private String menuCode;
    
    private String menuName;

    private String createTime;

    private String updateTime;
}
  • 2煮盼,build項目短纵,在兩張表里都添加兩條數(shù)據(jù)。此時數(shù)據(jù)庫的schemaVersion版本號為1


    menuInfo表創(chuàng)建.png

    song表創(chuàng)建.png
  • 3孕似,如果我們在下個版本需要對MenuInfo的字段做在線更改踩娘,增加一個menuDesc字段,改為如下:

public class MenuInfo {

    @Id(autoincrement = true)
    private Long id;

    private String menuCode;
    
    private String menuName;

    //增加的字段
    private String menuDesc;

    private String createTime;

    private String updateTime;
}
  • 4喉祭,版本升級养渴,自定義DaoMaster.OpenHelper,然后在build.gradle里將schemaVersion版本改為2
public class VersionChangeHelper extends DaoMaster.DevOpenHelper {

    public VersionChangeHelper(Context context, String name) {
        this(context, name, null);
    }

    public VersionChangeHelper(Context context, String name, SQLiteDatabase
        .CursorFactory factory) {
        super(context, name, factory);
    }

    @Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) {
        super.onUpgrade(db, oldVersion, newVersion);
        Log.e("miss08", "oldVersion = " + oldVersion + "newVersion = " + newVersion);
        MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {

            @Override
            public void onCreateAllTables(Database db, boolean ifNotExists) {
                Log.e("dongyiming", "onCreateAllTables");
                //DaoMaster.createAllTables(db, ifNotExists);
            }

            @Override
            public void onDropAllTables(Database db, boolean ifExists) {
                Log.e("dongyiming", "onDropAllTables");
                //DaoMaster.dropAllTables(db, ifExists);
            }
        }, MenuInfoDao.class);
        }
    }
}

同時修改其引用的地方DaoSessionManager的getDaoMaster方法:
DevOpenHelper mHelper = new VersionChangeHelper(new ContextWrapper(mContext)
  • 5泛烙,運行項目理卑,查看日志和數(shù)據(jù)庫如下:
版本改變后,VersionChangeHelper的update方法被調用

10-08 18:22:07.797 22536-22536/com.example.pver.androiddevelopment E/miss08: 
oldVersion = 1_newVersion = 2

表單信息為:

menuInfo表修改數(shù)據(jù).png

song表沒有更新.png

發(fā)現(xiàn)兩張表都是空表蔽氨,這是因為我們在上面onUpgrade方法里super.onUpgrade(db, oldVersion, newVersion)會先執(zhí)行父類DaoMaster.DevOpenHelper里的刪除所有表單數(shù)據(jù)的方法藐唠,這個上面有分析,現(xiàn)在我們去掉super.onUpgrade后鹉究,再假設重復上面的操作宇立,就只有我們做過更新的MenuInfo表為空,其他表單還是有原始的數(shù)據(jù)自赔。

menuInfo表修改數(shù)據(jù).png
song表沒有更新.png
MigrationHelper的簡單分析
  • 先看看MigrationHelper的migrate方法妈嘹,它有三個重載方法,我們使用的那個方法有三個參數(shù)绍妨,分別為database润脸,一個ReCreateAllTableListener回調柬脸,一個繼承AbstractDao的class對象的可變參數(shù)類型。
    public static void migrate(SQLiteDatabase db
                         , Class<? extends AbstractDao<?, ?>>... daoClasses) {
        printLog("【The Old Database Version】" + db.getVersion());
        Database database = new StandardDatabase(db);
        migrate(database, daoClasses);
    }

    public static void migrate(Database database
                         , ReCreateAllTableListener listener
                         , Class<? extends AbstractDao<?, ?>>... daoClasses) {
        weakListener = new WeakReference<>(listener);
        migrate(database, daoClasses);
    }

AbstractDao作為所有表單操作類(如MenuInfoDao)的父類毙驯,項目中有多少表單在當前版本進行了修改倒堕,我們調用migrate方法,就需要傳多少進去爆价。

MigrationHelper.migrate(db垦巴,listener,xxDao.class铭段,xxDao.class魂那,xxDao.class...);

也可調用沒有l(wèi)istener的migrate方法,即不用回調給我們來處理數(shù)據(jù)庫稠项,其使用一樣:

MigrationHelper.migrate(db涯雅,xxDao.class,xxDao.class展运,xxDao.class...);

  • 在上面帶listener的migrate方法里活逆,首先用弱引用指向listener,后調用migrate另一個重載方法拗胜,傳入database和修改的表單蔗候,方法如下。
    public static void migrate(Database database
             , Class<? extends AbstractDao<?, ?>>... daoClasses) {
        //輪詢daoClasses埂软,看對應的表單是否存在锈遥,并創(chuàng)建臨時表
        generateTempTables(database, daoClasses);
        //獲取接口對象,在最初調用時會new WeakReference()勘畔,所以這里的listener不為空 
        ReCreateAllTableListener listener = weakListener.get();
        if (listener != null) {
            //執(zhí)行回調
            listener.onDropAllTables(database, true);
            printLog("【Drop all table by listener】");
            listener.onCreateAllTables(database, false);
            printLog("【Create all table by listener】");
        } else {
            dropAllTables(database, true, daoClasses);
            createAllTables(database, false, daoClasses);
        }
        printLog("【Restore data】start");
        restoreData(database, daoClasses);
        printLog("【Restore data】complete");
    }
  • 當 listener = null 時所灸,會刪除傳入的Dao對應的那張表單,然后又重新創(chuàng)建這張表單炫七,如果有新的數(shù)據(jù)就會存入更新后的表單里爬立。

  • 當 listener != null 時,會通過listener回調給用戶自己處理万哪,更加的靈活侠驯。

  • 看看listener = null時刪除和重新創(chuàng)建的方法,不管是創(chuàng)建還是刪除最終會調用如下方法:

    private static void reflectMethod(Database db, String methodName, boolean 
    isExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
        if (daoClasses.length < 1) {
            return;
        }
        try {
            for (Class cls : daoClasses) {
                Method method = cls.getDeclaredMethod(methodName, Database.class
                , boolean.class);
                method.invoke(null, db, isExists);
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

reflectMethod方法里奕巍,會對daoClasses進行輪詢吟策,通過反射獲取我們我們傳入的daoClasses(本例中只有一個為MenuInfoDao)里的名為methodName的方法對象,然后通過invoke執(zhí)行MenuInfoDao類中的該方法的止,展示我們需要調用的兩個方法dropTable和createTable檩坚。


    //傳入的false
    /** Creates the underlying database table. */
    public static void createTable(Database db, boolean ifNotExists) {
        String constraint = ifNotExists? "IF NOT EXISTS ": "";
        db.execSQL("CREATE TABLE " + constraint + "\"MENU_INFO\" (" + //
                "\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id
                "\"MENU_CODE\" TEXT," + // 1: menuCode
                "\"MENU_NAME\" TEXT," + // 2: menuName
                "\"MENU_DESC\" TEXT," + // 3: menuDesc
                "\"CREATE_TIME\" TEXT," + // 4: createTime
                "\"UPDATE_TIME\" TEXT);"); // 5: updateTime
    }

    //傳入的true
    /** Drops the underlying database table. */
    public static void dropTable(Database db, boolean ifExists) {
        String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") 
                     + "\"MENU_INFO\"";
        db.execSQL(sql);
    }

這里會銷毀之前的那張表然后重新創(chuàng)建一張新表,所以表里原有數(shù)據(jù)是不會存在的,這里只有傳入的XXXDao.class做刪除和重建的工作效床,其他表單是不會受影響的,有回調的就得看用戶自己的需求了权谁。

上面的例子演示了MigrationHelper對一個/多個表字段的更新(增刪都一樣)剩檀,現(xiàn)在我們來演示在線增加一張/多張表。
  • 首先在項目里添加一張表Singer旺芽,然后build項目沪猴,現(xiàn)在整個項目中就存在三張表
@Entity
public class Singer {

    @Id(autoincrement = true)
    private Long id;

    private String singerCode;
    
    private String singerName;

}
  • 數(shù)據(jù)庫版本加1,修改VersionChangeHelper方法如下采章,這里演示沒有l(wèi)istener回調的方法运嗜,然后運行項目
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
    super.onUpgrade(db, oldVersion, newVersion);
    Log.e("miss08", "oldVersion = " + oldVersion + "_newVersion = " + newVersion);

    MigrationHelper.migrate(db, SingerDao.class);
}
  • 項目運行時會出現(xiàn)一個問題,MigrationHelper里migrate對listener的非空判斷悯舟,這里修改一下然后重新運行
public static void migrate(Database database
              , Class<? extends AbstractDao<?, ?>>... daoClasses) {
    printLog("【Generate temp table】start");
    generateTempTables(database, daoClasses);
    printLog("【Generate temp table】complete");
    //添加非空判斷
    ReCreateAllTableListener listener = null;
    if (weakListener != null) {
        listener = weakListener.get();
    }
    if (listener != null) {
        ...省略
    }
}
  • 項目運行成功后担租,發(fā)現(xiàn)確實出現(xiàn)了三張表,這里只展示第三張表:


    數(shù)據(jù)庫里所有表單.png

    singer表創(chuàng)建.png
最后說說更新版本時刪除表操作
  • 實現(xiàn)方式挺多的抵怎,提供幾種建議(前提是自己的helper里不要有super父類更新方法)奋救,這里我只用第一種方案試過并成功,其他幾種就不演示了反惕。
1,不使用MigrationHelper直接通過SingerDao里的dropTable(db, true)方法完成
2,不使用MigrationHelper直接通過sql語句完成
3,使用MigrationHelper尝艘,對里面代碼簡單修改下,通過reflectMethod來處理傳入的daoClasses

(三)GreenDao源碼的簡單分析

說明:

要查看具體源碼姿染,可以到github下載greenDao背亥,里面有它的源碼和操作demo。

  • 1悬赏,用戶可操作的幾個類的關系圖如下:


    GreenDao類圖.png
  • 2狡汉,DaoGenerator:通過給定的模板Schema和路徑生成相應的實體類和Dao相關的類。Schema的關系圖如下:
Schema.png

Schema的操作方式如下闽颇,包含了數(shù)據(jù)庫的相關信息:

//版本和包名
Schema schema = new Schema(1, "org.greenrobot.greendao.daotest2");
Entity keepEntity = schema2.addEntity("KeepEntity");
keepEntity .addIdProperty();
keepEntity .addStringProperty("count");
keepEntity .addStringProperty("select");
keepEntity .addStringProperty("sum");
keepEntity .addStringProperty("avg");
keepEntity .addStringProperty("join");

再來看看DaoGenerator里生成實體類和Daos的方法generateAll

/** Generates all entities and DAOs for the given schema. */
public void generateAll(Schema schema, String outDir, String outDirEntity
, String outDirTest) throws Exception {

    List<Entity> entities = schema.getEntities();
    for (Entity entity : entities) {
        generate(templateDao, outDirFile, entity.getJavaPackageDao()
           , entity.getClassNameDao(), schema, entity);
    }
    generate(templateDaoMaster, outDirFile, schema.getDefaultJavaPackageDao(),
            schema.getPrefix() + "DaoMaster", schema, null);
    generate(templateDaoSession, outDirFile, schema.getDefaultJavaPackageDao(),
            schema.getPrefix() + "DaoSession", schema, null);
}
  • 3轴猎,DaoMaster:插件生成的daos的最頂層,可以看它的注釋(如下)

Master of DAO (schema version 16): knows all DAOs.

包含項目所有表單的創(chuàng)建工作createAllTables和升級處理(先dropAllTables再createAllTables)

public static void createAllTables(Database db, boolean ifNotExists) {
    SingerDao.createTable(db, ifNotExists);
    MenuInfoDao.createTable(db, ifNotExists);
    SongDao.createTable(db, ifNotExists);
}

public static void dropAllTables(Database db, boolean ifExists) {
    SingerDao.dropTable(db, ifExists);
    MenuInfoDao.dropTable(db, ifExists);
    SongDao.dropTable(db, ifExists);
}

還有DaoSession的創(chuàng)建方法进萄,創(chuàng)建時會傳入daoConfigMap捻脖,這個daoConfigMap是項目所有表單操作類XXXDao的class對象緩存,它是在創(chuàng)建DaoMaster時創(chuàng)建和存入class的中鼠。

public DaoSession newSession() {
    return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
}

看看DaoMaster的構造方法可婶,通過registerDaoClass方法把當前項目里所有的XXXDao的class對象緩存起來(這里不是實例對象,是class對象援雇,一般用來搞反射)矛渴。

public DaoMaster(SQLiteDatabase db) {
    this(new StandardDatabase(db));
}

public DaoMaster(Database db) {
    super(db, SCHEMA_VERSION);
    registerDaoClass(SingerDao.class);
    registerDaoClass(MenuInfoDao.class);
    registerDaoClass(SongDao.class);
}

在DaoMaster的父類中AbstractDaoMaster緩存起來,
protected final Map<Class<? extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap;

protected void registerDaoClass(Class<? extends AbstractDao<?, ?>> daoClass) {
    DaoConfig daoConfig = new DaoConfig(db, daoClass);
    daoConfigMap.put(daoClass, daoConfig);
}

registerDaoClass方法緩存數(shù)據(jù)前,會創(chuàng)建一個DaoConfig具温,DaoConfig里面存儲了很多Dao的基本數(shù)據(jù)蚕涤,如下

    public final Database db;
    public final String tablename;
    public final Property[] properties;     

    public final String[] allColumns;
    public final String[] pkColumns;
    public final String[] nonPkColumns;

我們再看看DaoConfig的這個構造方法,通過reflectProperties方法獲取到Property數(shù)組铣猩。Property里的數(shù)據(jù)描述了映射到數(shù)據(jù)庫里列的屬性揖铜。用于創(chuàng)建查詢構建器使用的對象(包含所有select查詢用到的條件操作)。

public DaoConfig(Database db, Class<? extends AbstractDao<?, ?>> daoClass) {
    this.db = db;
    try {
        this.tablename = (String) daoClass.getField("TABLENAME").get(null);
        Property[] properties = reflectProperties(daoClass);

        allColumns = new String[properties.length];

        List<String> pkColumnList = new ArrayList<String>();
        List<String> nonPkColumnList = new ArrayList<String>();
        Property lastPkProperty = null;
        for (int i = 0; i < properties.length; i++) {
            Property property = properties[i];
            String name = property.columnName;
            allColumns[i] = name;
            if (property.primaryKey) {
                pkColumnList.add(name);
                lastPkProperty = property;
            } else {
                nonPkColumnList.add(name);
            }
        }
    }
}

reflectProperties這個方法里达皿,通過反射獲取AbstractDao類的內部類Properties里所有的靜態(tài)字段和public字段天吓,然后再通過field.get(null)獲取所有字段的屬性值,存儲這些屬性值峦椰。

private static Property[] reflectProperties(Class<? extends AbstractDao<?, ?>> 
daoClass) throws ClassNotFoundException, IllegalArgumentException
, IllegalAccessException {
    Class<?> propertiesClass = Class.forName(daoClass.getName() + "$Properties");
    Field[] fields = propertiesClass.getDeclaredFields();

    ArrayList<Property> propertyList = new ArrayList<Property>();
    final int modifierMask = Modifier.STATIC | Modifier.PUBLIC;
    for (Field field : fields) {
        if ((field.getModifiers() & modifierMask) == modifierMask) {
            Object fieldValue = field.get(null);
            if (fieldValue instanceof Property) {
                propertyList.add((Property) fieldValue);
            }
        }
    }
}

以SingerDao為例龄寞,看看其內部類的字段和屬性:屬性值就是一個Property對象,包含實體類的字段名汤功,對應的數(shù)據(jù)庫里的列名物邑,以及類型

public class SingerDao extends AbstractDao<Singer, Long> {
    public static class Properties {
        public final static Property Id = new Property(0, Long.class
                              , "id", true, "_id");
        public final static Property SingerCode = new Property(1, String.class
                              , "singerCode", false, "SINGER_CODE");
        public final static Property SingerName = new Property(2, String.class
                              , "singerName", false, "SINGER_NAME");
        public final static Property SingerDesc = new Property(3, String.class
                              , "singerDesc", false, "SINGER_DESC");
    }
}
  • 4,DaoSession:在構造里創(chuàng)建出XXXDao的實例對象滔金。
public DaoSession(Database db, IdentityScopeType type, Map<Class<?
        extends AbstractDao<?, ?>>, DaoConfig> daoConfigMap) {
    super(db);
    singerDaoConfig = daoConfigMap.get(SingerDao.class).clone();
    singerDaoConfig.initIdentityScope(type);

    menuInfoDaoConfig = daoConfigMap.get(MenuInfoDao.class).clone();
    menuInfoDaoConfig.initIdentityScope(type);

    songDaoConfig = daoConfigMap.get(SongDao.class).clone();
    songDaoConfig.initIdentityScope(type);

    singerDao = new SingerDao(singerDaoConfig, this);
    menuInfoDao = new MenuInfoDao(menuInfoDaoConfig, this);
    songDao = new SongDao(songDaoConfig, this);

    registerDao(Singer.class, singerDao);
    registerDao(MenuInfo.class, menuInfoDao);
    registerDao(Song.class, songDao);
}

構造方法里拂封,首先從傳入的daoConfigMap里獲取DaoConfig,通過原型模式創(chuàng)建DaoConfig對象鹦蠕,然后創(chuàng)建XXXDao的實例冒签,DaoConfig對象做為Dao的構參傳入,最后在DaoSession的父類里用Map<Class<?>, AbstractDao<?, ?>>集合緩存實例類和它的操作類對象钟病。
接下來看看DaoSession的父類AbstractDaoSession萧恕,在AbstractDaoSession提供基本增刪改查的方法,但是數(shù)據(jù)庫真正的執(zhí)行者確是AbstractDao肠阱,從上面的緩存里獲取的AbstractDao票唆。

    public <T> long insert(T entity) {
        @SuppressWarnings("unchecked")
        AbstractDao<T, ?> dao = (AbstractDao<T, ?>) getDao(entity.getClass());
        return dao.insert(entity);
    }

    public <T> long insertOrReplace(T entity) {
        @SuppressWarnings("unchecked")
        AbstractDao<T, ?> dao = (AbstractDao<T, ?>) getDao(entity.getClass());
        return dao.insertOrReplace(entity);
    }

    public <T> void update(T entity) {
        @SuppressWarnings("unchecked")
        AbstractDao<T, ?> dao = (AbstractDao<T, ?>) getDao(entity.getClass());
        dao.update(entity);
    }

    public <T> void delete(T entity) {
        @SuppressWarnings("unchecked")
        AbstractDao<T, ?> dao = (AbstractDao<T, ?>) getDao(entity.getClass());
        dao.delete(entity);
    }

    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);
    }

    public <T> QueryBuilder<T> queryBuilder(Class<T> entityClass) {
        @SuppressWarnings("unchecked")
        AbstractDao<T, ?> dao = (AbstractDao<T, ?>) getDao(entityClass);
        return dao.queryBuilder();
    }

還有支持數(shù)據(jù)庫的Rx方式(沒用過,這里不分析)

    @Experimental
    public RxTransaction rxTx() {
        if (rxTxIo == null) {
            rxTxIo = new RxTransaction(this, Schedulers.io());
        }
        return rxTxIo;
    }

    @Experimental
    public RxTransaction rxTxPlain() {
        if (rxTxPlain == null) {
            rxTxPlain = new RxTransaction(this);
        }
        return rxTxPlain;
    }

接下來看看數(shù)據(jù)庫的真實操作類XXXDao

  • 5屹徘,XXXDao:數(shù)據(jù)庫提供的真實操作類:提供所有數(shù)據(jù)庫的操作方式走趋。這里就只通過查詢語句來分析一下它的操作過程(以Singer表為例)。

GreenDao查詢語句
singerDao.queryBuilder().where(Properties.SingerCode.eq("111")).orderAsc(Properties.SINGER_NAME).list();

對比下sqlite的查詢的兩種方式

1噪伊,db.rawQuery(sql)
   select * from SINGER where SINGER_CODE = ‘111’ order by SINGER_NAME asc簿煌;

2,db.query(...)
   query(String table, String[] columns, String selection,String[] selectionArgs
                    , String groupBy, String having,String orderBy, String limit)
   
   query("Singer",null,"SINGER_CODE = ?",new String[]{"111"},"null","null"
                    ,"SINGER_NAME asc")

下面分析GreenDao的查詢鉴吹,在XXXDao的父類AbstractDao里找到queryBuilder方法姨伟,該方法里會創(chuàng)建一個QueryBuilder對象,我們來看看QueryBuilder的描述:

Builds custom entity queries using constraints and parameters and without SQL 
(QueryBuilder creates SQL for you)

使用約束條件和參數(shù)來構造實體對象的查詢豆励,而不使用SQL語句(QueryBuilder 為我們生成SQL語句)

QueryBuilder的where方法,使用一個類WhereCondition來收集所有的條件,這個在下面的build方法里會被用到技扼。

public QueryBuilder<T> where(WhereCondition cond, WhereCondition... condMore) {
    whereCollector.add(cond, condMore);
    return this;
}

接著看QueryBuilder類的orderAsc方法办斑,實際會調用下面這個方法挣柬,分析見里面注釋。

private void orderAscOrDesc(String ascOrDescWithLeadingSpace, Property... properties) 
{
    for (Property property : properties) {

        //獲取StringBuilder對象拼接字符串睛挚,沒有就創(chuàng)建邪蛔,存在就append(",")
        checkOrderBuilder();
        //首先判斷排序的字段屬于Singer表里字段,然后拼裝該columnName
        append(orderBuilder, property);
        //字段是String類型并且stringOrderCollation不為空扎狱,
        if (String.class.equals(property.type) && stringOrderCollation != null) {
            //拼接stringOrderCollation =  " COLLATE NOCASE"
            orderBuilder.append(stringOrderCollation);
        }
        //拼裝ASC
        orderBuilder.append(ascOrDescWithLeadingSpace);
    }
}

最終會創(chuàng)建一個orderBuilder侧到,里面包含“SINGER_NAME asc”

最后看QueryBuilder類的list方法,list方法里先執(zhí)行build方法淤击,然后執(zhí)行build方法返回對象的list方法床牧。

public List<T> list() {
    return build().list();
}

先找到build方法,build里會做很多拼接工作遭贸。

public Query<T> build() {

    //創(chuàng)建一個StringBuilder對象戈咳,拼接工作下面分析
    StringBuilder builder = createSelectBuilder();

    //判斷是否有LIMIT和OFFSET條件,如果有則拼接上
    int limitPosition = checkAddLimit(builder);
    int offsetPosition = checkAddOffset(builder);

    String sql = builder.toString();
    checkLog(sql);

    //返回一個Query對象,查詢操作返回的結果(實體類或游標)著蛙,values是position的值
    return Query.create(dao, sql, values.toArray(), limitPosition, offsetPosition);
}

重點看看上面build方法里執(zhí)行的createSelectBuilder方法删铃,他會拼接返回完整的用于查詢條件的select語句

private StringBuilder createSelectBuilder() {

    //創(chuàng)建一個StringBuilder,拼接“SELECT FROM tablename”
    String select = SqlUtils.createSqlSelect(dao.getTablename(), tablePrefix
    , dao.getAllColumns(), distinct);
    StringBuilder builder = new StringBuilder(select);

    //如果上面的whereCollector條件集合有值踏堡,拼接“WHERE”和條件
    appendJoinsAndWheres(builder, tablePrefix);

    //orderBuilder如果存在則拼接“ORDER BY ”和orderBuilder,這個在上面分析過
    if (orderBuilder != null && orderBuilder.length() > 0) {
        builder.append(" ORDER BY ").append(orderBuilder);
    }

    //返回最終拼裝的StringBuilder
    return builder;
}

再看最后的list方法調用猎唁,這個方法在類Query里,找到了我們最熟悉的Sqlite操作dao.getDatabase().rawQuery顷蟆,查詢傳入我們上面拼接的sql語句和傳入的參數(shù)值诫隅,得到游標,輪詢就能返回結果帐偎。

public List<T> list() {
    checkThread();
    Cursor cursor = dao.getDatabase().rawQuery(sql, parameters);
    return daoAccess.loadAllAndCloseCursor(cursor);
}

最后說明

在分析完GreenDao的查詢流程后逐纬,發(fā)現(xiàn)XXXDao對數(shù)據(jù)庫的操作最終還是Sqlite里對sql語句的操作。體現(xiàn)了ORM面向對象操作和關系數(shù)據(jù)表操作的映射削樊,所以要想熟練使用GreenDao對數(shù)據(jù)庫的操作豁生,必須得先掌握SQLite里對數(shù)據(jù)庫的操作和常用的SQL語句的書寫。

(四)Sqlite數(shù)據(jù)庫的使用案例:

下面這個案例是之前的公司項目在迭代Orimlite框架前漫贞,對數(shù)據(jù)庫的操作甸箱,對比下GreenDao的使用方式,這里就直接給出代碼迅脐,就不做多余的分析芍殖,最終的demo里也會給出完整代碼。

  • 1谴蔑,創(chuàng)建基類
public class Singer {

    private int id;

    private String singerCode;

    private String singerName;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getSingerCode() {
        return singerCode;
    }

    public void setSingerCode(String singerCode) {
        this.singerCode = singerCode;
    }

    public String getSingerName() {
        return singerName;
    }

    public void setSingerName(String singerName) {
        this.singerName = singerName;
    }
}
  • 2围小,自定義SqliteOpenHelper,設置版本號树碱,數(shù)據(jù)庫名稱肯适,完成初始化表格的創(chuàng)建和更新的操作
public class DBHelper extends SQLiteOpenHelper {

    //數(shù)據(jù)庫名
    private static final String DB_NAME = "android2.db";
    //版本號
    private static final int VERSION_CODE = 1;
    //創(chuàng)建table的sql語句
    private static final String SQL_CREATE_TABLE = String.format
                    ("create table %s ( " +
                    "%s integer primary key autoincrement" +
                    ",%s text" +
                    ",%s text)"
            , ISingerRepository.TABLE
            , ISingerRepository.ID
            , ISingerRepository.SINGER_CODE
            , ISingerRepository.SINGER_NAME);

    public DBHelper(Context mContext) {
        super(mContext, DB_NAME, null, VERSION_CODE);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

        //增加一列
        String sql = String.format("alter table %s add %s text", ISingerRepository
              .TABLE, ISingerRepository.SINGER_DESC);
        db.execSQL(sql);
    }
}
  • 3,把表單存儲到sd卡里成榜,需要自定義ContextWrapper框舔,在構建helper時傳入這個context即可
public class DataBaseContext extends ContextWrapper {

    private static final String DB_PATH = "AndroidDevelopment/nc/miss08/database";

    public DataBaseContext(Context base) {
        super(base);
    }

    @Override
    public SQLiteDatabase openOrCreateDatabase(String name, int mode
                            , SQLiteDatabase.CursorFactory factory) {
        return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null);
    }

    @Override
    public SQLiteDatabase openOrCreateDatabase(String name, int mode
    , SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
        return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null);
    }

    @Override
    public File getDatabasePath(String name) {

        File file = FileUtils.buildDataBasePath(DB_PATH, name);
        return file != null ? file : super.getDatabasePath(name);
    }
}
  • 4,把數(shù)據(jù)庫操作的一些公共方法放到一個BaseRepository中
public class BaseRepository<T> {

    public final DBHelper dbHelper;

    public BaseRepository(Context mContext) {
        DataBaseContext context = new DataBaseContext(mContext);
        this.dbHelper = new DBHelper(context);
    }

    public long insert(String table, String nullColumnHack, ContentValues values) {
        long ret = 0L;
        SQLiteDatabase database = this.dbHelper.getWritableDatabase();
        database.beginTransaction();

        try {
            ret = database.insert(table, nullColumnHack, values);
            database.setTransactionSuccessful();
        } catch (RuntimeException var11) {
            Log.e("miss08", "exception : " + var11);
        } finally {
            database.endTransaction();
        }

        return ret;
    }

    public <T> List<T> query(String table, String[] columns, String selection
      , String[] selectionArgs, String groupBy, String having, String orderBy
      , Integer limit) {
        Object results = new ArrayList();
        Cursor cursor = null;

        try {
            if (limit != null) {
                cursor = this.dbHelper.getReadableDatabase().query(table, columns
              , selection , selectionArgs, groupBy, having, orderBy, limit + "");
            } else {
                cursor = this.dbHelper.getReadableDatabase().query(table, columns
                          , selection, selectionArgs, groupBy, having, orderBy);
            }

            results = this.queryResult(cursor);
        } catch (RuntimeException var15) {
            Log.e("miss08", "exception : " + var15);
        } finally {
            if (cursor != null) {
                cursor.close();
            }

        }

        return (List) results;
    }

    public <T> List<T> query(String table, String[] columns, String selection
    , String[] selectionArgs, String groupBy, String having, String orderBy) {
        return this.query(table, columns, selection, selectionArgs, groupBy, having
       , orderBy, (Integer) null);
    }

    public <T> List<T> queryResult(Cursor cursor) {
        throw new RuntimeException("Please overwrite method.");
    }

    public int update(String table, ContentValues values, String whereClause
                           , String[] whereArgs) {
        int ret = 0;
        SQLiteDatabase database = this.dbHelper.getWritableDatabase();
        database.beginTransaction();

        try {
            ret = database.update(table, values, whereClause, whereArgs);
            database.setTransactionSuccessful();
        } catch (RuntimeException var11) {
            Log.e("miss08", "exception : " + var11);
        } finally {
            database.endTransaction();
        }

        return ret;
    }

    public int delete(String table, String whereClause, String[] whereArgs) {
        int ret = 0;
        SQLiteDatabase database = this.dbHelper.getWritableDatabase();
        database.beginTransaction();

        try {
            ret = database.delete(table, whereClause, whereArgs);
            database.setTransactionSuccessful();
        } catch (RuntimeException var10) {
            Log.e("miss08", "exception : " + var10);
        } finally {
            database.endTransaction();
        }

        return ret;
    }
}
  • 5赎婚,創(chuàng)建Singer表的操作類SingerRepository
public class SingerRepository extends BaseRepository<Singer> 
                         implements ISingerRepository<Singer, Integer> {

    public SingerRepository(Context mContext) {
        super(mContext);
    }

    @Override
    public long add(Singer singer) {

        ContentValues cv = getContentValues(singer);
        long res = insert(TABLE, null, cv);
        return res;
    }

    @Override
    public int update(Singer singer) {
        ContentValues cv = getContentValues(singer);
        String whereClause = String.format("%s = ?", SINGER_CODE);
        update(TABLE, cv, whereClause, new String[]{singer.getSingerCode()});
        return 0;
    }

    @Override
    public Singer queryById(Integer id) {

        String sql = String.format("%s = ?", ID);
        List<Singer> singerList = query(TABLE, null, sql
                , new String[]{String.valueOf(id)}, null, null, null);
        if (singerList != null) {
            return singerList.get(0);
        }
        return null;
    }

    @Override
    public List<Singer> queryForAll() {
        return query(TABLE, null, null, null, null, null, null);
    }

    @Override
    public int delete(Integer id) {

        String sql = String.format("delete from %s where %s = ?", TABLE, ID);
        dbHelper.getWritableDatabase()
                .execSQL(sql, new String[]{String.valueOf(id)});
        return 0;
    }

    public ContentValues getContentValues(Singer singer) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(SINGER_CODE, singer.getSingerCode());
        contentValues.put(SINGER_NAME, singer.getSingerName());
        return contentValues;
    }

    //查詢需要對cursor遍歷刘绣,父類需要的方法
    public List<Singer> queryResult(Cursor cursor) {
        List<Singer> list = new ArrayList<>();
        while (cursor.moveToNext()) {
            Singer singer = new Singer();
            singer.setId(cursor.getInt(cursor.getColumnIndex(ID)));
            singer.setSingerName(
                       cursor.getString(cursor.getColumnIndex(SINGER_NAME)));
            singer.setSingerCode(
                       cursor.getString(cursor.getColumnIndex(SINGER_CODE)));
            list.add(singer);
        }
        return list;
    }
}
  • 6,在接口里統(tǒng)一Singer表的名稱和字段以及方法
public interface ISingerRepository<T, ID> {

    public static final String TABLE = "singer";
    public static final String COLUMN_PREFIX = TABLE.concat("_");
    public static final String ID = "_id";
    public static final String SINGER_CODE = COLUMN_PREFIX + "code";
    public static final String SINGER_NAME = COLUMN_PREFIX + "name";
    public static final String SINGER_DESC = COLUMN_PREFIX + "desc";

    //只給出簡單的增刪改查
    public long add(T var1);

    public int update(T var1);

    T queryById(ID var1);

    List<T> queryForAll();

    int delete(ID var1);
}
  • 7挣输,在MainActivity里操作數(shù)據(jù)庫
SingerRepository singerRepository = new SingerRepository(this);
Singer singer = new Singer();
singer.setSingerCode("112");
singer.setSingerName("張三");
singerRepository.add(singer);
  • 8纬凤,上面Demo自測表的創(chuàng)建成功,而且對表的所有操作方法都通過驗證撩嚼。

總結:

上面就是所有對GreenDao的簡單分析停士,由于公司使用的是ORMLite框架挖帘,所以GreenDao里比較復雜的一些操作(多表關聯(lián)/事物的處理/版本跳級更新/融合Rxjava等)這里都沒演示,一是使用不熟練恋技,二是目前確實沒時間慢慢弄拇舀,文章里所有demo都是自己操作成功的,如果有什么其他問題和建議可留言蜻底。

其他:

本文所有demo的github地址(gradle-3.5-all在項目根目錄下骄崩,解壓使用):GreenDao和SQLite的使用

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市薄辅,隨后出現(xiàn)的幾起案子要拂,更是在濱河造成了極大的恐慌,老刑警劉巖站楚,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脱惰,死亡現(xiàn)場離奇詭異,居然都是意外死亡源请,警方通過查閱死者的電腦和手機枪芒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進店門彻况,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谁尸,“玉大人,你說我怎么就攤上這事纽甘×悸” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵悍赢,是天一觀的道長决瞳。 經常有香客問我,道長左权,這世上最難降的妖魔是什么皮胡? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮赏迟,結果婚禮上屡贺,老公的妹妹穿的比我還像新娘。我一直安慰自己锌杀,他們只是感情好,可當我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哈踱,像睡著了一般艘款。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上突想,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天殴蹄,我揣著相機與錄音究抓,去河邊找鬼。 笑死饶套,一個胖子當著我的面吹牛漩蟆,可吹牛的內容都是我干的。 我是一名探鬼主播妓蛮,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼怠李,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蛤克?” 一聲冷哼從身側響起捺癞,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎构挤,沒想到半個月后髓介,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡筋现,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年唐础,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矾飞。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡一膨,死狀恐怖,靈堂內的尸體忽然破棺而出洒沦,到底是詐尸還是另有隱情豹绪,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布申眼,位于F島的核電站瞒津,受9級特大地震影響,放射性物質發(fā)生泄漏括尸。R本人自食惡果不足惜巷蚪,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望濒翻。 院中可真熱鬧屁柏,春花似錦、人聲如沸肴焊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娶眷。三九已至似嗤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間届宠,已是汗流浹背烁落。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工乘粒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伤塌。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓灯萍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親每聪。 傳聞我的和親對象是個殘疾皇子旦棉,可洞房花燭夜當晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)药薯,斷路器绑洛,智...
    卡卡羅2017閱讀 134,633評論 18 139
  • 一、關于greenDAO greenDAO應該算是當前最火的數(shù)據(jù)庫開源框架了童本,它是一個將對象映射到SQLite數(shù)據(jù)...
    當幸福來敲門58閱讀 13,862評論 3 19
  • 序言 (寫分享前的序言)大家好.很高興能為大家分享Android之GreenDao筆記,也希望能和大家一起學習進步...
    王黎聰閱讀 1,088評論 4 11
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,770評論 25 707
  • GreenDao 介紹:greenDAO是一個對象關系映射(ORM)的框架真屯,能夠提供一個接口通過操作對象的方式去操...
    小董666閱讀 730評論 0 1