這已經是第六篇了,本來還打算分好幾個章節寫的,但是考慮到可讀性,所以本章將全面講解GreenDao的使用。
數據庫的加密前面篇章已經寫的,本章就不介紹了。
在Android開發中,我們需要選擇一個數據庫管理工具,Android支持直接使用SQL語句,但是如果直接使用SQL語句的話顯然出錯率比較高。GreenDao是現在主流的數據庫框架之一。
(1)作用
通過GreenDao,我們可以更快速的操作數據庫,我們可以使用簡單的面相對象的API來存儲,更新,刪除和查詢Java對象。(CUID)
(2)優缺點
- 堅如磐石:greenDAO自2011年以來一直存在,並被無數着名的應用程序使用
- 超級簡單:簡潔直接的API,帶有註釋的V3
- 小:庫是<150K,它只是普通的Java jar(沒有CPU依賴的本機部分)
- 快速:可能是Android上最快的ORM,由智能代碼生成驅動
- 安全和富有表現力的查詢API:QueryBuilder使用屬性常量來避免拼寫錯誤
- 強大的連接:跨實體查詢,甚至是複雜關係的鏈接
- 靈活的屬性類型:使用自定義類或枚舉來表示實體中的數據
- 加密:支持SQLCipher加密數據庫
(3)配置
(4)核心類
GreenDao的核心類有三個:分別是DaoMaster,DaoSession,XXXDao,這三個類都是自動生成的。(第三點配置
有說明)
DaoMaster:
DaoMaster保存數據庫對象(SQLiteDatabase)並管理特定模式的DAO類(而不是對象)。它有靜態方法來創建表或刪除它們。它的內部類OpenHelper和DevOpenHelper是SQLiteOpenHelper實現,它們在SQLite數據庫中創建模式。
DaoSession:
管理特定模式的所有可用DAO對象,您可以使用其中一個getter方法獲取該對象。DaoSession還提供了一些通用的持久性方法,如實體的插入,加載,更新,刷新和刪除。
XXXDao:
數據訪問對象(DAO)持久存在並查詢實體。對於每個實體,greenDAO生成DAO。它具有比DaoSession更多的持久性方法,例如:count,loadAll和insertInTx。
Entities :
可持久化對象。通常, 實體對象代表一個數據庫行使用標準 Java 屬性(如一個POJO 或 JavaBean )。
(5) GreenDao初始化
這個之前已經提到過,說白了就是獲取指定數據庫,如果沒有數據庫則新建一個數據庫,GreenDao初始化代碼一般放在Application中執行,因爲我們的最終目的是要拿到DaoSession,DaoSession可以管理各種Dao。
(7)實體註解@Entity
@Entity
是Greendao註解之一,Greendao只保留用此註釋標記的類的對象,用次註解標註的類的對象就是一個實體
。
我們查看一下源碼,發現該註解有一些成員變量:
nameInDb:
指定此實體映射到的數據庫端的名稱(例如表名)。默認情況下,名稱基於實體類名稱,默認爲空。
indexes:
實體的索引,默認爲空。
createInDb:
是否允許在數據庫中創建表格,默認爲true。
schema:
指定實體的架構名稱:Greendao可以爲每個架構生成獨立的類集。屬於不同模式的實體應該有關係。默認爲“default”。
active:
標記一個實體處於活躍狀態,活動實體有更新、刪除和刷新方法。默認爲false。
generateConstructors:
是否生成構造方法。默認爲true。
generateGettersSetters:
如果缺少setXXX或者getXXX,是否生成setXXX或者getXXX。默認爲true。
protobuf:
定義此實體的protobuf類以爲其創建額外的特殊DAO。默認爲void.class。
接下來,我們一起看一下其用法吧。
-
@Entity
的用法
簡單聲明一個實體,最簡單的代碼如下:
@Entity
public class Student {
private String mId;
private String mName;
private int mGender;
}
也就是說,被註解@Entity
修飾的類就是一個實體了,重新編譯之後就會自動生成DaoMaster.java
、DaoSession.java
、StudentDao.java
這是三個類。
-
nameInDb
的用法
nameInDb的默認值是“”,所以其表明如圖所示:
默認表名就是類的大寫字母,非駝峯命名法。
nameInDb屬性的配置,可以將表格名稱的命名不再默認,如:
@Entity(nameInDb = "T_STUDENT")
public class Student {
private String mId;
private String mName;
private int mGender;
}
值得注意的是,表格的命名最好是字母和下劃線,如果存在特殊符號,比如:
@Entity(nameInDb = "t_student、")
public class Student {
private String mId;
private String mName;
private int mGender;
}
上圖可知,表名亂碼了,所以爲了避免這種情況,nameInDb的取值最好是字母和下劃線,或者加個數字也是可以的。
還有一點需要注意,假如本地已經存在STUDENT表,如果修改屬性nameInDb = "T_STUDENT",那麼STUDENT表的表名不會被修改爲T_STUDENT,只有在第一次建表的時候纔會命名爲T_STUDENT。
-
indexes
的使用
indexes顧名思義就是索引的意思。
索引是對數據庫表中一列或多列的值進行排序的一種結構,使用索引可快速訪問數據庫表中的特定信息。如果想按特定職員的姓來查找他或她,則與在表中搜索所有的行相比,索引有助於更快地獲取信息。
索引的一個主要目的就是加快檢索表中數據,亦即能協助信息搜索者儘快的找到符合限制條件的記錄ID的輔助數據結構。
代碼如下:
@Entity(
nameInDb = "T_STUDENT",
indexes = {
@Index(name = "index1", value = "mName", unique = true)
}
)
public class Student {
@Id(autoincrement = true)
private Long mId;
private String mName;
private int mGender;
//....
}
之前介紹了nameInDb屬性,如果多個屬性就用逗號隔開。
以上代碼就是索引集合的簡單寫法,name
是索引名稱,value
是表的字段名稱,unique
是索引的唯一約束。
【例一】創建常規索引
indexes = {
@Index(name = "index1", value = "mName", unique = false)
}
只要將unique的值改成false即可。
【例二】創建唯一索引
indexes = {
@Index(name = "index1", value = "mName", unique = true)
}
只要將unique的值改成true即可。
【例三】創建一個按字段正序排序或者倒序排序的索引
正序
indexes = {
@Index(name = "index1", value = "mName asc", unique = true)
}
倒序
indexes = {
@Index(name = "index1", value = "mName desc", unique = true)
}
【例四】一個索引綁定多個字段
indexes = {
@Index(name = "index1", value = "mId desc, mName asc", unique = true)
}
字段和字段之間用逗號隔開。
【例五】創建多個索引
indexes = {
@Index(name = "index1", value = "mId, mName asc", unique = true),
@Index(name = "index2", value = "mGender desc", unique = false)
}
索引和索引之間用逗號隔開。
【例六】創建主鍵索引
洗洗睡吧,在@Entity
中只支持創建常規索引和唯一索引,主鍵索引在後面會講到。
-
createInDb
的用法
@Entity(
nameInDb = "T_STUDENT",
indexes = {
@Index(name = "index1", value = "mId, mName asc", unique = true),
@Index(name = "index2", value = "mGender desc", unique = false)
},
createInDb = true
)
createInDb的默認值是true,如果改爲false,將不會在這個數據庫中建表。如果不想使用該實體,又不想刪除這個文件,那麼可以將createInDb的值改成false,等下次使用的話再改成true。
-
schema
的用法
schema
怎麼定義?
在模塊的的build.gradle
文件裏,添加以下配置
//greendao配置
greendao {
//版本號,升級時可配置
schemaVersion 1
daoPackage 'com.xxx.ooo.greendaodemo.gendao'
targetGenDir 'src/main/java'
}
以上配置就是schema的定義了。
還有一種定義方法需要導入依賴包:
implementation 'org.greenrobot:greendao-generator:3.2.0'
導入這個依賴之後就可以創建schema對象
Schema schema = new Schema(1, "com.xxx.ooo.greendaodemo.gendao");
String schemaName1 = schema.getName();
schema的name成員變量就是schema架構名稱,在目前的版本中只能獲取架構名稱,不可以設置架構名稱。schema架構名稱的默認值是“default”。
那麼,schema在@Entity中的用法是怎樣的呢?
@Entity(
nameInDb = "T_STUDENT",
indexes = {
@Index(name = "index1", value = "mId, mName asc", unique = true),
@Index(name = "index2", value = "mGender desc", unique = false)
},
createInDb = true,
schema = "default"
)
當前版本只能將schema配置成"default",也就是說,當前版本只有一個架構,期待後期版本的更新會支持吧。
-
active
的用法
設置Entity是否是活動的。如果是活動的,那麼他將有更新,刪除,刷新的方法。默認值爲false。
如果爲true,實體類中就會自動生成以下代碼
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}.
* Entity must attached to an entity context.
*/
@Generated(hash = 128553479)
public void delete() {
if (myDao == null) {
throw new DaoException("Entity is detached from DAO context");
}
myDao.delete(this);
}
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}.
* Entity must attached to an entity context.
*/
@Generated(hash = 1942392019)
public void refresh() {
if (myDao == null) {
throw new DaoException("Entity is detached from DAO context");
}
myDao.refresh(this);
}
/**
* Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}.
* Entity must attached to an entity context.
*/
@Generated(hash = 713229351)
public void update() {
if (myDao == null) {
throw new DaoException("Entity is detached from DAO context");
}
myDao.update(this);
}
/** called by internal mechanisms, do not call yourself. */
@Generated(hash = 1701634981)
public void __setDaoSession(DaoSession daoSession) {
this.daoSession = daoSession;
myDao = daoSession != null ? daoSession.getStudentDao() : null;
}
-
generateConstructors
的用法
是否生成帶有全部參數的構造方法,默認值爲true。
@Generated(hash = 7034290)
public Student(Long mId, String mName, int mGender) {
this.mId = mId;
this.mName = mName;
this.mGender = mGender;
}
如果設置爲false,那麼會生成不帶參數的構造方法
public Student() {
}
-
generateGettersSetters
的用法
默認值爲true。是否生成getXXX和setXXX,這個比較簡單。
-
protobuf
的用法
GreenDao 支持 protocol buffer(protobuf) 協議數據的直接存儲,如果你通過 protobuf 協議與服務器交互,將不需要任何的映射。
可以參考一下我的另一篇博客
Android之協議緩衝區(Protocol Buffers)詳解
(8)基礎屬性註解
- @Id
被@Id
修飾的成員變量爲主鍵
,主鍵的基本數據類型必須是Long或者long,可以通過@Id(autoincrement = true)設置自增長,被@Id
修飾的成員變量也被稱之爲主鍵索引
。
@Entity
public class Student {
@Id(autoincrement = true)
private Long mId;
private String mName;
private int mGender;
...
}
- @Property
設置一個非默認關係映射所對應的列名,默認是的使用字段名。
比如:
@Property(nameInDb = "tName")
private String mName;
查看錶字段之後,發現mName字段變成了tName字段,如圖
- @NotNul
設置數據庫表當前列不能爲空。
@Id(autoincrement = true)
@NotNull
private Long mId;
@Property(nameInDb = "tName")
private String mName;
@NotNull
private int mGender;
- @Transient
添加次標記之後不會生成數據庫表的列。
- @Deprecated
元素已被棄用,程序員可以使用它,但不鼓勵使用,通常是因爲它很危險,或者因爲存在更好的選擇。編譯器在不推薦使用的程序元素在不推薦使用的代碼中使用或重寫。
- @Generated和@Keep
@Generated:
GreenDao運行所產生的構造函數或者方法,被此標註的代碼可以變更或者下次運行時清除。
@Keep:
註解的代碼段在GreenDao下次運行時保持不變。
- @Convert
GreenDao默認支持的類型參數有
boolean,Boolean,int, Integer,short,Short,long,Long,float,Float,double,Double,byte,Byte,byte[],String,Date
如果greenDao的默認參數類型滿足不了你的需求,比如你想定義一個顏色屬性,那麼你可以使用數據庫支持的原生數據類型通過PropertyConverter類轉換成你想要的顏色屬性。
@Convert(converter = ColorConverter.class, columnType = Integer.class)
private Color mColor;
public enum Color {
WHITE(0xffffff), BLACK(0x000000), YELLOW(0xfff000);
final int id;
Color(int id) {
this.id = id;
}
}
public static class ColorConverter implements PropertyConverter<Color, Integer> {
//將Integer值轉換成Color值
@Override
public Color convertToEntityProperty(Integer databaseValue) {
if (databaseValue == null) {
return null;
}
for (Color color : Color.values()) {
if (color.id == databaseValue) {
return color;
}
}
return Color.WHITE;
}
//將Color值轉換成Integer值
@Override
public Integer convertToDatabaseValue(Color entityProperty) {
return entityProperty == null ? null : entityProperty.id;
}
}
(9)索引註解
文章前面講到:
【一】在實體註解中可以配置索引集合,一個索引可以綁定一個或多個字段,並且只能創建常規索引和唯一索引。
【二】使用@Id修飾的字段爲主鍵,也可以稱之爲主鍵索引。
現在,我們來說明一個另外兩個有關索引的註解:@Index和@Unique
- @Index
該註解有三個屬性,分別是name ,value,unique ,如下
@Index(name = "ageName", value = "age", unique = false)
private int age;
name爲索引名稱,如果沒有指定,greendao將會默認命名一個名稱。
value爲字段名稱,必須和字段名稱一模一樣,如果單獨修飾某字段,可以不指定,即
@Index(name = "ageName", unique = false)
private int age;
unique 爲true時,是唯一索引,爲false時,是常規索引。
- @Unique
標誌位唯一索引
@Unique
private int age;
(10)關係註解
關係註解有兩種, 分別是@ToOne和@ToMany
- @ToOne 一對一的關係
實體和實體之間一對一的關係。
我們來舉一個學生和學生檔案的關係,一個學生對應一個學生檔案,一個學生檔案對應一個學生。
創建學生實體:
@Entity
public class Student {
@Id(autoincrement = true)
@NotNull
private Long mId;//表格唯一ID
@Unique
private String ID;//身份證
private int mAge;//年齡
private String hobby;//興趣愛好
...
}
創建學生檔案實體:
@Entity
public class StudentArchives {
@Id(autoincrement = true)
@NotNull
private Long mId;
@Unique
private String ID;//身份證
private String city;//城市
//...
}
以上兩個實體有一個共同的屬性,那就是身份證
,且是唯一的。那麼如何使兩個實體變成一對一的關係呢?
修改學生實體:
@Entity
public class Student {
@Id(autoincrement = true)
@NotNull
private Long mId;//表格唯一ID
@Unique
private String identityID;//身份證
private int mAge;//年齡
private String hobby;//興趣愛好
@ToOne(joinProperty = "identityID")//將身份證設置成外鍵,對應着學生檔案表中的主鍵
StudentArchives studentArchives;
}
修改學生檔案實體:
@Entity
public class StudentArchives {
@Id
@NotNull
private String identityID;//身份證
private String city;//城市
}
使用@ToOne註解來標識兩個實體一對一的關係,其中joinProperty的作用是:將學生表中的identityID
字段作爲外鍵約束
,關聯學生檔案表中的主鍵。
這樣兩個實體就關聯成一對一的關係了。
- @ToMany 一對多的關係
@ToMany定義兩個實體之間一對多的關係,如圖:
學生和班級是兩個實體,一個學生屬於一個班級,一個班級有多個學生。
下面分別創建兩個實體。
學生實體:
@Entity
public class Student {
@Id(autoincrement = true)
@NotNull
private Long mId;//表格唯一ID
@Unique
private String identityID;//身份證
private int mAge;//年齡
private String hobby;//興趣愛好
private Long classId;//班級ID
}
班級實體:
@Entity
public class StudentClass {
@Id(autoincrement = true)
@NotNull
private Long mId;
private String name;//班級名稱
@ToMany(referencedJoinProperty = "classId")//classId爲學生表中的classId字段,和班級表中的mId對應
@OrderBy(value = "mAge desc")
List<Student> list;
}
使用@ToMany來表示實體間一隊多的關係,referencedJoinProperty指定學生表中的“classId”字段,“classId”字段與班級表中的主鍵對應。
@OrderBy(value = "mAge desc")
指集合按照mAge字段倒序排序,如果需要正序排序,就改成:
@OrderBy(value = "mAge asc")
還有一種比較複雜的一對多的對應關係,即多個字段有一對多的關係,請往下看:
學生實體類:
@Entity
public class Student {
@Id(autoincrement = true)
@NotNull
private Long mId;//表格唯一ID
@Unique
private String identityID;//身份證
private int mAge;//年齡
private String hobby;//興趣愛好
private Long classId;//班級ID
private String className;//班級名稱
}
班級實體類:
@Entity
public class StudentClass {
@Id(autoincrement = true)
@NotNull
private Long mId;
private String name;//班級名稱
@ToMany(joinProperties = {
@JoinProperty(name = "mId", referencedName = "classId"),
@JoinProperty(name = "name", referencedName = "className")
})
@OrderBy(value = "mAge desc")
List<Student> list;
}
- @JoinEntity 多對多的關係
如果兩個實體是多對多的關係,那麼需要第三張表(表示兩個實體關係的表)
例如學生與課程是多對多關係,需要一個選課表來轉成兩個一對多的關係(學生與選課是一對多,課程與選課是一對多)
學生表:
@Entity
public class Student {
@Id(autoincrement = true)
@NotNull
private Long mId;//表格唯一ID
@Unique
private String identityID;//身份證
private int mAge;//年齡
@ToMany
@JoinEntity(
entity = Student_Course.class,
sourceProperty = "studentId",
targetProperty = "courseId"
)
private List<Course> list;
}
課程表:
@Entity
public class Course {
@Id(autoincrement = true)
@NotNull
private Long mId;//表格唯一ID
private String course;//課程
}
學生和課程之間的關係表:
@Entity
public class Student_Course {
@Id(autoincrement = true)
@NotNull
private Long mId;//表格唯一ID
private Long studentId;//學生ID
private Long courseId;//課程ID
}
(11)獲取數據庫
在Application中獲取或新建數據庫:
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "demo.db", null);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
daoSession = daoMaster.newSession();
public static DaoSession getDaoSession(){
return daoSession;
}
最終需要隨時可以獲取DaoSession對象,進而操作數據庫。
(12)插入數據
- 當主鍵類型是long(非Long)類型時,不支持自增長,實體如下:
@Entity
public class Student {
@Id
private long mId;//表格唯一ID
@Unique
private String identityID;//身份證
@Index(unique = true)
private int mAge;//年齡
private String hobby;//興趣愛好
private long classId;//班級ID
private String className;//班級名稱
}
當設置字段值時,必須設置mId的值,並且設置的值唯一,這裏設置當前時間戳,比如:
StudentDao studentDao = MyCustomApplication.getDaoSession().getStudentDao();
for(int i=0;i<20;i++){
Student student = new Student();
student.setHobby("打球");
student.setClassId(12 + i);
student.setIdentityID("320911199006728299"+i);
student.setClassName("強化班");
student.setMAge(12);
student.setMId(System.currentTimeMillis());
studentDao.insert(student);
}
- 當主鍵類型是Long(非long)類型時,支持自增長,實體如下:
@Entity
public class Student {
@Id
private Long mId;//表格唯一ID
@Unique
private String identityID;//身份證
@Index(unique = true)
private int mAge;//年齡
private String hobby;//興趣愛好
private long classId;//班級ID
private String className;//班級名稱
}
不管autoincrement爲true還是爲false時,主鍵都支持自增成長,也就是說autoincrement屬性無效(這是GreenDao的一個坑,希望在以後的版本會修復吧)
當然,如果設置了mId值,那麼將失去自增長特性,反之,如果沒有設置mId值,則可以實現自增長。自增長的mId的值從1開始遞增。不設置mId的代碼如下:
for(int i=0;i<20;i++){
Student student = new Student();
student.setHobby("打球");
student.setClassId(12 + i);
student.setIdentityID("320911199006728299"+i);
student.setClassName("強化班");
student.setMAge(12);
studentDao.insert(student);
}
插入之後的表格如下:
- 官方推薦的主鍵類型是Long或者long型,但是僅僅是推薦,並不代表一定需要按照推薦的來,實體如下:
@Entity
public class Student {
@Id
private String mId;//表格唯一ID
@Unique
private String identityID;//身份證
private int mAge;//年齡
private String hobby;//興趣愛好
private long classId;//班級ID
private String className;//班級名稱
}
由於主鍵是唯一的(不能重複),所以我們需要保證主鍵唯一,否則插入數據失敗,可以將UUID作爲唯一標識:
student.setMId(UUID.randomUUID().toString());
當主鍵不是long或者Long類型時,將不支持自增長。
使用@Unique註解的字段必須保證唯一,否則插入失敗。
具有唯一索引
@Index(unique = true)
的字段也必須保證唯一,否則插入失敗。插入操作的方法介紹
//這是最簡單的插入語句,新增一行數據,返回值爲行號
public long insert(T entity)
//傳遞一個數組,新增多行數據
public void insertInTx(T... entities)
//傳遞一個集合,新增多行數據
public void insertInTx(Iterable<T> entities)
//傳遞一個集合,新增多行數據,setPrimaryKey:是否設置主鍵
public void insertInTx(Iterable<T> entities, boolean setPrimaryKey)
//將給定的實體插入數據庫,若此實體類存在,則覆蓋
public long insertOrReplace(T entity)
//使用事務操作,將給定的實體插入數據庫,若此實體類存在,則覆蓋
public void insertOrReplaceInTx(T... entities)
//使用事務操作,將給定的實體插入數據庫,若此實體類存在,則覆蓋
public void insertOrReplaceInTx(Iterable<T> entities)
//使用事務操作,將給定的實體插入數據庫,若此實體類存在,則覆蓋,並設置是否設定主鍵
public void insertOrReplaceInTx(Iterable<T> entities, boolean setPrimaryKey)
//將給定的實體插入數據庫,但不設定主鍵
public long insertWithoutSettingPk(T entity)
insert和insertOrReplace的區別?
- insert只新增數據,如果主鍵字段的值重複或者唯一索引字段的值重複,那麼插入失敗。
- insertOrReplace新增數據或者替換數據,當主鍵重複時則直接替換數據,如果主鍵不同則插入數據。
總結:
如果保證主鍵唯一性?
- 當主鍵類型是Long時,不要給主鍵傳值,否則自增長特性失效。(autoincrement屬性無效)
@Id(autoincrement = true)
- 如果主鍵類型是long或者Long時,主鍵傳值可以是當前時間戳,可保證唯一。
student.setMId(System.currentTimeMillis());
3.如果主鍵類型是字符串時,主鍵傳值可以是UUID,可保證唯一。
student.setMId(UUID.randomUUID().toString());
(13)查詢
查詢分爲Dao查詢、
- Dao查詢
//根據主鍵來查詢一條數據
public T load(K key)
//根據行號來查詢一條數據,行號從1開始
public T loadByRowId(long rowId)
//查詢表中所有的數據
public List<T> loadAll()
- QueryBuilder 查詢
返回值爲QueryBuilder
的常見方法:
// 條件,AND 連接
public QueryBuilder<T> where(WhereCondition cond, WhereCondition... condMore)
// 條件,OR 連接
public QueryBuilder<T> whereOr(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore)
//去重
public QueryBuilder<T> distinct()
//分頁
public QueryBuilder<T> limit(int limit)
//偏移結果起始位,配合limit(int)使用
public QueryBuilder<T> offset(int offset)
//排序,升序
public QueryBuilder<T> orderAsc(Property... properties)
//排序,降序
public QueryBuilder<T> orderDesc(Property... properties)
// 排序,自定義
public QueryBuilder<T> orderCustom(Property property, String customOrderForProperty)
// 排序,SQL 語句
public QueryBuilder<T> orderRaw(String rawOrder)
//本地化字符串排序,用於加密數據庫無效
public QueryBuilder<T> preferLocalizedStringOrder()
//自定義字符串排序,默認不區分大小寫
public QueryBuilder<T> stringOrderCollation(String stringOrderCollation)
返回值爲WhereCondition
的常見方法:
//嵌套條件且,用法同and
public WhereCondition and(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore)
//嵌套條件或者,用法同or
public WhereCondition or(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore)
返回結果的方法:
//值返回一條結果,如果返回結果的數量>1,則報錯。如果返回結果的數量等於0,那麼返回null。
public T unique()
//值返回一條結果,如果返回結果的數量>1或=0,則報錯。
public T uniqueOrThrow()
//返回一個集合
public List<T> list()
//讓我們通過按需加載數據(懶惰)來迭代結果。數據未緩存。必須關閉
public CloseableListIterator<T> listIterator()
//實體按需加載到內存中。首次訪問列表中的元素後,將加載並緩存該元素以供將來使用。必須關閉。
public LazyList<T> listLazy()
//實體的“虛擬”列表:對列表元素的任何訪問都會導致從數據庫加載其數據。必須關閉。
public LazyList<T> listLazyUncached()
有關排序的方法:
//正序排序
public QueryBuilder<T> orderAsc(Property... properties)
//倒序排序
public QueryBuilder<T> orderDesc(Property... properties)
有關多表查詢的方法:
//多表查詢
public <J> Join<T, J> join(Class<J> destinationEntityClass, Property destinationProperty)
public <J> Join<T, J> join(Property sourceProperty, Class<J> destinationEntityClass)
public <J> Join<T, J> join(Property sourceProperty, Class<J> destinationEntityClass, Property destinationProperty)
public <J> Join<T, J> join(Join<?, T> sourceJoin, Property sourceProperty, Class<J> destinationEntityClass, Property destinationProperty)
條件判斷:
//等於
eq()
//不等於
notEq()
//值等於
like()
//取中間範圍
between()
//in命令
in()
//not in 命令
notIn()
//大於
gt()
//小於
lt()
//大於等於
ge()
//小於等於
le()
//爲空
isNull()
//不爲空
isNotNull()
【舉例一】
查詢所有數據
List<Student> list = studentDao.queryBuilder()
.list();
【舉例二】
根據條件查詢
List<Student> list = studentDao.queryBuilder()
.where(StudentDao.Properties.MAge.eq(12))
.where(StudentDao.Properties.MId.eq(1))
.list();
【舉例三】
查詢前10條數據,偏移量爲5
List<Student> list = studentDao.queryBuilder()
.offset(5)
.limit(10)
.list();
從第6條數據開始查詢,只返回10條數據。
【舉例四】
查詢className中有“強”的所有數據(模糊查詢)
List<Student> list = studentDao.queryBuilder()
.where(StudentDao.Properties.ClassName.like("%強%"))
.list();
這裏的%代表“強”前面和後面可能存在字符,如果改成
Properties.ClassName.like("強%")
表示,模糊查詢以“強”爲開頭的數據。
【舉例五】
返回按照年齡倒序排序之後的結果
List<Student> list = studentDao.queryBuilder()
.orderDesc(StudentDao.Properties.MAge)
.list();
【舉例六】
in操作符演示
List<Student> list = studentDao.queryBuilder()
.where(StudentDao.Properties.MId.in(10))
.list();
in的參數可以傳遞一個對象(如果傳遞基本數據類型,那麼自動轉成對象),也可以傳遞一個集合。
假設現在有一張表,如圖:
現在要求篩選出興趣愛好爲Hobby1和Hobby2的數據。
我們可以用whereOr和or操作符來實現
List<Student> list = studentDao.queryBuilder()
.whereOr(StudentDao.Properties.Hobby.eq("Hobby1"), StudentDao.Properties.Hobby.eq("Hobby2"))
.list();
當然,使用in操作符也能篩選出興趣愛好爲Hobby1和Hobby2的數據,代碼如下:
List<Student> list = studentDao.queryBuilder()
.where(StudentDao.Properties.Hobby.in("Hobby1", "Hobby2"))
.list();
其中文意思是,在Student表中篩選出Hobby字段中包含“Hobby1”和“Hobby2”的數據。
結果集合如下:
假設現在有兩張表,第一張是STUDENT表,第二張是STUDENT_CLASS表,如圖:
假設,查詢STUDENT表中“CLASS_NAME”字段值和STUDENT_CLASS表中的“NAME”字段值相同的數據。
代碼如下:
StudentClassDao studentClassDao = MyCustomApplication.getDaoSession().getStudentClassDao();
List<String> classNames = new ArrayList<>();
for(StudentClass studentClass : studentClassDao.queryBuilder().list()){
classNames.add(studentClass.getName());
}
StudentDao studentDao = MyCustomApplication.getDaoSession().getStudentDao();
List<Student> list = studentDao.queryBuilder()
.where(StudentDao.Properties.ClassName.in(classNames))
.list();
結果集合如下:
【舉例七】
使用sql語句查詢
List<Student> list = studentDao.queryBuilder()
.where(new WhereCondition.StringCondition("CLASS_NAME=?", "Class1"))
.list();
相當於sql語句
select * from STUDENT stu where stu.CLASS_NAME = "Class1"
還有一種寫法是:
List<Student> list = studentDao.queryRaw("where CLASS_NAME = ?", "Class1");
相當於sql語句
select * from STUDENT stu where stu.CLASS_NAME = "Class1"
【舉例八】
多表查詢(連接)
假設有兩張表,第一張是STUDENT表,第二張是STUDENT_CLASS表。
說到多表查詢,您可能會不由自主的想到表連接
,表連接
分爲三大類:
- 內連接
內連接又稱之爲普通連接或者自然連接。
sql語句如下:
//顯性
select * from STUDENT stu inner join STUDENT_CLASS stu_c on stu._id = stu_c._id where stu_c.NAME = "Class2"
或
//隱性
select * from STUDENT stu , STUDENT_CLASS stu_c on stu._id = stu_c._id where stu_c.NAME = "Class2"
結果集合如下:
- 外連接
外連接又分爲左外連接、右外連接和全外連接,我們一般說成左連接
、右連接
和全連接
。
【左連接】(左表取全部,右表取部分)
select * from STUDENT stu left join STUDENT_CLASS stu_c on stu._id = stu_c._id
或者
select * from STUDENT stu left outer join STUDENT_CLASS stu_c on stu._id = stu_c._id
結果集合如下
【右連接】(左表取部分,右表取全部)和【全連接】(左右兩表都取全部)
這個瞭解即可,sqlite不支持右連接和全連接。
- 交叉連接
交叉連接又被稱之爲笛卡爾積
。
sql語句:
select * from STUDENT stu cross join STUDENT_CLASS stu_c
笛卡爾積的結果相當於多表相乘的結果。STUDENT表有10條數據,STUDENT_CLASS有3條數據,那麼笛卡爾積的結果有10x3 = 30條數據。
以上三種連接就說到這裏,如果講的具體點篇幅比較長,不是本文的重點。之所以提到是因爲,接下來將講解GreenDao的多表查詢Join。
//多表查詢
public <J> Join<T, J> join(Class<J> destinationEntityClass, Property destinationProperty)
public <J> Join<T, J> join(Property sourceProperty, Class<J> destinationEntityClass)
public <J> Join<T, J> join(Property sourceProperty, Class<J> destinationEntityClass, Property destinationProperty)
public <J> Join<T, J> join(Join<?, T> sourceJoin, Property sourceProperty, Class<J> destinationEntityClass, Property destinationProperty)
GreenDao爲連接查詢(多表查詢)提供了四個構造方法,這四個構造方法其實是一樣的,所以我們不需要研究其中的區別。
GreenDao的Join接近於內連接,但又不像。內連接的結果集是包含兩表的所有字段,而GreenDao的Join只返回源表滿足條件的行。
參數說明:
sourceProperty:源表字段
destinationProperty:目標表字段
destinationEntityClass:目標表實體Class
sourceJoin:源連接(Join)
基本使用如圖:
如圖所示,從上到下總共有5個框:
第一個框確定了源表,如果你想要查詢的數據在A表,那麼就把A當做源表。
第二個框是源表的查詢條件,根據查詢條件生成一個新的結果集。
第四個框是目標表的查詢條件,根據查詢條件生成一個新的結果集。
第三個框確定了目標表,並將原表的結果集和目標表的結果集相連,相連之後就是一個Join,這個Join可以繼續和第三個表相連,形成一個新的Join。
第五個框就是連接(Join)之後的最終結果集,最終結果集爲源表的數據。
總結:GreenDao的連接查詢沒有內連接、外連接、交叉連接的概念,GreenDao的Join基本可以滿足一切多表查詢的需求。
【舉例九】
多表查詢(關係)
GreenDao不僅可以通過Join實現多表查詢,還可以通過關係
實現多表查詢,關係分爲一對一
、一對多
、多對多
,這三個概念前面已經說過了,這裏就用一對一關係來舉例。
一對一關係可以用@ToOne
註解來標誌,比如以下兩個實體:
@Entity
public class Student {
@Id
private Long mId;//表格唯一ID
@Unique
private String identityID;//身份證
private int mAge;//年齡
private String hobby;//興趣愛好
private long classId;//班級ID
private String className;//班級名稱
//...
}
@Entity
public class StudentClass {
@Id
private Long mId;
private String name;//班級名稱
private Long classId;//班級ID
@ToOne(joinProperty = "mId")//將身份證設置成外鍵,對應着學生檔案表中的主鍵
Student student;
//....
}
兩個實體之間存在一對一的關係(類似於兩個實體存在依賴關係),思路是:
先獲取StudentClass對象,再獲取Student對象,代碼如下:
StudentClassDao studentClassDao = MyCustomApplication.getDaoSession().getStudentClassDao();
StudentClass studentClass = studentClassDao.loadDeep(1l);
Student student = studentClass.getStudent();
因爲兩個實體存在一對一的關係,所以StudentClassDao自動生成了loadDeep和queryDeep這兩個方法。
(14)更新
public void update(T entity)
public void updateInTx(T... entities)
public void updateInTx(Iterable<T> entities)
以上三個方法很簡單,就是更新一條或者多條數據。
已有表格數據如下:
現在,如果想要將_id爲8的數據中的HOBBY修改爲“Hobby10”,代碼如下:
StudentDao studentDao = MyCustomApplication.getDaoSession().getStudentDao();
Student student = new Student();
student.setMId(Long.valueOf(8));
student.setHobby("Hobby10");
studentDao.update(student);
我們新建一個Student對象,其中主鍵不能爲空,因爲總是先根據主鍵來找到對應的數據,再進行更新操作。修改的效果如下:
我們發現,除了_id和HOBBY之外,其它字段的值都變化了,所以我們需要改進一下代碼。
StudentDao studentDao = MyCustomApplication.getDaoSession().getStudentDao();
Student student = studentDao.load(Long.valueOf(8));
student.setHobby("Hobby10");
studentDao.update(student);
更新後的最終效果如下:
(15)插入或更新
public void save(T entity)
public void saveInTx(T... entities)
public void saveInTx(Iterable<T> entities)
我們先查看一下源碼
public void save(T entity) {
if (hasKey(entity)) {
update(entity);
} else {
insert(entity);
}
}
傳入一個實體entity,如果entity的主鍵在表中已存在,則更新已有數據,反之,則插入新數據。
(16)刪除
- dao刪除
//根據實體,刪除一條數據(默認根據主鍵刪除數據)
public void delete(T entity)
//刪除所有數據
public void deleteAll()
//根據key刪除數據
public void deleteByKey(K key)
//刪除多條數據
public void deleteInTx(T... entities)
//根據主鍵數組,刪除多條數據
public void deleteByKeyInTx(K... keys)
//根據集合,刪除多條數據
public void deleteByKeyInTx(Iterable<K> keys)
- DeleteQuery
假如想要,刪除主鍵值爲10的數據
studentDao.queryBuilder()
.where(StudentDao.Properties.MId.eq(Long.valueOf(10)))
.buildDelete()
.executeDeleteWithoutDetachingEntities();
(17)開啓日誌
QueryBuilder.LOG_SQL = true;
QueryBuilder.LOG_VALUES = true;
(18)事務
插入,刪除、更新數據庫都是高風險操作,如果任務執行了一半突然中斷是非常危險的事情。這個時候可以使用事務,如果任務突然中斷可以回滾到初始狀態。如圖所示,GreenDao事務的處理已經幫我們做好了。
(19)RxDao、RxQuery、RxTransaction
GreenDao支持RxJava,不懂RxJava的同學建議學習一下。但是當前最新GreenDao版本是“3.2.2”,最新版本只支持RxJava1,不支持RxJava2。
我不使用RxJava1,所以就不介紹了。我就滿懷期待的等待GreenDao對RxJava2的支持,說真的,很期待,因爲RxJava可以使代碼更新優雅,線程控制更加完美,代碼也看起來簡潔。
(20)升級
在項目開發中,數據庫升級顯得尤爲重要。
在模塊下,我們已經配置了schema
greendao {
//版本號,升級時可配置
schemaVersion 1
daoPackage 'com.xxx.ooo.greendaodemo.gendao'
targetGenDir 'src/main/java'
}
我們發現數據庫當前版本號爲1,如果改成2,就會執行升級代碼,GreenDao3.0已經幫我們自動生成了升級代碼,首先在DaoMaster找到以下代碼
/** 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);
}
}
當數據庫版本有變化時,就會執行onUpgrade回調方法,默認的升級邏輯是,先刪除所有的表格,再重新創建所有的表格
dropAllTables(db, true);
onCreate(db);
這個操作是愚蠢的,數據庫升級怎麼能允許刪除原有數據?
所以,我們必須重寫onUpgrade方法,具體代碼如下:
public class MyDevOpenHelper extends DaoMaster.DevOpenHelper {
public MyDevOpenHelper(Context context, String name) {
super(context, name);
}
public MyDevOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
upgradeToVersion2(db);
case 2:
upgradeToVersion3(db);
default:
break;
}
}
/**
* 更新到版本2
* @param db
*/
private void upgradeToVersion2(Database db){
Log.d("aaa", "更新到版本2");
}
/**
* 更新到版本3
* @param db
*/
private void upgradeToVersion3(Database db){
Log.d("aaa", "更新到版本3");
}
}
MyDevOpenHelper helper = new MyDevOpenHelper(this, "demo.db");
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
daoSession = daoMaster.newSession();
以上代碼已經可以實現數據庫的升級操作了,當數據庫新增字段時,可以在upgradeToVersionX方法裏面去新增一個字段,但是這樣做有兩個很明顯的缺點
- 每次數據庫變化,都要編寫代碼做升級操作;
- 萬一在某次升級代碼寫錯了,升級之後是一個高危風險;
那麼有沒有什麼辦法實現自動升級呢?
答案是有的。
接下來,我要粘貼一下網上現成的工具類
/**
*
* please call {@link #migrate(SQLiteDatabase, Class[])} or {@link #migrate(Database, Class[])}
*
*/
public final class MigrationHelper {
public static boolean DEBUG = false;
private static String TAG = "MigrationHelper";
private static final String SQLITE_MASTER = "sqlite_master";
private static final String SQLITE_TEMP_MASTER = "sqlite_temp_master";
private static WeakReference<ReCreateAllTableListener> weakListener;
public interface ReCreateAllTableListener{
void onCreateAllTables(Database db, boolean ifNotExists);
void onDropAllTables(Database db, boolean ifExists);
}
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(SQLiteDatabase db, ReCreateAllTableListener listener, Class<? extends AbstractDao<?, ?>>... daoClasses) {
weakListener = new WeakReference<>(listener);
migrate(db, daoClasses);
}
public static void migrate(Database database, ReCreateAllTableListener listener, Class<? extends AbstractDao<?, ?>>... daoClasses) {
weakListener = new WeakReference<>(listener);
migrate(database, daoClasses);
}
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) {
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");
}
private static void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
for (int i = 0; i < daoClasses.length; i++) {
String tempTableName = null;
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String tableName = daoConfig.tablename;
if (!isTableExists(db, false, tableName)) {
printLog("【New Table】" + tableName);
continue;
}
try {
tempTableName = daoConfig.tablename.concat("_TEMP");
StringBuilder dropTableStringBuilder = new StringBuilder();
dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName).append(";");
db.execSQL(dropTableStringBuilder.toString());
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("CREATE TEMPORARY TABLE ").append(tempTableName);
insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";");
db.execSQL(insertTableStringBuilder.toString());
printLog("【Table】" + tableName +"\n ---Columns-->"+getColumnsStr(daoConfig));
printLog("【Generate temp table】" + tempTableName);
} catch (SQLException e) {
Log.e(TAG, "【Failed to generate temp table】" + tempTableName, e);
}
}
}
private static boolean isTableExists(Database db, boolean isTemp, String tableName) {
if (db == null || TextUtils.isEmpty(tableName)) {
return false;
}
String dbName = isTemp ? SQLITE_TEMP_MASTER : SQLITE_MASTER;
String sql = "SELECT COUNT(*) FROM " + dbName + " WHERE type = ? AND name = ?";
Cursor cursor=null;
int count = 0;
try {
cursor = db.rawQuery(sql, new String[]{"table", tableName});
if (cursor == null || !cursor.moveToFirst()) {
return false;
}
count = cursor.getInt(0);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
}
return count > 0;
}
private static String getColumnsStr(DaoConfig daoConfig) {
if (daoConfig == null) {
return "no columns";
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < daoConfig.allColumns.length; i++) {
builder.append(daoConfig.allColumns[i]);
builder.append(",");
}
if (builder.length() > 0) {
builder.deleteCharAt(builder.length() - 1);
}
return builder.toString();
}
private static void dropAllTables(Database db, boolean ifExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
reflectMethod(db, "dropTable", ifExists, daoClasses);
printLog("【Drop all table by reflect】");
}
private static void createAllTables(Database db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
reflectMethod(db, "createTable", ifNotExists, daoClasses);
printLog("【Create all table by reflect】");
}
/**
* dao class already define the sql exec method, so just invoke it
*/
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();
}
}
private static void restoreData(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
for (int i = 0; i < daoClasses.length; i++) {
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String tableName = daoConfig.tablename;
String tempTableName = daoConfig.tablename.concat("_TEMP");
if (!isTableExists(db, true, tempTableName)) {
continue;
}
try {
// get all columns from tempTable, take careful to use the columns list
List<TableInfo> newTableInfos = TableInfo.getTableInfo(db, tableName);
List<TableInfo> tempTableInfos = TableInfo.getTableInfo(db, tempTableName);
ArrayList<String> selectColumns = new ArrayList<>(newTableInfos.size());
ArrayList<String> intoColumns = new ArrayList<>(newTableInfos.size());
for (TableInfo tableInfo : tempTableInfos) {
if (newTableInfos.contains(tableInfo)) {
String column = '`' + tableInfo.name + '`';
intoColumns.add(column);
selectColumns.add(column);
}
}
// NOT NULL columns list
for (TableInfo tableInfo : newTableInfos) {
if (tableInfo.notnull && !tempTableInfos.contains(tableInfo)) {
String column = '`' + tableInfo.name + '`';
intoColumns.add(column);
String value;
if (tableInfo.dfltValue != null) {
value = "'" + tableInfo.dfltValue + "' AS ";
} else {
value = "'' AS ";
}
selectColumns.add(value + column);
}
}
if (intoColumns.size() != 0) {
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("REPLACE INTO ").append(tableName).append(" (");
insertTableStringBuilder.append(TextUtils.join(",", intoColumns));
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(TextUtils.join(",", selectColumns));
insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");
db.execSQL(insertTableStringBuilder.toString());
printLog("【Restore data】 to " + tableName);
}
StringBuilder dropTableStringBuilder = new StringBuilder();
dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
db.execSQL(dropTableStringBuilder.toString());
printLog("【Drop temp table】" + tempTableName);
} catch (SQLException e) {
Log.e(TAG, "【Failed to restore data from temp table 】" + tempTableName, e);
}
}
}
private static List<String> getColumns(Database db, String tableName) {
List<String> columns = null;
Cursor cursor = null;
try {
cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 0", null);
if (null != cursor && cursor.getColumnCount() > 0) {
columns = Arrays.asList(cursor.getColumnNames());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
if (null == columns)
columns = new ArrayList<>();
}
return columns;
}
private static void printLog(String info){
if(DEBUG){
Log.d(TAG, info);
}
}
private static class TableInfo {
int cid;
String name;
String type;
boolean notnull;
String dfltValue;
boolean pk;
@Override
public boolean equals(Object o) {
return this == o
|| o != null
&& getClass() == o.getClass()
&& name.equals(((TableInfo) o).name);
}
@Override
public String toString() {
return "TableInfo{" +
"cid=" + cid +
", name='" + name + '\'' +
", type='" + type + '\'' +
", notnull=" + notnull +
", dfltValue='" + dfltValue + '\'' +
", pk=" + pk +
'}';
}
private static List<TableInfo> getTableInfo(Database db, String tableName) {
String sql = "PRAGMA table_info(" + tableName + ")";
printLog(sql);
Cursor cursor = db.rawQuery(sql, null);
if (cursor == null)
return new ArrayList<>();
TableInfo tableInfo;
List<TableInfo> tableInfos = new ArrayList<>();
while (cursor.moveToNext()) {
tableInfo = new TableInfo();
tableInfo.cid = cursor.getInt(0);
tableInfo.name = cursor.getString(1);
tableInfo.type = cursor.getString(2);
tableInfo.notnull = cursor.getInt(3) == 1;
tableInfo.dfltValue = cursor.getString(4);
tableInfo.pk = cursor.getInt(5) == 1;
tableInfos.add(tableInfo);
// printLog(tableName + ":" + tableInfo);
}
cursor.close();
return tableInfos;
}
}
}
MigrationHelper的使用方法如下:
public class MyDevOpenHelper extends DaoMaster.DevOpenHelper {
public MyDevOpenHelper(Context context, String name) {
super(context, name);
}
public MyDevOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
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);
}
}, StudentDao.class, StudentClassDao.class);
}
}
以上代碼可以實現數據庫的自動升級(無論是刪除字段還是新增字段)
(21)其它
- 當數據庫的數據有很多時,比如一百萬條數據
【一】
GreenDao是有內存的,如果插入上萬條數據,GreenDao本身的緩存設計導致內存佔用比較大,我們可以清空Session中的緩存
daoSession.clear();
或者清空Dao中的緩存
mBookDao.detachAll();
當然,清除緩存的存在可不僅僅是因爲佔用了內存,有時候在項目中,您會發現查詢到的數據竟然是內存中修改後的數據,而不是數據庫中的數據,這個時候可以清除緩存來解決。
【二】
當您從100萬條數據中查找某個條件(比如:age > 15),返回所有滿足條件的數據。由於數據太多,查詢所消耗的時間比較長,這個時候,您可以將age字段添加爲索引,以增加查詢速度。
【三】
當您插入或刪除數據時,建議先刪除索引,再進行插入或刪除操作,最後再添加索引。因爲一邊插入或刪除數據一邊維護索引所消耗的資源是恐怖的。
【四】
當查詢、插入、刪除、修改數據量比較龐大時,所花費的時間是比較多的,這時,您可以考慮在異步線程執行,防止卡UI線程,文章前面提到過的RxJava可以對線程進行控制。除了RxJava能控制線程之外,DaoSession也可以開啓線程,代碼如下:
MyCustomApplication.getDaoSession().runInTx(new Runnable() {
@Override
public void run() {
}
});
只是這種方式沒有RxJava優雅罷了。
- 多用戶的情況
可以採用多用戶多數據庫的設計方案(即每個用戶都有獨立的數據庫)。
[本章完...]