個人博客:haichenyi.com。感謝關(guān)注
??本篇介紹android常用的數(shù)據(jù)庫之一GreenDao的簡單用法,增刪改查?;谇懊娲畹目蚣埽?dāng)然,你也可以選擇不用??炊梅ㄖ?,用起來很方便。GreenDao數(shù)據(jù)庫升級到3.0版本之后api用起來更加方便了,便于讓開發(fā)人員專注于業(yè)務(wù)邏輯。我需要額外說明的是,我把之前的項目框架轉(zhuǎn)成了kotlin,不會kotlin語法的同學(xué),可以去研究一下。
添加依賴
最終,我們要添加如下代碼,效果圖如下:

第一步
??打開的你根目錄下面的build.gradle文件,也就是項目下面的,并不是app目錄下面的build.gradle。
// In your root build.gradle file:
buildscript {
repositories {
jcenter()
...//其他你自己的
mavenCentral() // add repository
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
...//其他你自己的
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin
}
}
第二步
??打開你的項目下面的build.gradle文件,也就是你的app目錄下面的,之前我們添加依賴的時候的那個文件
// In your app projects build.gradle file:
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' // apply plugin
dependencies {
compile 'org.greenrobot:greendao:3.2.2' // add library
}
??完成上面兩步,辣么,關(guān)于greendao的依賴我們就添加完成了
初始化
??我們首先得有一個bean類,這個bean類對應(yīng)的就是數(shù)據(jù)庫表的表結(jié)構(gòu)。我這里想說明的是(敲黑板了),看到了很多網(wǎng)上說的什么主鍵id必須用Long類型,這種說法是不準(zhǔn)確的,準(zhǔn)確的說,你的主鍵字段名稱,如果是“id”,辣么,你這個字段“id”,必須用Long類型,如果你換一個名稱,比方說“myId”,辣么,你就不必用Long類型,這個問題,說大不大,說小,又困擾了我有一會。我這里新建用戶表,就需要一個User的java bean類。如下:
package com.haichenyi.myproject.model.bean;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.NotNull;
import org.greenrobot.greendao.annotation.Property;
import org.greenrobot.greendao.annotation.Transient;
/**
* Author: 海晨憶
* Date: 2018/2/24
* Desc: 用戶表的bean類
*/
@Entity
public class User {
@Id
private String name;
private int sex;
@Property(nameInDb = "Height")
private int height;
private String weight;
@NotNull
private int age;
@Transient
private String character;
}
??這里我把幾個常用的注解都寫出來了,說一下這幾個注解是什么意思
| 注解 | 意義 |
|---|---|
| @Entity | 用于標(biāo)識這是一個需要Greendao幫我們生成代碼的bean |
| @Id | 標(biāo)明主鍵,括號里可以指定是否自增 |
| @Property | 用于設(shè)置屬性在數(shù)據(jù)庫中的列名(默認(rèn)不寫就是保持一致) |
| @NotNull | 非空 |
| @Transient | 標(biāo)識這個字段是自定義的不會創(chuàng)建到數(shù)據(jù)庫表里 |
簡單的講一下:
@Entity:標(biāo)識的bean類,我們在運行的時候,greendao會自動幫我們生成對應(yīng)的表
@Id:標(biāo)識的字段就是這個表對應(yīng)的主鍵
@Property:標(biāo)識的字段在表中對應(yīng)的那一欄的名稱是后面括號里面的,這個表height字段對應(yīng)表中的Height,一般我們直接設(shè)置成默認(rèn)就可以了
@NotNull:標(biāo)識的字段,這個字段在表中不能為空,不然就出錯,所以,在添加數(shù)據(jù)的時候設(shè)置默認(rèn)值
@Transient:標(biāo)識的字段,在生成表的時候不會生成對應(yīng)的字段。這個什么時候用呢?這個,我一般用作標(biāo)記flag,比方說,從數(shù)據(jù)庫拿數(shù)據(jù),又不想重新寫一個bean類,就用這個bean類,RecyclerView,填充完數(shù)據(jù),item點擊的時候,狀態(tài)發(fā)生變化,我們要有一個flag,就通過修改這個字段的值,頁面做出相應(yīng)的變化。
??寫到這里,我們的bean類也有了,要怎么生成數(shù)據(jù)庫呢?在生成數(shù)據(jù)庫之前,我們先把項目重新clean一遍,再build一遍,看到你剛寫的需要生成表的bean類變成了如下樣子:
package com.haichenyi.myproject.model.bean;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.NotNull;
import org.greenrobot.greendao.annotation.Property;
import org.greenrobot.greendao.annotation.Transient;
import org.greenrobot.greendao.annotation.Generated;
/**
* Author: 海晨憶
* Date: 2018/2/24
* Desc: 用戶表的bean類
*/
@Entity
public class User {
@Id
private String name;
private int sex;
@Property(nameInDb = "Height")
private int height;
private String weight;
@NotNull
private int age;
@Transient
private String character;
@Generated(hash = 717717955)
public User(String name, int sex, int height, String weight, int age) {
this.name = name;
this.sex = sex;
this.height = height;
this.weight = weight;
this.age = age;
}
@Generated(hash = 586692638)
public User() {
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getSex() {
return this.sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public int getHeight() {
return this.height;
}
public void setHeight(int height) {
this.height = height;
}
public String getWeight() {
return this.weight;
}
public void setWeight(String weight) {
this.weight = weight;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
??如上,greendao通過注解的方式幫我們自動生成了set/get方法,還有構(gòu)造方法,這就對了,我們不用關(guān),之后我們再執(zhí)行如下代碼生成數(shù)據(jù)庫和表:
DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(getApplicationContext(), "haichenyi.db", null);
DaoMaster daoMaster = new DaoMaster(devOpenHelper.getWritableDb());
DaoSession daoSession = daoMaster.newSession();
??通過 DaoMaster 的內(nèi)部類 DevOpenHelper,你可以得到一個便利的 SQLiteOpenHelper 對象??赡苣阋呀?jīng)注意到了,你并不需要去編寫「CREATE TABLE」這樣的 SQL 語句,因為 greenDAO 已經(jīng)幫你做了。注意:默認(rèn)的 DaoMaster.DevOpenHelper會在數(shù)據(jù)庫升級時,刪除所有的表,意味著這將導(dǎo)致數(shù)據(jù)的丟失。所以,在正式的項目中,你還應(yīng)該做一層封裝,來實現(xiàn)數(shù)據(jù)庫的安全升級。升級的問題,我們在后面講,這里我們先把數(shù)據(jù)庫和表先創(chuàng)建了。
??上面這個方式是java格式的,由于,我昨天寫完框架之后,我把項目轉(zhuǎn)成了kotlin代碼,所以這里有點不一樣,項目我后面會上傳。這里我要說明的是(敲黑板)我用kotlin的時候,碰到了一個問題,當(dāng)我使用greendao的時候,他提示我,無法引入用注解方式生成的類,dagger2也是一樣的,我用java代碼寫就沒有問題,我寫這篇博客的時候,目前還沒有找到解決的辦法。

??我用了另外一種方式,采用跟之前網(wǎng)絡(luò)請求一樣的設(shè)計模式——裝飾者模式。我這里就不多做說明了。我貼出我的代碼。
SqlHelper
package com.haichenyi.myproject.model.sql;
/**
* Author: 海晨憶
* Date: 2018/2/24
* Desc:
*/
public interface SqlHelper {
void onAdd();
void onDelete();
void onUpdate();
void onSelect();
}
??這里定義增刪改查4個方法,用于測試這4個功能
SqlImpl
package com.haichenyi.myproject.model.sql;
import com.haichenyi.myproject.base.MyApplication;
import com.haichenyi.myproject.model.bean.DaoMaster;
import com.haichenyi.myproject.model.bean.DaoSession;
import com.haichenyi.myproject.model.bean.UserDao;
import com.haichenyi.myproject.utils.ToastUtils;
/**
* Author: 海晨憶
* Date: 2018/2/24
* Desc:
*/
public class SqlImpl implements SqlHelper {
private final UserDao userDao;
/**
* 初始化Sql Dao.
*
* @param application {@link MyApplication}
*/
public SqlImpl(MyApplication application) {
SqlOpenHelper helper = new SqlOpenHelper(application, "haichenyi.db");
DaoSession daoSession = new DaoMaster(helper.getWritableDb()).newSession();
userDao = daoSession.getUserDao();
}
@Override
public void onAdd() {
ToastUtils.Companion.showTipMsg("增加數(shù)據(jù)");
}
@Override
public void onDelete() {
ToastUtils.Companion.showTipMsg("刪除數(shù)據(jù)");
}
@Override
public void onUpdate() {
ToastUtils.Companion.showTipMsg("更新數(shù)據(jù)");
}
@Override
public void onSelect() {
ToastUtils.Companion.showTipMsg("查詢數(shù)據(jù)");
}
}
??功能實現(xiàn)類,看到他的構(gòu)造方法里面,第二個參數(shù)就是我們的數(shù)據(jù)庫名稱,后面通過getWritableDb()獲取的是可寫的數(shù)據(jù)庫,可寫就肯定可讀。然后就是接口的實現(xiàn)類了,這里就是具體的增刪改查功能的實現(xiàn)類,我這里在對應(yīng)的方法里面就寫了Toast,增刪改查具體怎么寫后面再說
SqlOpenHelper
package com.haichenyi.myproject.model.sql;
import android.content.Context;
import com.haichenyi.myproject.model.bean.DaoMaster;
import com.haichenyi.myproject.model.bean.UserDao;
import org.greenrobot.greendao.database.Database;
/**
* Author: 海晨憶
* Date: 2018/2/24
* Desc:
*/
public class SqlOpenHelper extends DaoMaster.OpenHelper {
public SqlOpenHelper(Context context, String name) {
super(context, name);
}
@SuppressWarnings("unchecked")
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
super.onUpgrade(db, oldVersion, 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);
}
}, UserDao.class);
}
}
??這個類用于管理數(shù)據(jù)庫的表對應(yīng)的字段發(fā)生變化的時候,數(shù)據(jù)庫需要進(jìn)行的版本更新,連上下面那個類,都是用于版本數(shù)據(jù)庫版本更新的,防止數(shù)據(jù)丟失。怎么寫呢?看到最后面的?UserDao.class?了嗎?這個就是我們需要更新的表,你哪個表需要更新,直接寫在后面就可以了,這個是可以一次傳多個表的,并不是一次只能傳一個
MigrationHelper
package com.haichenyi.myproject.model.sql;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.StandardDatabase;
import org.greenrobot.greendao.internal.DaoConfig;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Author: 海晨憶.
* Date: 2018/2/24
* Desc:
*/
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<String> columns = getColumns(db, tempTableName);
ArrayList<String> properties = new ArrayList<>(columns.size());
for (int j = 0; j < daoConfig.properties.length; j++) {
String columnName = daoConfig.properties[j].columnName;
if (columns.contains(columnName)) {
properties.add("`" + columnName + "`");
}
}
if (properties.size() > 0) {
final String columnSQL = TextUtils.join(",", properties);
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("REPLACE INTO ").append(tableName).append(" (");
insertTableStringBuilder.append(columnSQL);
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(columnSQL);
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);
}
}
}
??這個類是工具類,拿過去用就好了,還有就是,應(yīng)用怎么判斷是否需要版本更新呢?打開你的app下面的build.grade,在根結(jié)點下面添加如下代碼:
greendao {
schemaVersion 1
}
每當(dāng)你發(fā)布新版本的時候,把這個版本號+1即可。
??當(dāng)然,我門這里依然是用的dagger生成的全局單例,所以,你還需要在你的AppModule下面添加如下代碼:
@Provides
@Singleton
SqlHelper provideSqlHelper() {
return new SqlImpl(application);
}
??記得把項目重新clean一遍,build一遍,重新跑項目的時候,找到你的數(shù)據(jù)庫。data-data-你的應(yīng)用包名-databases-haichenyi.db,這個就是我們的數(shù)據(jù)庫。找個Sqlite可視化工具打開,你會看到如下結(jié)構(gòu)。

??太多了,不寫了,下一篇寫增刪改查。