(一)GreenDao簡(jiǎn)介
GreenDao是一個(gè)對(duì)象關(guān)系映射(ORM)的開源框架,目前最主流的安卓數(shù)據(jù)庫操作框架。
- 首先說說什么是對(duì)象關(guān)系映射ORM:Object Relational Mapping,是一種將對(duì)象層次結(jié)構(gòu)映射成關(guān)系型結(jié)構(gòu)的方法。
Object:即對(duì)象,java是一門面向?qū)ο蟮木幊陶Z言,對(duì)開發(fā)者而言,更習(xí)慣以對(duì)象的角度看待某個(gè)問題,或
者說通過面向?qū)ο蟮姆绞教幚砟硞€(gè)問題,對(duì)java程序員來說更能理解。
Relational:關(guān)系,SQLite是一種關(guān)系型數(shù)據(jù)庫,其關(guān)系模型就是指二維表格模型,因而一個(gè)關(guān)系型數(shù)據(jù)庫
就是由二維表及其之間的聯(lián)系組成的一個(gè)數(shù)據(jù)組織,它是從數(shù)學(xué)理論發(fā)展而來的(百度百科的定
義),這種關(guān)系型數(shù)據(jù)庫與面向?qū)ο蟮乃枷胧菦_突的。開發(fā)人員需要時(shí)時(shí)去面對(duì)表單和數(shù)據(jù)庫的
操作,特別是當(dāng)表結(jié)構(gòu)復(fù)雜時(shí),會(huì)在這些數(shù)據(jù)的處理上花費(fèi)大量時(shí)間。
Mapping:映射(可以通過Map來理解),一種對(duì)應(yīng)關(guān)系,用面向?qū)ο蟮姆绞絹硖幚黻P(guān)系型結(jié)構(gòu)的數(shù)據(jù)庫。簡(jiǎn)單
的理解是一張表按照統(tǒng)一規(guī)則映射成一個(gè)java實(shí)體類,對(duì)表的操作可以轉(zhuǎn)換對(duì)成開發(fā)者更熟悉
的對(duì)實(shí)體對(duì)象的操作。
- 通過上面的簡(jiǎn)單分析,來說說ORM框架的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):
1,開發(fā)起來簡(jiǎn)單,ORM框架將我們的對(duì)象模型轉(zhuǎn)化為SQL語句,只需要掌握一些api就能夠操作數(shù)據(jù)庫,
不用親自處理sql語句了(下面greendao和原生Sqlite開發(fā)案例可以對(duì)比)。
2,當(dāng)面對(duì)一個(gè)復(fù)雜的程序時(shí),其內(nèi)部較多的數(shù)據(jù)處理,sql語句大量的硬編碼,會(huì)讓代碼顯得混亂和不
易維護(hù),ORM框架能讓結(jié)構(gòu)更清晰。
缺點(diǎn):
1,雖然ORM框架開發(fā)起來簡(jiǎn)單,但是我們需要掌握的東西卻更多了,框架需要去學(xué)習(xí),SQL原生操作需
要去掌握。
2,在一些復(fù)雜的數(shù)據(jù)庫操作(如多表關(guān)聯(lián)查詢)時(shí),ORM語法會(huì)變得十分復(fù)雜。直接用SQL語句會(huì)更清晰
,更直接。
-
給出GreenDao的ORM體現(xiàn)圖:
GreenDao的ORM體現(xiàn).png
(二)GreenDao的簡(jiǎn)單使用
1,版本說明:作為演示,本文中會(huì)用到目前最新的版本greenDao3.2.2,更多的信息可以去github看看greenDao
2,在build.grade中配置插件信息并引入依賴包
- 2.1,聲明添加的腳本類型和設(shè)置腳本運(yùn)行環(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,編譯項(xiàng)目,引入Greendao
最后給出完整的build.gradle文件,里面有詳細(xì)說明,這里在編譯時(shí)會(huì)出現(xiàn)一個(gè)問題,具體解決辦法可以借鑒這篇博文greenDao3.2.2配置出現(xiàn)的問題,這里面需要下載的文件我會(huì)在結(jié)束時(shí)給出。
apply plugin: 'com.android.library'
//1,聲明添加的插件類型
apply plugin: 'org.greenrobot.greendao'
//2,設(shè)置腳本的運(yùn)行環(huán)境(如果用在app啟動(dòng)模塊,直接加3,4步,在lib模塊整個(gè)需要加進(jìn)去)
buildscript {
repositories {
//3,支持java 依賴庫管理(maven/ivy),用于項(xiàng)目的依賴。
mavenCentral() // add repository
}
dependencies {
//4,依賴包的定義。支持maven/ivy,遠(yuǎn)程,本地庫,也支持單文件
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
}
}
//6,自定義Greendao版本和生成路徑
greendao {
//數(shù)據(jù)庫版本號(hào),數(shù)據(jù)庫修改后這里一定要記得修改,否則會(huì)報(bào)錯(cuò)no such table
schemaVersion 1
//通過gradle插件生成的數(shù)據(jù)庫相關(guān)文件的包名,默認(rèn)為你的entity所在的包名
daoPackage 'com.example.android_db_biz.greendao'
//這就是我們上面說到的自定義生成數(shù)據(jù)庫文件的目錄了,可以將生成的文件放到我們的java目錄中
,而不是build中,這樣就不用額外的設(shè)置資源目錄了
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)建一個(gè)實(shí)體類,實(shí)體類添加@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;
}
這里自增長(zhǎng)的id類型一定是Long/long類型,否則會(huì)報(bào)如下錯(cuò)誤:
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)項(xiàng)目
會(huì)自動(dòng)生成一些數(shù)據(jù)庫相關(guān)類,這些類在build.gradle里設(shè)置的目錄下,而且實(shí)體類里面也會(huì)自動(dòng)生成get/set方法

這里有多少個(gè)@Entity注釋的實(shí)體類就會(huì)生成多少個(gè)相關(guān)的XXXDao類,XXXDao類里提供對(duì)實(shí)體類對(duì)應(yīng)的表單的CRUD的操作方法,即ORM里提供以面向?qū)ο蟮姆绞絹硖幚黻P(guān)系型數(shù)據(jù)庫,不需要我們?nèi)憇ql語句。
5,greenDao的簡(jiǎn)單使用
- 首先獲取操作類DaoSession,默認(rèn)數(shù)據(jù)庫表存儲(chǔ)在內(nèi)存里,看看下面的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獲取上面實(shí)體類Song對(duì)應(yīng)的表單操作類SongDao,并進(jìn)行簡(jiǎn)單的CURD操作,具體復(fù)雜的SQL操作(如多表關(guān)聯(lián)),這里就不說明了:
//獲取Song這張表的操作類SongDao
DaoSession daoSession = DaoSessionManager.getInstace()
.getDaoSession(getApplicationContext());
SongDao songDao = daoSession.getSongDao();
//創(chuàng)建一個(gè)對(duì)象
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);
可以看出,在進(jìn)行增刪改查的操作并不需要我們?nèi)懴鄳?yīng)的sql語句,只需要調(diào)用songDao的相關(guān)API就行。
6,數(shù)據(jù)庫設(shè)置存儲(chǔ)為本地路徑
- 我現(xiàn)在調(diào)試用的小米手機(jī),無法root所以沒辦法看到data/data目錄下的數(shù)據(jù)情況,所有現(xiàn)在把數(shù)據(jù)庫表單存儲(chǔ)在手機(jī)本地目錄下,方便查看數(shù)據(jù)庫數(shù)據(jù),對(duì)上面的DaoSessionManager的getDaoMaster方法進(jìn)行修改(記得添加文件的讀寫權(quán)限)
//本地存儲(chǔ)目錄
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;
}
-
修改后,運(yùn)行項(xiàng)目,在指定目錄找到數(shù)據(jù)庫文件android.db:
greendao的數(shù)據(jù)庫文件.png - 執(zhí)行一次insert操作后,打開數(shù)據(jù)庫查看Song表數(shù)據(jù)

7,數(shù)據(jù)庫的版本升級(jí)
-
在上面基礎(chǔ)上,不更新數(shù)據(jù)庫的表單,只增加數(shù)據(jù)庫的版本號(hào),會(huì)發(fā)現(xiàn)數(shù)據(jù)庫里所有數(shù)據(jù)被清空,下面通過源碼分析下。
只修改版本信息.png
在基本數(shù)據(jù)庫操作里,Sqlite的數(shù)據(jù)庫更新是在SQLiteOpenHelper里的onUpgrade里進(jìn)行的,在GreenDao框架里肯定會(huì)繼承它來定制自己框架的需求,在DaoMaster里我們找到了GreenDao用來處理版本升級(jí)的類DevOpenHelper
/** WARNING: Drops all table on Upgrade! Use only during development. */
public static class DevOpenHelper extends OpenHelper {
...
@Override //數(shù)據(jù)庫版本升級(jí)會(huì)觸發(fā)這個(gè)方法
public void onUpgrade(Database db, int oldVersion, int newVersion) {
dropAllTables(db, true);
onCreate(db);
}
}
在其onUpgrade方法里會(huì)通過dropAllTables方法刪除項(xiàng)目里所有的數(shù)據(jù)庫,通過所有表單操作類XXXDao來刪除所有表單。
/** Drops underlying database table using DAOs. */
public static void dropAllTables(Database db, boolean ifExists) {
//項(xiàng)目里所有實(shí)體類操作Dao,都會(huì)在此刪除表單,當(dāng)前項(xiàng)目里只有一張表
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);
}
刪除完成后,調(diào)用onCreate(db)方法創(chuàng)建所有表單,onCreate方法存在于DaoMaster里的OpenHelper類里,它是上面DevOpenHelper的父類
public static abstract class OpenHelper extends DatabaseOpenHelper {
...
@Override //數(shù)據(jù)庫創(chuàng)建時(shí)觸發(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默認(rèn)在版本升級(jí)時(shí)會(huì)刪除所有表單然后再創(chuàng)建,如果用戶想自己控制版本升級(jí)的情況,就需要自己實(shí)現(xiàn)OpenHelper。
項(xiàng)目每次編譯運(yùn)行時(shí),DaoMaster里的內(nèi)容都會(huì)恢復(fù)成默認(rèn)狀態(tài),所以不要在DaoMaster的DevOpenHelper里進(jìn)行業(yè)務(wù)操作。
數(shù)據(jù)庫升級(jí)需要用戶自定義DaoMaster.OpenHelper,在其onUpgrade實(shí)現(xiàn)方法里進(jìn)行版本對(duì)比更新。這里借用一個(gè)開源解決辦法MigrationHelper類,來完成數(shù)據(jù)庫的升級(jí),有現(xiàn)成的我們就直接拿來用,里面代碼也挺清晰的,如果不想用也可以自己通過DaoMaster來處理,或者更直接的使用sql語句來處理。
MigrationHelper在我的demo里與greenDao不兼容,故直接把源碼拿來用,其github目錄為MigrationHelper解決greenDao版本升級(jí),大家可以直接去這里找符合你greenDao的版本,這里先給出用法,后面會(huì)進(jìn)行分析。
通過MigrationHelper進(jìn)行版本升級(jí)
- 1,還是結(jié)合上面的例子,我們刪除之前的本地?cái)?shù)據(jù)庫目錄,并在之前的表單基礎(chǔ)上再添加一張歌單表MenuInfo,這樣項(xiàng)目中就存在兩張表。
@Entity
public class MenuInfo {
@Id(autoincrement = true)
private Long id;
private String menuCode;
private String menuName;
private String createTime;
private String updateTime;
}
-
2,build項(xiàng)目,在兩張表里都添加兩條數(shù)據(jù)。此時(shí)數(shù)據(jù)庫的schemaVersion版本號(hào)為1
menuInfo表創(chuàng)建.png
song表創(chuàng)建.png 3,如果我們?cè)谙聜€(gè)版本需要對(duì)MenuInfo的字段做在線更改,增加一個(gè)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,版本升級(jí),自定義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);
}
}
}
同時(shí)修改其引用的地方DaoSessionManager的getDaoMaster方法:
DevOpenHelper mHelper = new VersionChangeHelper(new ContextWrapper(mContext)
- 5,運(yùn)行項(xiàng)目,查看日志和數(shù)據(jù)庫如下:
版本改變后,VersionChangeHelper的update方法被調(diào)用
10-08 18:22:07.797 22536-22536/com.example.pver.androiddevelopment E/miss08:
oldVersion = 1_newVersion = 2
表單信息為:


發(fā)現(xiàn)兩張表都是空表,這是因?yàn)槲覀冊(cè)谏厦鎜nUpgrade方法里super.onUpgrade(db, oldVersion, newVersion)會(huì)先執(zhí)行父類DaoMaster.DevOpenHelper里的刪除所有表單數(shù)據(jù)的方法,這個(gè)上面有分析,現(xiàn)在我們?nèi)サ魋uper.onUpgrade后,再假設(shè)重復(fù)上面的操作,就只有我們做過更新的MenuInfo表為空,其他表單還是有原始的數(shù)據(jù)。


MigrationHelper的簡(jiǎn)單分析
- 先看看MigrationHelper的migrate方法,它有三個(gè)重載方法,我們使用的那個(gè)方法有三個(gè)參數(shù),分別為database,一個(gè)ReCreateAllTableListener回調(diào),一個(gè)繼承AbstractDao的class對(duì)象的可變參數(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)的父類,項(xiàng)目中有多少表單在當(dāng)前版本進(jìn)行了修改,我們調(diào)用migrate方法,就需要傳多少進(jìn)去。
MigrationHelper.migrate(db,listener,xxDao.class,xxDao.class,xxDao.class...);
也可調(diào)用沒有l(wèi)istener的migrate方法,即不用回調(diào)給我們來處理數(shù)據(jù)庫,其使用一樣:
MigrationHelper.migrate(db,xxDao.class,xxDao.class,xxDao.class...);
- 在上面帶listener的migrate方法里,首先用弱引用指向listener,后調(diào)用migrate另一個(gè)重載方法,傳入database和修改的表單,方法如下。
public static void migrate(Database database
, Class<? extends AbstractDao<?, ?>>... daoClasses) {
//輪詢daoClasses,看對(duì)應(yīng)的表單是否存在,并創(chuàng)建臨時(shí)表
generateTempTables(database, daoClasses);
//獲取接口對(duì)象,在最初調(diào)用時(shí)會(huì)new WeakReference(),所以這里的listener不為空
ReCreateAllTableListener listener = weakListener.get();
if (listener != null) {
//執(zhí)行回調(diào)
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");
}
當(dāng) listener = null 時(shí),會(huì)刪除傳入的Dao對(duì)應(yīng)的那張表單,然后又重新創(chuàng)建這張表單,如果有新的數(shù)據(jù)就會(huì)存入更新后的表單里。
當(dāng) listener != null 時(shí),會(huì)通過listener回調(diào)給用戶自己處理,更加的靈活。
看看listener = null時(shí)刪除和重新創(chuàng)建的方法,不管是創(chuàng)建還是刪除最終會(huì)調(diào)用如下方法:
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方法里,會(huì)對(duì)daoClasses進(jìn)行輪詢,通過反射獲取我們我們傳入的daoClasses(本例中只有一個(gè)為MenuInfoDao)里的名為methodName的方法對(duì)象,然后通過invoke執(zhí)行MenuInfoDao類中的該方法,展示我們需要調(diào)用的兩個(gè)方法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);
}
這里會(huì)銷毀之前的那張表然后重新創(chuàng)建一張新表,所以表里原有數(shù)據(jù)是不會(huì)存在的,這里只有傳入的XXXDao.class做刪除和重建的工作,其他表單是不會(huì)受影響的,有回調(diào)的就得看用戶自己的需求了。
上面的例子演示了MigrationHelper對(duì)一個(gè)/多個(gè)表字段的更新(增刪都一樣),現(xiàn)在我們來演示在線增加一張/多張表。
- 首先在項(xiàng)目里添加一張表Singer,然后build項(xiàng)目,現(xiàn)在整個(gè)項(xiàng)目中就存在三張表
@Entity
public class Singer {
@Id(autoincrement = true)
private Long id;
private String singerCode;
private String singerName;
}
- 數(shù)據(jù)庫版本加1,修改VersionChangeHelper方法如下,這里演示沒有l(wèi)istener回調(diào)的方法,然后運(yùn)行項(xiàng)目
@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àng)目運(yùn)行時(shí)會(huì)出現(xiàn)一個(gè)問題,MigrationHelper里migrate對(duì)listener的非空判斷,這里修改一下然后重新運(yùn)行
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) {
...省略
}
}
-
項(xiàng)目運(yùn)行成功后,發(fā)現(xiàn)確實(shí)出現(xiàn)了三張表,這里只展示第三張表:
數(shù)據(jù)庫里所有表單.png
singer表創(chuàng)建.png
最后說說更新版本時(shí)刪除表操作
- 實(shí)現(xiàn)方式挺多的,提供幾種建議(前提是自己的helper里不要有super父類更新方法),這里我只用第一種方案試過并成功,其他幾種就不演示了。
1,不使用MigrationHelper直接通過SingerDao里的dropTable(db, true)方法完成
2,不使用MigrationHelper直接通過sql語句完成
3,使用MigrationHelper,對(duì)里面代碼簡(jiǎn)單修改下,通過reflectMethod來處理傳入的daoClasses
(三)GreenDao源碼的簡(jiǎn)單分析
說明:
要查看具體源碼,可以到github下載greenDao,里面有它的源碼和操作demo。
-
1,用戶可操作的幾個(gè)類的關(guān)系圖如下:
GreenDao類圖.png - 2,DaoGenerator:通過給定的模板Schema和路徑生成相應(yīng)的實(shí)體類和Dao相關(guān)的類。Schema的關(guān)系圖如下:

Schema的操作方式如下,包含了數(shù)據(jù)庫的相關(guān)信息:
//版本和包名
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里生成實(shí)體類和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.
包含項(xiàng)目所有表單的創(chuàng)建工作createAllTables和升級(jí)處理(先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)建時(shí)會(huì)傳入daoConfigMap,這個(gè)daoConfigMap是項(xiàng)目所有表單操作類XXXDao的class對(duì)象緩存,它是在創(chuàng)建DaoMaster時(shí)創(chuàng)建和存入class的。
public DaoSession newSession() {
return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
}
看看DaoMaster的構(gòu)造方法,通過registerDaoClass方法把當(dāng)前項(xiàng)目里所有的XXXDao的class對(duì)象緩存起來(這里不是實(shí)例對(duì)象,是class對(duì)象,一般用來搞反射)。
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ù)前,會(huì)創(chuàng)建一個(gè)DaoConfig,DaoConfig里面存儲(chǔ)了很多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;
我們?cè)倏纯碊aoConfig的這個(gè)構(gòu)造方法,通過reflectProperties方法獲取到Property數(shù)組。Property里的數(shù)據(jù)描述了映射到數(shù)據(jù)庫里列的屬性。用于創(chuàng)建查詢構(gòu)建器使用的對(duì)象(包含所有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這個(gè)方法里,通過反射獲取AbstractDao類的內(nèi)部類Properties里所有的靜態(tài)字段和public字段,然后再通過field.get(null)獲取所有字段的屬性值,存儲(chǔ)這些屬性值。
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為例,看看其內(nèi)部類的字段和屬性:屬性值就是一個(gè)Property對(duì)象,包含實(shí)體類的字段名,對(duì)應(yīng)的數(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:在構(gòu)造里創(chuàng)建出XXXDao的實(shí)例對(duì)象。
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);
}
構(gòu)造方法里,首先從傳入的daoConfigMap里獲取DaoConfig,通過原型模式創(chuàng)建DaoConfig對(duì)象,然后創(chuàng)建XXXDao的實(shí)例,DaoConfig對(duì)象做為Dao的構(gòu)參傳入,最后在DaoSession的父類里用Map<Class<?>, AbstractDao<?, ?>>集合緩存實(shí)例類和它的操作類對(duì)象。
接下來看看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();
}
還有支持?jǐn)?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ù)庫的真實(shí)操作類XXXDao
- 5,XXXDao:數(shù)據(jù)庫提供的真實(shí)操作類:提供所有數(shù)據(jù)庫的操作方式。這里就只通過查詢語句來分析一下它的操作過程(以Singer表為例)。
GreenDao查詢語句
singerDao.queryBuilder().where(Properties.SingerCode.eq("111")).orderAsc(Properties.SINGER_NAME).list();
對(duì)比下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方法,該方法里會(huì)創(chuàng)建一個(gè)QueryBuilder對(duì)象,我們來看看QueryBuilder的描述:
Builds custom entity queries using constraints and parameters and without SQL
(QueryBuilder creates SQL for you)
使用約束條件和參數(shù)來構(gòu)造實(shí)體對(duì)象的查詢,而不使用SQL語句(QueryBuilder 為我們生成SQL語句)
QueryBuilder的where方法,使用一個(gè)類WhereCondition來收集所有的條件,這個(gè)在下面的build方法里會(huì)被用到。
public QueryBuilder<T> where(WhereCondition cond, WhereCondition... condMore) {
whereCollector.add(cond, condMore);
return this;
}
接著看QueryBuilder類的orderAsc方法,實(shí)際會(huì)調(diào)用下面這個(gè)方法,分析見里面注釋。
private void orderAscOrDesc(String ascOrDescWithLeadingSpace, Property... properties)
{
for (Property property : properties) {
//獲取StringBuilder對(duì)象拼接字符串,沒有就創(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);
}
}
最終會(huì)創(chuàng)建一個(gè)orderBuilder,里面包含“SINGER_NAME asc”
最后看QueryBuilder類的list方法,list方法里先執(zhí)行build方法,然后執(zhí)行build方法返回對(duì)象的list方法。
public List<T> list() {
return build().list();
}
先找到build方法,build里會(huì)做很多拼接工作。
public Query<T> build() {
//創(chuàng)建一個(gè)StringBuilder對(duì)象,拼接工作下面分析
StringBuilder builder = createSelectBuilder();
//判斷是否有LIMIT和OFFSET條件,如果有則拼接上
int limitPosition = checkAddLimit(builder);
int offsetPosition = checkAddOffset(builder);
String sql = builder.toString();
checkLog(sql);
//返回一個(gè)Query對(duì)象,查詢操作返回的結(jié)果(實(shí)體類或游標(biāo)),values是position的值
return Query.create(dao, sql, values.toArray(), limitPosition, offsetPosition);
}
重點(diǎn)看看上面build方法里執(zhí)行的createSelectBuilder方法,他會(huì)拼接返回完整的用于查詢條件的select語句
private StringBuilder createSelectBuilder() {
//創(chuàng)建一個(gè)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,這個(gè)在上面分析過
if (orderBuilder != null && orderBuilder.length() > 0) {
builder.append(" ORDER BY ").append(orderBuilder);
}
//返回最終拼裝的StringBuilder
return builder;
}
再看最后的list方法調(diào)用,這個(gè)方法在類Query里,找到了我們最熟悉的Sqlite操作dao.getDatabase().rawQuery,查詢傳入我們上面拼接的sql語句和傳入的參數(shù)值,得到游標(biāo),輪詢就能返回結(jié)果。
public List<T> list() {
checkThread();
Cursor cursor = dao.getDatabase().rawQuery(sql, parameters);
return daoAccess.loadAllAndCloseCursor(cursor);
}
最后說明
在分析完GreenDao的查詢流程后,發(fā)現(xiàn)XXXDao對(duì)數(shù)據(jù)庫的操作最終還是Sqlite里對(duì)sql語句的操作。體現(xiàn)了ORM面向?qū)ο蟛僮骱完P(guān)系數(shù)據(jù)表操作的映射,所以要想熟練使用GreenDao對(duì)數(shù)據(jù)庫的操作,必須得先掌握SQLite里對(duì)數(shù)據(jù)庫的操作和常用的SQL語句的書寫。
(四)Sqlite數(shù)據(jù)庫的使用案例:
下面這個(gè)案例是之前的公司項(xiàng)目在迭代Orimlite框架前,對(duì)數(shù)據(jù)庫的操作,對(duì)比下GreenDao的使用方式,這里就直接給出代碼,就不做多余的分析,最終的demo里也會(huì)給出完整代碼。
- 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è)置版本號(hào),數(shù)據(jù)庫名稱,完成初始化表格的創(chuàng)建和更新的操作
public class DBHelper extends SQLiteOpenHelper {
//數(shù)據(jù)庫名
private static final String DB_NAME = "android2.db";
//版本號(hào)
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,把表單存儲(chǔ)到sd卡里,需要自定義ContextWrapper,在構(gòu)建helper時(shí)傳入這個(gè)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ù)庫操作的一些公共方法放到一個(gè)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;
}
//查詢需要對(duì)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";
//只給出簡(jiǎn)單的增刪改查
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自測(cè)表的創(chuàng)建成功,而且對(duì)表的所有操作方法都通過驗(yàn)證。
總結(jié):
上面就是所有對(duì)GreenDao的簡(jiǎn)單分析,由于公司使用的是ORMLite框架,所以GreenDao里比較復(fù)雜的一些操作(多表關(guān)聯(lián)/事物的處理/版本跳級(jí)更新/融合Rxjava等)這里都沒演示,一是使用不熟練,二是目前確實(shí)沒時(shí)間慢慢弄,文章里所有demo都是自己操作成功的,如果有什么其他問題和建議可留言。
其他:
本文所有demo的github地址(gradle-3.5-all在項(xiàng)目根目錄下,解壓使用):GreenDao和SQLite的使用






