SQL語句寫到累了?試試GreenDAO

JYcoder發表於2018-03-31

安卓基礎開發庫,讓開發簡單點。
DevRing & Demo地址github.com/LJYcoder/De…

學習/參考地址:
http://www.jianshu.com/p/4e6d72e7f57a
http://blog.csdn.net/qq_30379689/article/details/54410838
http://blog.csdn.net/shineflowers/article/details/53405644

前言

在以前選擇資料庫框架的時候,接觸過GreenDAO,但由於那時的GreenDAO配置起來很繁瑣,需要自己建立java庫,所以就沒使用它。
但如今在3.0版本後,GreenDAO大大簡化了使用流程,加上其本身存取快、體積小、支援快取、支援加密等優點,使得它成為了一個更受歡迎的ORM解決方案。

附上一張官方提供的,GreenDAO、OrmLite、ActiveAndroid的對比圖

對比圖


介紹

下面分為 配置、建庫建表、增刪改查、加密、升級、混淆 這幾個部分來介紹。

1. 配置

1.1 新增依賴

Project下的build.gradle檔案加入

dependencies {
    //greendao配置
    classpath 'org.greenrobot:greendao-gradle-plugin:3.2.0'
}
複製程式碼

Module下的build.gradle檔案加入

apply plugin: 'org.greenrobot.greendao'

dependencies {
	//greenDAO配置
	compile 'org.greenrobot:greendao:3.2.0'
}
複製程式碼

1.2 設定版本號、生成路徑

android {
    //greendao配置
    greendao {
        //資料庫版本號,升級時修改
        schemaVersion 1
        //生成的DAO,DaoMaster和DaoSession的包路徑。預設與表實體所在的包路徑相同
        daoPackage 'com.dev.base.model.db'
        //生成原始檔的路徑。預設原始檔目錄是在build目錄中的(build/generated/source/greendao)
        targetGenDir 'src/main/java' 
    }
}
複製程式碼

2. 建庫建表

2.1 建立資料庫

//DaoMaster為後面建立表實體後自動生成的類。
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "test.db", null);
複製程式碼

2.2 建立資料表

GreenDAO通過ORM(Object Relation Mapping 物件關係對映)的方式建立資料表。即建立一個實體類,將該實體結構對映成資料表的結構。
下面演示如何建立一個"電影收藏"資料表。分兩步:建立表實體,Make Project生成程式碼。

2.2.1 建立表實體

@Entity
public class MovieCollect {

    @Id
    private Long id;
    private String movieImage;
    private String title;
    private int year;
    
}
複製程式碼

註解介紹:(參考自http://www.jianshu.com/p/4e6d72e7f57a)

@Entity 用來宣告類實體,表示它將對映為資料表
@Entity()括號內可加入更詳細的設定,如:
nameInDb =“TABLE_NAME” ------> 宣告該表的表名,預設取類名
createInDb = true ------> 是否建立表,預設為true
generateConstructors = true ------> 是否生成含所有引數的建構函式,預設為true
generateGettersSetters = true ------> 是否生成getter/setter,預設為true

@Id 用來宣告某變數為表的主鍵,型別使用Long
@Id()括號可加入autoincrement = true表明自增長

@Unique 用來宣告某變數的值需為唯一值

@NotNull 用來宣告某變數的值不能為null

@Property @Property(nameInDb = "URL") 用來宣告某變數在表中的實際欄位名為URL

@Transient 用來宣告某變數不被對映到資料表中

@ToOne、@ToMany 用來宣告"對一"和“對多”關係,下面舉例說明:

  • 學生與學校之間一對多的關係(一個學生對應一個學校,一個學校對應有多個學生)
@Entity
class Student{
	//...省略其他變數
	private long fk_schoolId;//外來鍵
	
	@ToOne(joinProperty = "fk_schoolId")
	private School school;
}

@Entity
class School{
	//...省略其他變數	
	@ToMany(referencedJoinProperty = "fk_schoolId")
    private List<Student> students;
}
複製程式碼
  • 學生與課程之間**“多對多”**的關係(一個學生對應有多門課程,一門課程對應有多個學生)
@Entity
class Student{
	//...省略其他變數
	@ToMany
	@JoinEntity(
            entity = StudentWithCourse.class,
            sourceProperty = "sId",
            targetProperty = "cId"
    )
    private List<Course> courses;
}

@Entity 
class Course{
	//...省略其他變數
	@ToMany
	@JoinEntity(
            entity = StudentWithCourse.class,
            sourceProperty = "cId",
            targetProperty = "sId"
    )
    private List<Course> courses;
}

@Entity
class StudentWithCourse{
	@Id
    private Long id;
    private Long sId;
    private Long cId;
}
複製程式碼

2.2.2 Make Project

利用上面註解寫好表實體後,通過Build--->Make Project重新編譯專案,
將會在表實體中自動生成構造方法getter/setter方法,另外在指定(或預設)的包中生成DaoMasterDaoSession以及表實體對應的Dao(如MovieCollectDao)。

其中,
DaoMaster:用於建立資料庫以及獲取DaoSession
DaoSession:用於獲取各個表對應的Dao類
各個表對應的Dao:提供了對錶進行增刪改查的方法

3. 增刪改查

要對資料表進行增刪改查,需要獲取該表對應的Dao類,而獲取Dao類需要DaoSession。
建立一個DaoManager用於初始化資料庫以及提供DaoSession。

//DaoManager中程式碼

//獲取DaoSession,從而獲取各個表的操作DAO類
public DaoSession getDaoSession() {
    if (mDaoSession == null) {
        initDataBase();
    }
    return mDaoSession;
}

//初始化資料庫及相關類
private void initDataBase(){
    setDebugMode(true);//預設開啟Log列印
    mSQLiteOpenHelper = new DaoMaster.DevOpenHelper(MyApplication.getInstance(), DB_NAME, null);//建庫
    mDaoMaster = new DaoMaster(mSQLiteOpenHelper.getWritableDatabase());
    mDaoSession = mDaoMaster.newSession();
    mDaoSession.clear();//清空所有資料表的快取
}

//是否開啟Log
public void setDebugMode(boolean flag) {
    MigrationHelper.DEBUG = true;//如果檢視資料庫更新的Log,請設定為true
    QueryBuilder.LOG_SQL = flag;
    QueryBuilder.LOG_VALUES = flag;
}
複製程式碼

通過DaoSeesion獲取目標資料表對應的Dao,然後就可開始進行增刪改查的操作了。

MovieCollectDao mMovieCollectDao = DaoManager.getInstance().getDaoSession().getMovieCollectDao();
複製程式碼

3.1 增

  • 插入單個資料
MovieCollect movieCollect;
mMovieCollectDao.insert(movieCollect);
複製程式碼
  • 插入一組資料
List<MovieCollect> listMovieCollect;
mMovieCollectDao.insertInTx(listMovieCollect);
複製程式碼
  • 插入或替換資料
//插入的資料如果已經存在表中,則替換掉舊資料(根據主鍵來檢測是否已經存在)

MovieCollect movieCollect;
mMovieCollectDao.insertOrReplace(movieCollect);//單個資料

List<MovieCollect> listMovieCollect;
mMovieCollectDao.insertOrReplaceInTx(listMovieCollect);//一組資料
	
複製程式碼

3.2 刪

  • 刪除單個資料
MovieCollect movieCollect;
mMovieCollectDao.delete(movieCollect);
複製程式碼
  • 刪除一組資料
List<MovieCollect> listMovieCollect;
mMovieCollectDao.deleteInTx(listMovieCollect);
複製程式碼
  • 刪除所有資料
mMovieCollectDao.deleteAll();
複製程式碼

3.3 改

  • 修改單個資料
MovieCollect movieCollect;
mMovieCollectDao.update(movieCollect);
複製程式碼
  • 修改一組資料
List<MovieCollect> listMovieCollect;
mMovieCollectDao.updateInTx(listMovieCollect);
複製程式碼

3.4 查

  • 查詢全部資料
List<MovieCollect> listMovieCollect = mMovieCollectDao.loadAll();
複製程式碼
  • 查詢數量
int count = mMovieCollectDao.count();
複製程式碼
  • 條件查詢

精確查詢(where)

//查詢電影名為“肖申克的救贖”的電影
MovieCollect movieCollect = 
mMovieCollectDao.queryBuilder().where(MovieCollectDao.Properties.Title.eq("肖申克的救贖")).unique(); 

//查詢電影年份為2017的電影
List<MovieCollect> movieCollect =
mMovieCollectDao.queryBuilder().where(MovieCollectDao.Properties.Year.eq(2017)).list(); 
複製程式碼

模糊查詢(like)

//查詢電影名含有“傳奇”的電影
List<MovieCollect> movieCollect = mMovieCollectDao.queryBuilder().where(MovieCollectDao.Properties.Title.like("傳奇")).list();

//查詢電影名以“我的”開頭的電影
List<MovieCollect> movieCollect = mMovieCollectDao.queryBuilder().where(MovieCollectDao.Properties.Title.like("我的%")).list();
複製程式碼

區間查詢

//大於
//查詢電影年份大於2012年的電影
List<MovieCollect> movieCollect = mMovieCollectDao.queryBuilder().where(MovieCollectDao.Properties.Year.gt(2012)).list();

//大於等於
//查詢電影年份大於等於2012年的電影
List<MovieCollect> movieCollect = mMovieCollectDao.queryBuilder().where(MovieCollectDao.Properties.Year.ge(2012)).list();

//小於
//查詢電影年份小於2012年的電影
List<MovieCollect> movieCollect = mMovieCollectDao.queryBuilder().where(MovieCollectDao.Properties.Year.lt(2012)).list();

//小於等於
//查詢電影年份小於等於2012年的電影
List<MovieCollect> movieCollect = mMovieCollectDao.queryBuilder().where(MovieCollectDao.Properties.Year.le(2012)).list();

//介於中間
//查詢電影年份在2012-2017之間的電影
List<MovieCollect> movieCollect = mMovieCollectDao.queryBuilder().where(MovieCollectDao.Properties.Year.between(2012,2017)).list();

複製程式碼

升序降序

//查詢電影年份大於2012年的電影,並按年份升序排序
List<MovieCollect> movieCollect = mMovieCollectDao.queryBuilder().where(MovieCollectDao.Properties.Year.gt(2012)).orderAsc(MovieCollectDao.Properties.Year).list();

//查詢電影年份大於2012年的電影,並按年份降序排序
List<MovieCollect> movieCollect = mMovieCollectDao.queryBuilder().where(MovieCollectDao.Properties.Year.gt(2012)).orderDesc(MovieCollectDao.Properties.Year).list();
複製程式碼

and/or

//and
//查詢電影年份大於2012年且電影名以“我的”開頭的電影
List<MovieCollect> movieCollect = mMovieCollectDao.queryBuilder().and(MovieCollectDao.Properties.Year.gt(2012), MovieCollectDao.Properties.Title.like("我的%")).list();

//or
//查詢電影年份小於2012年或者大於2015年的電影
List<MovieCollect> movieCollect = mMovieCollectDao.queryBuilder().or(MovieCollectDao.Properties.Year.lt(2012), MovieCollectDao.Properties.Year.gt(2015)).list();
複製程式碼

SQL語句

//查詢名字為“羞羞的鐵拳”的電影

//使用Dao.queryBuilder().where() 配合 WhereCondition.StringCondition() 實現SQL查詢
Query query =
mMovieCollectDao.queryBuilder()
.where(new WhereCondition.StringCondition("TITLE = ?", "羞羞的鐵拳")).build();
List<MovieCollect> movieCollect = query.list();

//使用Dao.queryRawCreate() 實現SQL查詢
Query query = mMovieCollectDao.queryRawCreate("WHERE TITLE = ?", "羞羞的鐵拳");
List<MovieCollect> movieCollect = query.list();
複製程式碼

去重

//根據全部表欄位來去重
Query query = mMovieCollectDao.queryBuilder().distinct();
List<MovieCollect> movieCollect = query.list();

//根據某個欄位來去重,請參考
String SQL_DISTINCT = "SELECT DISTINCT "+ MovieCollectDao.Properties.Title.columnName+" FROM "+MovieCollectDao.TABLENAME;
Cursor curcor = session.getDatabase().rawQuery(SQL_DISTINCT , null);
...
複製程式碼
  • 快取問題

由於GreenDao預設開啟了快取,所以當你呼叫A查詢語句取得X實體,然後對X實體進行修改並更新到資料庫,接著再呼叫A查詢語句取得X實體,會發現X實體的內容依舊是修改前的。其實你的修改已經更新到資料庫中,只是查詢採用了快取,所以直接返回了第一次查詢的實體。
解決方法:查詢前先清空快取,清空方法如下

//清空所有資料表的快取資料
DaoSession daoSession = DaoManager.getInstance().getDaoSession();
daoSession .clear();

//清空某個資料表的快取資料
MovieCollectDao movieCollectDao = DaoManager.getInstance().getDaoSession().getMovieCollectDao();
movieCollectDao.detachAll();
複製程式碼
  • 查詢.db檔案的內容
    一個朋友提供的思路,我就直接貼出他給我的程式碼截圖了。

greendao查詢.db檔案

程式碼的意思大概就是:
將asset下的.db檔案通過IO流寫進資料庫資料夾下的.db檔案,然後通過DaoMaster的建構函式傳入檔案。後續的查詢操作就和前面介紹的一樣了。

4. 加密

注意: 加密需要新增sqlcipher庫,而該庫體積龐大,使用後apk大小會增加大概5M,所以如果你的應用對安全性要求不高,不建議使用。

加密資料庫的步驟:
第一步: Module下的build.gradle中新增一個庫依賴,用於資料庫加密。

compile 'net.zetetic:android-database-sqlcipher:3.5.7'//使用加密資料庫時需要新增
複製程式碼

第二步:
獲取DaoSession的過程中,使用getEncryptedWritableDb(“你的密碼”)來獲取操作的資料庫,而不是getWritableDatabase()。

mSQLiteOpenHelper = new MySQLiteOpenHelper(MyApplication.getInstance(), DB_NAME, null);//建庫
mDaoMaster = new DaoMaster(mSQLiteOpenHelper.getEncryptedWritableDb("你的密碼"));//加密
//mDaoMaster = new DaoMaster(mSQLiteOpenHelper.getWritableDatabase());
mDaoSession = mDaoMaster.newSession();
複製程式碼

第三步:
使用上面步驟得到的DaoSession進行具體的資料表操作。
如果執行後報無法載入有關so庫的異常,請對專案進行clean和rebuild。

5. 升級

在版本迭代時,我們經常需要對資料庫進行升級,而GreenDAO預設的DaoMaster.DevOpenHelper在進行資料升級時,會把舊錶刪除,然後建立新表,並沒有遷移舊資料到新表中,從而造成資料丟失。
這在實際中是不可取的,因此我們需要作出調整。下面介紹資料庫升級的步驟與要點。

第一步: 新建一個類,繼承DaoMaster.DevOpenHelper,重寫onUpgrade(Database db, int oldVersion, int newVersion)方法,在該方法中使用MigrationHelper進行資料庫升級以及資料遷移。
網上有不少MigrationHelper的原始碼,這裡採用的是https://github.com/yuweiguocn/GreenDaoUpgradeHelper中的MigrationHelper,它主要是通過建立一個臨時表,將舊錶的資料遷移到新表中,大家可以去看下原始碼。

public class MyOpenHelper extends DaoMaster.OpenHelper {
    public MyOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
        super(context, name, factory);
    }

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

        //把需要管理的資料庫表DAO作為最後一個引數傳入到方法中
        MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {

            @Override
            public void onCreateAllTables(Database db, boolean ifNotExists) {
                DaoMaster.createAllTables(db, ifNotExists);
            }

            @Override
            public void onDropAllTables(Database db, boolean ifExists) {
                DaoMaster.dropAllTables(db, ifExists);
            }
        },  MovieCollectDao.class);
    }
}
複製程式碼

然後使用MyOpenHelper替代DaoMaster.DevOpenHelper來進行建立資料庫等操作

mSQLiteOpenHelper = new MyOpenHelper(MyApplication.getInstance(), DB_NAME, null);//建庫
mDaoMaster = new DaoMaster(mSQLiteOpenHelper.getWritableDatabase());
mDaoSession = mDaoMaster.newSession();
複製程式碼

第二步:
在表實體中,調整其中的變數(表欄位),一般就是新增/刪除/修改欄位。注意:
1)新增的欄位或修改的欄位,其變數型別應使用基礎資料型別的包裝類,如使用Integer而不是int,避免升級過程中報錯。
2)根據MigrationHelper中的程式碼,升級後,新增的欄位和修改的欄位,都會預設被賦予null值。

第三步:
將原本自動生成的構造方法以及getter/setter方法刪除,重新Build--->Make Project進行生成。

第四步:
修改Module下build.gradle中資料庫的版本號schemaVersion ,遞增加1即可,最後執行app

greendao {
    //資料庫版本號,升級時進行修改
    schemaVersion 2

	daoPackage 'com.dev.base.model.db'
	targetGenDir 'src/main/java'
}
複製程式碼

6. 混淆

在proguard-rules.pro檔案中新增以下內容進行混淆配置

# greenDAO開始
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
# If you do not use SQLCipher:
-dontwarn org.greenrobot.greendao.database.**
# If you do not use RxJava:
-dontwarn rx.**
# greenDAO結束


# 如果按照上面介紹的加入了資料庫加密功能,則需新增一下配置
#sqlcipher資料庫加密開始
-keep  class net.sqlcipher.** {*;}
-keep  class net.sqlcipher.database.** {*;}
#sqlcipher資料庫加密結束
複製程式碼

相關文章