Android 数据库 -- greenDAO

数据存储方式

一般文章中介绍数据存储有这么几种:

  • 文件

  • SharedPreferences

  • 数据库

  • 网络

  • ContentProvider

其实,在内存存储也可以算是一种存储,比如,有些时候我们用 static 变量存储一些共享数据,只不过与上面的数据不用,上面的是持久化数据存储,但是也是一种数据存储方式,需要根据需求来决定使用哪种方式。加上内存存储构成三级缓存策略,

内存--->本地--->网络,不同级别的数据一般情况下,使用频率也是逐级降低的,这也是一种规律,因为对于移动端来说,提高用户体验的其中一点也是加载速度快,那么当然从内存中拿出数据来效率更高,但是不可能所有数据都能放在内存中,毕竟内存有限,而且有些数据也是需要长久记录下来的,设备重启或者应用重启后,保存在进程中的数据也就释放掉了。

下面说一说持久化存储的特点:

文件

文件,这种类型的数据一般都是文件格式,比如图片,word,text等但是大小和数量也不能太多,毕竟移动设备的存储空间有限。另外这种数据一般官方的建议是存储在外部私有存储空间,因为内部存储空间有限,对于外部存储空间,如果存在公有空间,则删除应用时文件不会被删除,如果保存在外部私有空间,删除应用时数据也会一并被删除,这样也能够减少一些用户喷来的口水!

SP

对于 SP 来说,一般也比较简单与方便,常常我们都是拿 SP 工具类来进行数据存储的,对于 SP 存储的数据,也是少量的,但是同为本地数据存储,有哪些特点,换句话说,在哪些场景下使用 SP ?

  • 只支持存储Java基础数据类型(Boolean、Int、Float、String、Long等)不支持自定义数据类型。

  • 本质是一种Map,通过键值对的形式进行数据存储。

  • 不支持查找功能。

从这几个特点中可以看出,实际上用 SP 存储一些基本的数据类型,虽然用你也可以将数据放在本地文件中,但是没有 SP 方便,SP 可以根据 key 直接取出。同样,SP 是放在内存存储空间的,数据量也是相对较小的。

数据库

很明显,数据库就是要解决 SP 不能存储自定义对象的烦恼。SQL 数据库叫做“结构化查询语言”,也就是说是用来存储结构化数据的,这里可以理解为对象,所以数据库一般是关系型数据库。对于数据存储的数据也可以存储到文件中,只不过我们需要将对象数据序列化,存储到文件中,然后读取的时候在反序列化,这对于开发者来说,很麻烦数据库能够很好地解决这个问题。开发者只管存储就行,不需要关心序列化和反序列化的过程。

对于移动端的数据库,一般都是轻量级的,像安卓端的 Sqlite,毕竟是移动端,不能搞得像 PC 上那么大。对于移动端的数据库一般也有以下几个特点

  • 自定义数据类型,一般是对象。

  • 存储在本地的内部存储空间,数据也不会太大

  • 支持查找。

数据库现在也有很多优秀的开源框架可以使用,不同的框架也有不同的特点,那么对于开发者来说如何选择?其实无非也就这么几点:

  • 满足性能要求,比如速度快,数据量大时,稳定

  • 简单易操作,能不写代码最好~~

  • 轻量级,引入的包很大的话,也会对APK的大小有影响,以及初始化的速度等

greenDao 介绍

为啥本篇主要来说一说 greenDao 呢?原因很简答,之前没用过,当然来尝试一下了。

原生的 sqlite 就不说了,应用大家都不会用原生的数据库,如果原生的数据库好用的话,估计也不会出现各种开源框架了!

对于数据库,特别是移动端的,涉及的操作不会很多,也就是增删改查,不熟悉的可以在 w3cschool 上复习一下。

SELECT - 从数据库表中获取数据
UPDATE - 更新数据库表中的数据
DELETE - 从数据库表中删除数据
INSERT INTO - 向数据库表中插入数据

大神们喜欢使用原始的 SQL 语句,对于我这种小菜鸟,还是喜欢更简单的框架,所以对于我来说,好的数据库框架,一般是越无脑越好,性能越高越好,怎么平衡它?看你自己了。

之前使用 LiteOrm,这个框架比较简单,有兴趣的可以看看这篇文章-Android ORM框架 LiteOrm使用。至于性能和 greenDAO 对于,没有亲子测过(主要是不会测~),可以看看 Android数据库框架:greenDAO vs LiteOrm 这篇文章。

greenDAO,官网链接 http://greenrobot.org/greendao

LiteOrm,官网链接 http://litesuits.com

对月 greenDAO,官方是这么说它的特点的:

(1) Rock solid: greenDAO has been around since 2011 and is used by countless famous apps 

    非常稳定,自从 2011 年开始就被无数有名气的 App 使用

(2) Super simple: concise and straight-forward API, in V3 with annotations
    超级简单(倒是觉得没有 LiteOrm简单),有简单的使用 API,在 v3 版本中引入注解
 
(3) Small: The library is <150K and it's just plain Java jar (no CPU dependent native parts)
    很小,库是于 150 K 的 jar 包(不含有依赖 CPU 的本地代码部分,指的是 so库)

(4) Fast: Probably the fastest ORM for Android, driven by intelligent code generation
    速度快,可能是 android 上最快的 ORM,可以自动生成代码
 
(5) Safe and expressive query API: QueryBuilder uses property constants to avoid typos
    安全和容易记忆的查询 API:QueryBuilder 使用合适的常量来避免拼写错误
 
(6) Powerful joins: query across entities and even chain joins for complex relations
    有强大的联合查询:支持实体间的交叉查询,以及支持复杂的链式查询
 
(7) Flexible property types: use custom classes or enums to represent data in your entity
    拥有更灵活的类型,可以使用类类型或者枚举,来表示你的实体类
 
(8) Encryption: supports SQLCipher encrypted databases
    可以加密:支持 SQLCipher 加密数据库

好,看它吹完牛和我唠叨完,我们还是实际使用一下,来感受感受!

greenDAO 使用

官方也是给出了例子

配置

工程目录下 build.gradle 添加如下配置:

buildscript {
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.0'
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin

    }
}

在 app 的 build.gradle 下配置:

apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' // apply plugin

android {
    ...
}

dependencies {
    ...
    implementation 'org.greenrobot:greendao:3.2.2'

}

新建实体类

@Entity
public class Note {
    @Id
    private Long id;
    private String name;
    private int age;

   //下面省去了 setter/getter
}

常用注解:

  1. @Entity -- 实体注解
public @interface Entity {

    /**
     * 在数据库中表的名称,默认为实体的类名 
     */
    String nameInDb() default "";

    /**
     *  定义索引,可以跨越多个列(默认为实体类成员变量的个数) 
     */
    Index[] indexes() default {};

    /**
     * 标记创建数据库表 
     * 如果是有多个实体都关联这个表,可以把多余的实体里面设置为false避免重复创建(默认是true)
     */
    boolean createInDb() default true;

    /**
     *  告知GreenDao当前实体属于哪个schema 
     */
    String schema() default "default";

    /**
     *  实体活动状体标志位(默认为false) 
     *  若设置为true,实体有更新、删除和刷新方法 
     */
    boolean active() default false;
  }

  1. @Generated 派生属性

@Generated 这个是build后greendao自动生成的,这个注解理解为防止重复,每一块代码生成后会加个hash作为标记。 官方不建议你去碰这些代码,改动会导致里面代码与hash值不符。

@Generated(hash = 1272611929)
    public Note() {
    }

    public Note(Long id) {
        this.id = id;
    }

    @Generated(hash = 1686394253)
    public Note(Long id, @NotNull String text, String comment, java.util.Date date, NoteType type) {
        this.id = id;
        this.text = text;
        this.comment = comment;
        this.date = date;
        this.type = type;
    }
  1. @NotNull -- 设置表中当前列的值不可为空

  2. @Convert-- 指定自定义类型,将自定义的数据类型,转换为数据库中所存储列的值的类型

    @Convert(converter = NoteTypeConverter.class, columnType = String.class)
    private NoteType type;
  1. @Id-- 主键 Long型,可以通过@Id(autoincrement = true)设置自增长。通过这个注解标记的字段必须是Long,数据库中表示它就是主键,并且默认是自增的。

@Id
    private Long id;

6.索引属性-两种方法

(1)@Index-- 使用@Index作为一个属性来创建一个索引;

@Index 通过这个字段建立索引

@Unique 添加唯一约束,上面的括号里unique=true作用相同

@Entity
public class User {
    @Id private Long id;
    @Index(unique = true)
    private String name;
}
 
@Entity
public class User {
    @Id private Long id;
    @Unique private String name;
}

(2)定义多列索引(@link Entity#indexes())

通过逗号间隔创建表的属性索引,例如 “propertyA,propertyB,propertyC” ,
若要指定排序, 需在列明以后添加 ASC(升序) 或者DESC(降序) , 例如 "propertyA DESC, propertyB ASC" ,只有实体类中使用 {@link Entity#indexes()} 才可设置


@Entity(indexes = {
        @Index(value = "text, date DESC", unique = true)
})
public class Note {
    ...
}
  1. 关系注解
    (1)一对一
@Entity
public class Order {
    @Id private Long id;
  
    private long customerId;
  
    @ToOne(joinProperty = "customerId")
    private Customer customer;
}
  
@Entity
public class Customer {
    @Id private Long id;
}

@ToOne 是将自己的一个属性与另一个表建立关联,如果不设置joinProperty,在Customer表中会自动创建外键,值是自动增加的。

(2)一对多

public @interface ToMany {  
    /** 
     * 目标实体持有源实体的名称 
     * 如果没设置,否则有 {@link JoinProperty} or {@link JoinEntity} 指定 
     */  
    String referencedJoinProperty() default "";  
  
    /** 
     * 将源表列索引->目标列 
     * 如果没设置,否则有 {@link JoinProperty} or {@link JoinEntity} 指定 
     */  
    JoinProperty[] joinProperties() default {};  
}

上面的可能不是很好理解,其中也涉及了其他注解,@JoinProperty@JoinEntity
,意思是如果设置了 referencedJoinProperty 作为另一个表的外键,可以不设置 @JoinProperty@JoinEntity,否则就要设置这两个注解。举个例子。

方式一:

@Entity
public class User {
    @Id private Long id;
  
    @ToMany(referencedJoinProperty = "ownerId")
    private List<Site> ownedSites;
}
  
@Entity
public class Site {
    @Id private Long id;
    private long ownerId;
}

一个 User 中可能有多个 Site ,那么对于 Site 和 User 联系起来是通过 ownerId

方式二:

如果不指定 ownerId,通过 @JoinProperty

@Entity
public class User {
    @Id private Long id;
    @Unique private String authorTag;
  
    @ToMany(joinProperties = {
            @JoinProperty(name = "authorTag", referencedName = "ownerTag")
    })
    private List<Site> ownedSites;
}
  
@Entity
public class Site {
    @Id private Long id;
    @NotNull private String ownerTag;
}

从上面看到通过 User 中的 authorTag 和 Site 中的 ownerTag 联系起来。

方式三:

(3) 多对多


@Entity
public class Site {
    @Id private Long id;
  
    @ToMany
    @JoinEntity(
            entity = JoinSiteToUser.class,
            sourceProperty = "siteId",
            targetProperty = "userId"
    )
    private List<User> authors;
}
  
@Entity
public class JoinSiteToUser {
    @Id private Long id;
    private Long siteId;
    private Long userId;
}
  
@Entity
public class User {
    @Id private Long id;
}

单独定义一个关系类 JoinSiteToUser ,通过该类将 Site 和 User 联系起来。

  1. @OrderBy-- 指定排序

一般也是使用在 @toMany 中.

public @interface OrderBy {  
    /** 
     * 通过逗号间隔创建表的属性索引,例如 “propertyA,propertyB,propertyC” 
     * 若要指定排序, 需在列明以后添加 ASC(升序) 或者DESC(降序) ,  例如 "propertyA DESC, propertyB ASC" 
     *  默认按升序排序 
     *  若不设置默认根据主键排序 
     */  
    String value() default "";  
}

9.@Property-- 设置数据库中的列名,默认是的使用字段名

举例:@Property (nameInDb="name")

public @interface Property {  
    /** 
     * 默认是的使用字段名 
     */  
    String nameInDb() default "";  
}
  1. @Keep-- 注解的代码段在GreenDao下次运行时保持不变
(1)注解实体类:默认禁止修改此类
(2)注解其他代码段,默认禁止修改注解的代码段
  1. @Transient-- 添加次标记之后不会生成数据库表的列

初始化

编写完实体类之后,进行编译,会生成 DaoMaster,DaoSession,和一个实体 DAO类,如实体类是 User,则对应的是 UseDao 类。
然后在 Application 中初始化,看到代码中加了加密的设置。greenDao 允许加密, 若要使用加密,需要在 build.gradle 中加入以下依赖。

 // This is only needed if you want to use encrypted databases
    implementation 'net.zetetic:android-database-sqlcipher:3.5.6'

Application 中初始化

public class MyApplication extends Application {

    /** A flag to show how easily you can switch from standard SQLite to the encrypted SQLCipher. */
    public static final boolean ENCRYPTED = false;

    private DaoSession daoSession;

    @Override
    public void onCreate() {
        super.onCreate();

        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, ENCRYPTED ? "notes-db-encrypted" : "notes-db");
        Database db = ENCRYPTED ? helper.getEncryptedWritableDb("super-secret") : helper.getWritableDb();
        daoSession = new DaoMaster(db).newSession();
    }

    public DaoSession getDaoSession() {
        return daoSession;
    }
}

DaoMaster:使用greenDAO的切入点。 DaoMaster保存数据库对象(SQLiteDatabase)并管理特定模式的DAO类(而不是对象)。 它有静态方法来创建表或删除它们。 它的内部类OpenHelper和DevOpenHelper都是SQLiteOpenHelper的实现,用来在SQLite数据库中创建模式。

DaoSession:管理特定模式的所有可用DAO对象,你可以使用其中一个的getter方法获取DAO对象。 DaoSession还为实体提供了一些通用的持久性方法,如插入,加载,更新,刷新和删除。 最后,DaoSession对象也跟踪identity scope。

DAO:数据访问对象(DAO),用于实体的持久化和查询。 对于每个实体,greenDAO会生成一个DAO。 它比DaoSession拥有更多的持久化方法,例如:count,loadAll和insertInTx。

greenDAO Gradle插件


greendao {
    targetGenDir 'src/main/java'
    daoPackage 'com.XXXX.dao.db'
}

schemaVersion:数据库模式的当前版本。 这由* OpenHelpers类用于在模式版本之间迁移。 如果更改实体/数据库模式,则必须增加此值。 默认值为1。

daoPackage:生成的DAO,DaoMaster和DaoSession的包名称。 默认为源实体的包名称。

targetGenDir:生成的源码应存储在的位置。 默认为生成目录(build / generated / source / greendao)中生成的源文件夹。

generateTests:设置为true以自动生成单元测试。

targetGenDirTests:生成的单元测试应存储在的基本目录。 默认为src / androidTest / java。

增删改查

  1. 查询
    DaoSession daoSession = ((MyApplication) getApplication()).getDaoSession();
    userDao = daoSession.getUserDao();

    ...
    
    QueryBuilder<User> qb = userDao.queryBuilder();
    qb.where(Properties.FirstName.eq("Joe"),// 查询条件
    qb.or(Properties.YearOfBirth.gt(1970), // 或 条件
    qb.and(Properties.YearOfBirth.eq(1970), Properties.MonthOfBirth.ge(10)))); // 与 条件
    List<User> youngJoes = qb.list();
  1. 添加

    User user = new User();
    user.setName("Nick");
    user.setAge(25);
    user.setId(12);
    userDao.insert(user);
  1. 更新
    user = userLists.get(10);
    user.setName(Ralf);
    user.setAge(29);
    userDao.update(user);
  1. 删除
    Long userId = user.getId();
    userDao.deleteByKey(userId);

以上就是 greenDao 的基本介绍,数据库也是很复杂的一部分,但是对于移动端来说,一般会一些基本的操作大部分场景都能够满足,如果需要更多操作可以参考官方的说明文档

官方 API.

另外需要关注的是,greenrobot 又出了 ObjectBox,使用更加单,也是 greenrobot 大力推荐的,后面会做一下介绍。下一篇会对 greenDao 进行封装使用,然后自己设计稍微复杂的表进行实践一下,也不能总是对着一张表增删改查,要不有点那啥。。。

好,就到这里,敬请期待!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容