GreenDao框架简析

GreenDao是一款开源的面向Android的轻便、快捷的ORM(对象映射)框架,将Java对象映射到SQLite数据库中,避免编写复杂的SQL语句。具有高性能、低开销,且支持数据库加密等功能。本文只做GreenDao核心API简析,详细使用请参考GreenDao官方文档译文ORM数据库框架greenDAO SQLite MD

相关概念

核心类

  • DaoMaster:负责管理数据库对象(SQLiteDatabase)和DAO类(对象),通过OpenHelper、DevOpenHelper和SQLiteOpenHelper创建不同模式的SQLite数据库;
  • DaoSession:管理指定模式下的所有DAO对象,提供了基本的实体类操作;
  • XxxDAO:针对每个实体类生成的数据访问对象;
  • Entities:可持久化对象,一个JavaBean实例对应数据库表的一行;

注解

  • @Entity:表明这个实体类会在数据库中生成一个与之对应的表;注:Kotlin不支持。
  • @Id:对应数据库表中的id字段,是一条数据的唯一标识,必须是Long类型;@Id(autoincrement = true)表示自增型主键。
  • @Property(nameInDb = "name"):表明这个属性对应数据库表中的name字段;
  • @NotNull:表明该属性值不能为空;
  • @Transient:表明该属性值不被存储在数据库中;
  • @Unique:表明该属性在数据库中只能有唯一值, 也可作为一条数据的唯一标识;优先级高于主键Id。
    • 多个@Unique注解的数据,满足其中一个就认定是同一条数据。
    • 自增型主键时,以@Unique注解的值为唯一性标识;
  • @OrderBy:增加某一字段的排序,如@OrderBy("data ASC") 正序;

常用API

  • QueryBuilder:SQL的语法错误只有在runtime时才会提示,但QueryBuilder可以在编译期就检测到错误。
  • where:配置查询条件,可传递多个查询条件。where语句里的条件用“且”连接,whereOr语句里的条件用"或"连接。
  • limit(int):从前面限制获取的条数。
  • offset(int):从某位置开始获取数据,即数据返回的偏移量。需要结合limit使用。
  • eq:等于;notEq:不等于;like:模糊查询;gt:大于;ge:大于等于;
  • insert:插入数据,如果已存在则插入失败,insertInTx是在事务中操作;
  • insertOrReplace:插入数据,如果已存在则替换(判断一条数据是否存在,由主键id或@Unique(高优先级)作为唯一标识),insertOrReplaceInTx是在事务中操作。

环境配置

  • module级别的build.gradle里:
apply plugin: 'org.greenrobot.greendao'
 
greendao {
       schemaVersion 1 //数据库版本号
       daoPackage 'com.example.myapplication.db'// 设置DaoMaster、DaoSession、Dao 包名
       targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录
}
dependencies {
    implementation 'org.greenrobot:greendao:3.2.2'//数据库
    implementation 'net.zetetic:android-database-sqlcipher:4.2.0'//数据库加密
    implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1'//数据库升级
}
  • Project级别的build.gradle里:
dependencies {
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
}
allprojects {
    repositories {
        maven { url "https://jitpack.io" }
    }
}

核心功能

加密

SQLCipher是在SQLite基础上自定义256位的AES加密开源数据库,加密性能高,开销低。

public class App extends Application {
    public static DaoSession daoSession;
    private static String dbPassword = "test";//推荐使用设备唯一标识类UUID的字段加密值。
 
    @Override
    public void onCreate() {
        super.onCreate();
        initGreenDao();
    }
 
    private void initGreenDao() {
        DaoMaster.DevOpenHelper helper = new DbOpenHelper(this, "vivo_test.db", null);
        daoSession = new DaoMaster(helper.getEncryptedWritableDb(dbPassword)).newSession();
 
        //要在debug模式下开启,输出带有具体数值的SQL日志
        QueryBuilder.LOG_SQL = true;
        QueryBuilder.LOG_VALUES = true;
    }
}

SQLCipher是应用级别的数据库加密,通过命令查询等操作数据库,会被提示数据库加密,操作失败,进一步保证了数据的安全性。
SQLCipher的so包会增加apk的大小,约1.4M,不过平衡程序的安全性,影响不大。

升级

GreenDao在数据库版本升级时,默认会删除低版本数据。目前主流的方案是在数据库升级时将低版本数据迁移到临时表中,成功插入新版本的表后,再删除低版本数据。

新建 DbOpenHelper类继承自 DaoMaster.DevOpenHelper,并重写 onUpgrade方法,通过 GreenDaoUpgradeHelper库内的MigrationHelper类配置好所有DAO类,如PersonBeanDao.class、BankCardDao.class类,即可实现数据的迁移。

之后更改build.gradle中GreenDao的数据库版本号,clean Project -> makeProject。

public class DbOpenHelper extends DaoMaster.DevOpenHelper {
 
    public DbOpenHelper(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);
        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);
            }
        }, PersonBeanDao.class,BankCardDao.class);
    }
}

在 onUpgrade中处理数据迁移时,要注释掉 super.onUpgrade(db, oldVersion, newVersion); 并将现有所有的DAO类都加到 MigrationHelper.migrate()的参数内。

多表关联

@ToOne 定义了一个entities与另一个entities的1:1对应关系。通过joinProperty参数定义外键。
@ToMany 定义了一个entities与另一个entities的1:N对应关系。通过referencedJoinProperty参数定义外键。

  • 1:1对应关系示例
//源实体
@Entity
puiblic class PersonBean {
    @Id
    puiblic Long id;
    public String name;
    public Long bankCardId;
 
    @ToOne(joinProperty = "bankCardId")
    public BankCardBean bankCardBeans;
...
}
 
//目标实体
@Entity
public class BankCardBean {
    @Id
    public long id;
    public float money;
}

一对一表格关联,源实体与目标实体通过@ToOne(joinProperty= "bankCardId")外键关联,源实体中的bankCardId值关联目标实体的主键id。

  • 1:N对应关系示例
//源实体
@Entity
puiblic class PersonBean {
    @Id
    puiblic Long id;
    public String name;
 
    @ToMany(referencedJoinProperty = "personId")
    @OrderBy("money ASC")
    public List<BankCardBean> bankCardBeans;
...
}
 
//目标实体
@Entity
public class BankCardBean {
    @Id
    public Long id;
    public float money;
    public long personId;
}

在目标实体中定义与源实体关联的外键,即BankCardBean的personId,在源实体中由referencedJoinProperty 指定目标实体的外键。

目前GreenDao的多表关联方案较原始,不支持级联删除。不过目前项目中此类业务不多,可分别处理关联表的数据操作。

操作示例

private void asyncInsert() {
        final AsyncSession asyncSession = CommonInit.daoSession.startAsyncSession();
        asyncSession.runInTx(new Runnable() {
            @Override
            public void run() {
                asyncSession.setListenerMainThread(new AsyncOperationListener() {
                    @Override
                    public void onAsyncOperationCompleted(AsyncOperation operation) {
                        if (operation.isCompletedSucessfully()) {
                           //主线程 判断数据库操作完毕 更新UI
                        }
                    }
                });
                //todo 子线程 在此处开启事务操作数据库
                App.daoSession.getPersonBeanDao().insertOrReplaceInTx(personBeans);
            }
        });
    }

为了性能最优化,宜采用异步操作数据库。事务除了可以确保数据处理的动作的完整性,还能提升大量插入数据的性能。

注意:以下数据库操作相比示例只是替换了具体操作。

CommonInit.daoSession.getPersonBeanDao().insertOrReplaceInTx(personBeans);
CommonInit.daoSession.getPersonBeanDao().updateInTx(personBeans);

示例:查询该账号下年龄大于29岁的人,并按照年龄正序输出从第五个开始的前20个人。多个检索条件,多个where。

final List<PersonBean> personBeans =
                       CommonInit.daoSession.getPersonBeanDao().queryBuilder()
                               .where(PersonBeanDao.Properties.Age.gt(29))
                               .offset(5)
                               .limit(20)
                               .orderAsc(PersonBeanDao.Properties.Age)
                               .list();

等同于

final List<PersonBean> personBeans =
                       CommonInit.daoSession.getPersonBeanDao().queryRaw(
                       "WHERE AGE>? ORDER BY AGE ASC LIMIT ? OFFSET ?", "221239", "20", "5");
CommonInit.daoSession.getPersonBeanDao().deleteInTx(personBeans);
  • 实体类
@Entity
public class PersonBean implements Serializable {
    public static final long serialVersionUID = 1234134312341234L;
 
    @Id(autoincrement = true)
    public Long id;//一般不参与业务,自增主键
    @Unique
    public String identifyId;
    public String firstname;
    public String secondname;
    public String age;
    @Transient
    public String address;
...
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容