GreenDao数据库


前言

成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。

这段时间在将公司的sql数据库使用第三方的开源数据库替代,综合比较了各个热门的数据库之后,最终还是敲定了greendao,原因不多说,综合性能之王,当然,realm和DBFlow也不错。后续会出相关的文章介绍一下,接下来,就让我们回归正题



image

先给大家灌输点==洪(dou)荒(bi)之力==,好的,正式开始。。

一、GreenDao和Realm对比。

在数据量较小的情况下,GreenDao和Realm相差无几,在数据量超过1000条时,GreenDao在删除方面优势很大,Realm在插入和查询方面优势很大,因此,要看需求选择相应的数据库。

二、GreenDao配置

//根build.gradle
buildscript {  
    repositories {  
        mavenCentral()  
    }  
    dependencies {  
        classpath 'org.greenrobot:greendao-gradle-plugin:3.0.0'//1
    }  
}  

//module下build.gradle
apply plugin: 'com.android.application'  
apply plugin: 'org.greenrobot.greendao'//2

//3
greendao {  
    schemaVersion 1  
    daoPackage 'com.example.anonymous.greendao'  
    targetGenDir 'src/main/java'  
}  

android {  
    compileSdkVersion 25  
    buildToolsVersion "25.0.2"  
    defaultConfig {  
        applicationId "com.example.anonymous.realmdemo"  
        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'])  
    compile 'com.android.support:appcompat-v7:25.0.1'  
    //4
    compile'org.greenrobot:greendao:3.0.1'  
    compile'org.greenrobot:greendao-generator:3.0.0'  
}  

三、编写实体类,对应的是数据库的每一张表

@Entity  
public class User {  
    @Id  
    private Long id;  
    private String name;  
} 

@Entity:将我们的java普通类变为一个能够被greenDAO识别的数据库类型的实体类

@Id:通过这个注解标记的字段必须是Long类型的,这个字段在数据库中表示它就是主键,并且它默认就是自增的

然后,点击Make Project(Ctrl + F9),然后GreenDao为你生成了3个类和一些代码,
DaoMaster、DaoSession、与User实体对应的UserDao以及User实体类的构造方法和其字段对应的get、set方法。
其中这三个生成的类的位置是在你app的build.gradle中daoPackage字段指明的位置,如果没有设置这个字段值,默认是在User对应的包下生成的。
注意:如果第二次定义一个实体Age类,点击Make Project,只会生成对应的AgeDao。

好,忙活了这么久,终于可以办正事了,不急,先祝贺一下咱们把扯蛋的配置弄完了



image

四、GreenDao增删改查

DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(MyApplication.getContext(), "my-db", null);   
DaoMaster daoMaster = new DaoMaster(devOpenHelper.getWritableDatabase());
DaoSession daoSession = daoMaster.newSession();    

注意:得到这个daoSession对象就可以获取到任意一张表单的Dao类进行增删改查操作了。

咳咳,在讲GreenDao的增删改查之前,咱们现在整一整GreenDao为我们生成的DaoMaster、DaoSession的概念以及他们的关系,废话不多说,直接上(tou)图(lan):



image

上图中,DaoMater包含了DevOpenHelper,其中createAllTable(db, false)和dropAllTable(db, ture)也是在DaoMster里面,DaoMaster负责创建DaoSession,
DaoSession负责创建和管理每一张表单对应的XyzDao类,XyzDao负责对对应的XyzEntity进行增删改查操作,下面,开始增删改查:

1.增

insert
insertOrReplace
insertInTx
insertOrReplaceInTx

insert与insertOrReplace的区别:

如果数据库中已有要插入的数据,那么上面的插入方法就会失败,可以调用insertOrReplace和insertOrReplaceInTx方法。跟踪源码可以发现,insert最终执行的sql语句是”INSERT INTO …”,而insertOrReplace执行的sql语句是”INSERT OR REPLACE INTO …”。

insert加上尾缀InTx则是批量插入的方式。

2.删

delete
deleteAll
deleteByKey (注意:目前版本仅仅支持拥有单个主键的表单使用此方法)
deleteInTx
deleteByKeyInTx

3.改

update  
updateInTx

4.最重要的就是查了,来一大波干货。。

在android中进行查询:
queryRaw
queryRawCreate
queryRawCreateListArgs
以上三个都是原生sql语句查询
offer 查询的起始位置
limit 查询的条目数量
查询所有:List<Son>list=mSonDao.queryBuilder().listLazy();//.list()
按属性查询:Son nate=mSonDao.queryBuilder().where(SonDao.Properties.Name.eq("senddi")).unique();
匹配符查询:List tomkin=mSonDao.queryBuilder().where(SonDao.Properties.Name.like("tomkin% ")).list();
范围(区间)查询:List sons=mSonDao.queryBuilder().where(SonDao.Properties.Age.between(20,30)).list();
范围(大于)查询:List data=mSonDao.queryBuilder().where(SonDao.Properties.Age.gt(18)).list();
范围(小于)查询:List data=mSonDao.queryBuilder().where(SonDao.Properties.Age.lt(19)).list();
范围(不等于)查询:List data=mSonDao.queryBuilder().where(SonDao.Properties.Age.notEq(19)).list();
范围(大于等于)查询:List data=mSonDao.queryBuilder().where(SonDao.Properties.Age.ge(19)).list();
升序查询:List<Son> data=mSonDao.queryBuilder().orderAsc(SonDao.Properties.Age).list();
降序查询:List<Son> data=mSonDao.queryBuilder().orderDesc(SonDao.Properties.Age).list();

与原始的SQL语句结合:(年龄在45岁一下的父亲的ID)
List data=mSonDao.queryBuilder().where(
new WhereCondition.StringCondition("FATHER_UD IN" +
                        "(SELECT _ID FROM FATHER WHERE AGE<45)")).list();

(⊙v⊙)嗯。。最后再提一点,greenDAO借助SQLiteStatement完成了数据的插入,避免了其他框架利用反射拼装sql语句而造成的执行效率低下的问题,这也是greenDao成为性能之王的主要原因之一。

总结:
  • 使用了静态代码生成,不通过反射运行时生成代理类,提升了性能
  • 使用了SQLiteStatement
  • 同时提供了同步和异步的数据库操作方式
  • 数据库提供内存缓存,更高效的查询
  • 基于sqlcipher提供加密数据库
  • 提供RxJava的API,异步操作更高效

五、数据库升级

在这里,我提一点,数据库升级的主要目的是为了保存升级前的数据,比如说,我在表Date中新加了一个CurrentTime字段,为了使以前的Date表单数据能保存下来,同时还要在每一个Date表单新加一个CurrentTime字段,此时,数据库就需要升级。

比方说,我现在要加一个字段,升级步骤如下:

1、需要一个MigrationHelper.java类,StackOverFlow的专家写的,如下:

/**
* Created by pokawa on 18/05/15
*/

public class MigrationHelper {

    private static final String CONVERSION_CLASS_NOT_FOUND_EXCEPTION = "MIGRATION HELPER - CLASS DOESN'T MATCH WITH THE CURRENT PARAMETERS";
    private static MigrationHelper instance;

    public static MigrationHelper getInstance() {
        if(instance == null) {
            instance = new MigrationHelper();
        }
        return instance;
    }

    public void migrate(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        generateTempTables(db, daoClasses);
        DaoMaster.dropAllTables(new StandardDatabase(db), true);
        DaoMaster.createAllTables(new StandardDatabase(db), false);
        restoreData(db, daoClasses);
    }

    private void generateTempTables(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        for(int i = 0; i < daoClasses.length; i++) {
            DaoConfig daoConfig = new DaoConfig(new StandardDatabase(db), daoClasses[i]);

            String divider = "";
            String tableName = daoConfig.tablename;
            String tempTableName = daoConfig.tablename.concat("_TEMP");
            ArrayList<String> properties = new ArrayList<>();

            StringBuilder createTableStringBuilder = new StringBuilder();

            createTableStringBuilder.append("CREATE TABLE ").append(tempTableName).append(" (");

            for(int j = 0; j < daoConfig.properties.length; j++) {
                String columnName = daoConfig.properties[j].columnName;

                if(getColumns(db, tableName).contains(columnName)) {
                    properties.add(columnName);

                    String type = null;

                    try {
                        type = getTypeByClass(daoConfig.properties[j].type);
                    } catch (Exception exception) {
//                        Crashlytics.logException(exception);
                    }

                    createTableStringBuilder.append(divider).append(columnName).append(" ").append(type);

                    if(daoConfig.properties[j].primaryKey) {
                        createTableStringBuilder.append(" PRIMARY KEY");
                    }

                    divider = ",";
                }
            }
            createTableStringBuilder.append(");");

            db.execSQL(createTableStringBuilder.toString());

            StringBuilder insertTableStringBuilder = new StringBuilder();

            insertTableStringBuilder.append("INSERT INTO ").append(tempTableName).append(" (");
            insertTableStringBuilder.append(TextUtils.join(",", properties));
            insertTableStringBuilder.append(") SELECT ");
            insertTableStringBuilder.append(TextUtils.join(",", properties));
            insertTableStringBuilder.append(" FROM ").append(tableName).append(";");

            db.execSQL(insertTableStringBuilder.toString());
        }
    }

    private void restoreData(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        for(int i = 0; i < daoClasses.length; i++) {
            DaoConfig daoConfig = new DaoConfig(new StandardDatabase(db), daoClasses[i]);

            String tableName = daoConfig.tablename;
            String tempTableName = daoConfig.tablename.concat("_TEMP");
            ArrayList<String> properties = new ArrayList();

            for (int j = 0; j < daoConfig.properties.length; j++) {
                String columnName = daoConfig.properties[j].columnName;

                if(getColumns(db, tempTableName).contains(columnName)) {
                    properties.add(columnName);
                }
            }

            StringBuilder insertTableStringBuilder = new StringBuilder();

            insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" (");
            insertTableStringBuilder.append(TextUtils.join(",", properties));
            insertTableStringBuilder.append(") SELECT ");
            insertTableStringBuilder.append(TextUtils.join(",", properties));
            insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");

            StringBuilder dropTableStringBuilder = new StringBuilder();

            dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);

            db.execSQL(insertTableStringBuilder.toString());
            db.execSQL(dropTableStringBuilder.toString());
        }
    }

    private String getTypeByClass(Class<?> type) throws Exception {
        if(type.equals(String.class)) {
            return "TEXT";
        }
        if(type.equals(Long.class) || type.equals(Integer.class) || type.equals(long.class)) {
            return "INTEGER";
        }
        if(type.equals(Boolean.class)) {
            return "BOOLEAN";
        }

        Exception exception = new Exception(CONVERSION_CLASS_NOT_FOUND_EXCEPTION.concat(" - Class: ").concat(type.toString()));
//        Crashlytics.logException(exception);
        throw exception;
    }

    private static List<String> getColumns(SQLiteDatabase db, String tableName) {
        List<String> columns = new ArrayList<>();
        Cursor cursor = null;
        try {
            cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 1", null);
            if (cursor != null) {
                columns = new ArrayList<>(Arrays.asList(cursor.getColumnNames()));
            }
        } catch (Exception e) {
            Log.v(tableName, e.getMessage(), e);
            e.printStackTrace();
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return columns;
    }
}

2、实现DaoMaster.OpenHelper,用MigrationHelper类对数据库进行升级,对应的类如下:

public class Helper extends DaoMaster.OpenHelper{

    private static DaoMaster daoMaster;
    private static DaoSession daoSession;

    public static final String DBNAME = "greendao.db";

    public Helper(Context context){
        super(context,DBNAME,null);
    }


    @Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) {
        super.onUpgrade(db, oldVersion, newVersion);
        Log.i("version", oldVersion + "---先前和更新之后的版本---" + newVersion);
        if (oldVersion < newVersion) {
            Log.i("version", oldVersion + "---先前和更新之后的版本---" + newVersion);
            MigrationHelper.getInstance().migrate(db, UserDao.class);
            //更改过的实体类(新增的不用加)   更新UserDao文件 可以添加多个  XXDao.class 文件
//             MigrationHelper.getInstance().migrate(db, UserDao.class,XXDao.class);
        }
    }

    /**
     * 取得DaoMaster
     *
     * @param context
     * @return
     */
    public static DaoMaster getDaoMaster(Context context) {
        if (daoMaster == null) {
            DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(context,
                    DBNAME, null);
            daoMaster = new DaoMaster(helper.getWritableDatabase());
        }
        return daoMaster;
    }

    /**
     * 取得DaoSession
     *
     * @param context
     * @return
     */
    public static DaoSession getDaoSession(Context context) {
        if (daoSession == null) {
            if (daoMaster == null) {
                daoMaster = getDaoMaster(context);
            }
            daoSession = daoMaster.newSession();
        }
        return daoSession;
    }
}

3、完成以上操作后,将版本号加1即可;

greendao {
    schemaVersion 2//改版本号为2
    daoPackage 'com.zhangqie.greendao.gen';
    targetGenDir 'src/main/java'
}

此时,之前版本存的数据保存下来了,并且新增加了一个字段,只不过字段值为空。

好的,咱们的GreenDao的基本使用和升级就到此为止了,GreenDao征(mo)途(ri)才刚刚开始,猿友们不要松懈,多多撸码才是正途。最后,发张我的近照给大家观(xian)赏(mu)观(xian)赏(mu)



image

好的,注意不要舔屏(这是唯一的要求),下一篇见。

赞赏

如果这个库对您有很大帮助,您愿意支持这个项目的进一步开发和这个项目的持续维护。你可以扫描下面的二维码,让我喝一杯咖啡或啤酒。非常感谢您的捐赠。谢谢!




Contanct Me

● 微信:

欢迎关注我的微信:bcce5360

● 微信群:

微信群如果不能扫码加入,麻烦大家想进微信群的朋友们,加我微信拉你进群。



● QQ群:

2千人QQ群,Awesome-Android学习交流群,QQ群号:959936182, 欢迎大家加入~

About me

很感谢您阅读这篇文章,希望您能将它分享给您的朋友或技术群,这对我意义重大。

希望我们能成为朋友,在 Github掘金上一起分享知识。

坚持原创技术分享,您的支持将鼓励我继续创作!