数据存储方式

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

文件

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,官方是这么说它的特点的:

(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
}

常用注解:

@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;
}
@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;
}
@NotNull -- 设置表中当前列的值不可为空
@Convert-- 指定自定义类型,将自定义的数据类型,转换为数据库中所存储列的值的类型
@Convert(converter = NoteTypeConverter.class, columnType = String.class)
private NoteType type;
@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)一对一

@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 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 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 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 联系起来。

@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 "";
}

@Keep-- 注解的代码段在GreenDao下次运行时保持不变

(1)注解实体类:默认禁止修改此类

(2)注解其他代码段,默认禁止修改注解的代码段

@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。

增删改查

查询

DaoSession daoSession = ((MyApplication) getApplication()).getDaoSession();
userDao = daoSession.getUserDao();
...
QueryBuilder 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 youngJoes = qb.list();

添加

User user = new User();
user.setName("Nick");
user.setAge(25);
user.setId(12);
userDao.insert(user);

更新

user = userLists.get(10);
user.setName(Ralf);
user.setAge(29);
userDao.update(user);

删除

Long userId = user.getId();
userDao.deleteByKey(userId);

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

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