GreenDao 是轻量快速的 SQLite 数据库 ORM 解决方案。(greenDAO is a light & fast ORM solution that maps objects to SQLite databases.)
ORM(Object-Relationl Mapping)用于在关系型数据库与对象之间做一个映射。可以使数据库操作想对象一样使用,而避开使用复杂的SQL语句交互。
GreenDao 特点:
- 性能强大。(可能是 Android 平台最快的 ORM 框架)
- 简易便捷的 API
- 开销小
- 依赖体积小
- 支持数据库加密
- 强大的社区支持
此前接触 Android 的 SQLite 数据库操作,有感于直接使用 SQLite 繁琐且低效,使用 Android 官方的 Room 也感觉效果不佳。最后选择 GreenDao 总算满足预期。
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // 版本建议最新
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao'
android {
...
// greendao 配置
greendao {
schemaVersion 1 // 数据库版本号
daoPackage 'org.cvte.research.faceapi.greendao' // greenDao 自动生成的代码保存的包名
targetGenDir 'src/main/java' // 自动生成的代码存储的路径,默认是 build/generated/source/greendao.
}
...
}
dependencies {
...
// GreenDao 数据库ORM
implementation 'org.greenrobot:greendao:3.2.2'
// GreenDao 生成dao和model的generator的项目 发布时可以去掉
implementation 'org.greenrobot:greendao-generator:3.2.2'
}
接下来可以使用 GreenDao 对数据库对象进行操作了。
创建实体需要了解 GreenDao 的注解:
注解 | 描述 | 其它参数 |
---|---|---|
Entity | 对应数据库中的表 | nameInDb:表使用别名。默认为类名 |
active:标记一个实体是否处于活动状态,活动实体有 update、delete、refresh 方法。默认为 false | ||
indexes:定义多列索引 | ||
Id | 该数据库表的主键,只能是 Long 或 long 类型 | autoincrement:设置是否自增,可以通过传入 null 自动分配 |
Unique | 唯一。可以通过设置唯一的属性设为主键 | |
Property | 列名 | nameInDb:列使用别名。默认为变量名 |
Index | 索引 | unique:设置唯一 |
name:设置索引的别名 | ||
NotNull | 非空。该字段值不能为空 | |
Transient | 忽略。greendao 将不会创建对应的项 | |
ToOne | 表格映射关系一对一 | joinProperty:外联实体与该实体主键的匹配成员 |
ToMany | 表格映射关系一对多或多对多 | |
Generated | greendao 产生的部分,手动修改会报错 | |
Keep | 替换 Generated,greendao 不再生成和报错 | |
Convert | 数据类型转换。实体类型与数据库类型转换,实现存储和修改的便捷 | converter:转换方法 |
columnType:数据库使用的数据类型 |
文件名为:UserBean.java
@Entity(nameInDb = "user_table")
public class UserBean {
@Id(autoincrement = true)
@Unique
@Property(nameInDb = "user_id")
private Long userId;
@NotNull
@Property(nameInDb = "group_id")
private Long groupId;
@NotNull
@Property(nameInDb = "user_name")
private String userName;
@Unique
@NotNull
@Property(nameInDb = "user_number")
private String userNumber;
}
另外增加一个 GroupBean.java 实体,内容如下:
@Entity(nameInDb = "group_table")
public class GroupBean {
@Id(autoincrement = true)
@Unique
@Property(nameInDb = "group_id")
private Long groupId;
@Unique
@NotNull
@Property(nameInDb = "group_name")
private String groupName;
}
编译后可以看到 UserBean.java 文件增加了不少接口。
@Entity(nameInDb = "user_table")
public class UserBean {
@Id(autoincrement = true)
@Unique
@Property(nameInDb = "user_id")
private Long userId;
@NotNull
@Property(nameInDb = "group_id")
private Long groupId;
@NotNull
@Property(nameInDb = "user_name")
private String userName;
@Unique
@NotNull
@Property(nameInDb = "user_number")
private String userNumber;
@Generated(hash = 1853997691)
public UserBean(Long userId, @NotNull Long groupId, @NotNull String userName,
@NotNull String userNumber) {
this.userId = userId;
this.groupId = groupId;
this.userName = userName;
this.userNumber = userNumber;
}
@Generated(hash = 1203313951)
public UserBean() {
}
public Long getUserId() {
return this.userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getGroupId() {
return this.groupId;
}
public void setGroupId(Long groupId) {
this.groupId = groupId;
}
public String getUserName() {
return this.userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserNumber() {
return this.userNumber;
}
public void setUserNumber(String userNumber) {
this.userNumber = userNumber;
}
}
自动生成的方法:
- 无参构造函数
- 有参构造函数
- getter / setter 方法
除此之外,还生成了 DaoMaster,DaoSession,UserBeanDao (GroupBean 对应生成 GroupBeanDao ) 等文件。
下面来介绍各自的功能。
文件 | 描述 | 相应文件 |
---|---|---|
DaoMaster | 保存数据库对象(SQLiteDatabase) | DaoMaster |
DaoSession | 管理所有的 Dao 对象 | DaoSession |
Dao | 数据访问对象(Data Access Object),可以通过 Dao 操作数据实体 | UserBeanDao、GroupBeanDao |
Entity | 数据实体(每个实体对应数据库内的一个表) | UserBean、GroupBean |
private static DaoSession mDaoSession;
public initDatabase(Context context, String databaseFileName) {
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(context, databaseFileName, null);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
mDaoSession = daoMaster.newSession();
// 打开查询的LOG
// QueryBuilder.LOG_SQL = true;
// QueryBuilder.LOG_VALUES = true;
}
public static DaoSession getDaoSession() {
return mDaoSession;
}
执行 initDatabase 初始化数据库后,可以通过 getDaoSession 拿到 DaoSession 对每个数据库实体进行处理。
以以下数据为例:
组织(GroupName) | 名字(UserName) | 编号(UserNumber) |
---|---|---|
CHN | LiTianYu | 1-0090 |
USA | LiBill | 10-8082 |
XYZ | MiXue | 2-720 |
public void insertData(String groupName, String userName, String userNumber) {
GroupBean groupBean = new GroupBean(null, groupName); // Group 主键(groupId)是自动增加的,使用 null 就可以自增了。
long keyGroupBean = getDaoSession().getGroupBeanDao().insert(groupBean); // 返回值是插入库后的 Group 实体的 key
groupBean = getDaoSession().getGroupBeanDao().load(keyGroupBean); // 通过 key 可以获取到插入的 Group 数据
UserBean userBean = new UserBean(null, groupBean.getGroupId(), userName, userNumber); // User 主键(userId)也是自动增加的,但是 groupId 需要通过关联的 Group 获取。
getDaoSession().getUserBeanDao().insert(userBean);
}
// 删除单个数据
getDaoSession().getUserBeanDao().delete(userBean);
// 删除多个数据 (userBeanList 类型为 List)
getDaoSession().getUserBeanDao().deleteInTx(userBeanList);
// 删除所有数据
getDaoSession().getUserBeanDao().deleteAll();
// 查询 userNumber 为 "1-0090" 的 user
getDaoSession().getUserBeanDao().queryBuilder().where(UserBeanDao.Properties.UserNumber.eq("1-0090")).unique();
// 查询 userName 形如 "Lixxx" 的 user ( 类似于 SQLite 的 like 模糊查询语法 )
getDaoSession().getUserBeanDao().queryBuilder().where(UserBeanDao.Properties.UserNumber.eq("Li%")).unique();
// 查询 groupName 为 "CHN" 的 user ( 关联表查询 )
QueryBuilder qb = getDaoSession().getUserBeanDao().queryBuilder(); // 需要获取 User 数据,所以 qb 为 User
Join join_UserBean = qb.join(UserBean.class, UserBeanDao.Properties.UserId); // 设置 User 的关联规则(根据 UserBean.userId == UserBean.userId)
Join join_GroupBean = qb.join(join_UserBean, UserBeanDao.Properties.GroupId, GroupBean.class, GroupBeanDao.Properties.GroupId); // 设置 Group 的关联规则(根据 UserBean.groupId == GroupBean.groupId)
join_GroupBean.where(GroupBeanDao.Properties.GroupName.eq("CHN")); // 其它查找条件(User和Group已经关联起来了)
qb.list(); // 返回查找结果
使用 .unique() 为获取查询满足要求的第一个数据。
使用 .list() 为获取所有满足要求的数据(返回结果为 List<> 类型)
多表关联查询稍显复杂,可以通过 ToOne、ToMany 设置表与表之间的关系进行直接访问的查询。
但是大数据量时效率没有使用以上方法快。
// 更改单个数据
getDaoSession().getUserBeanDao().update(userBean);
// 更改多个数据 (userBeanList 类型为 List)
getDaoSession().getUserBeanDao().updateInTx(userBeanList);
SQLite 数据库的数据类型有限(甚至不支持float),而作为对象则允许所有java的类型(数组、各种类等)。
因此在 GreenDao 中支持类型转换(从数据库数据类型转换为实体的数据类型),方便对实体进行修改查询。
请在对应的成员中加入 @Convert ,如:
// 以实体的 float[] 与 数据库的 TEXT 类型转换为例
@Convert(converter = ConvertFloatArrayToString.class, columnType = String.class)
private float[] featureData;
转换方法:
public class ConvertFloatArrayToString implements PropertyConverter {
// 数据库类型 -> 实体类型
public float[] convertToEntityProperty(String databaseValue) {
String[] strList = databaseValue.split(",");
float[] floatList = new float[strList.length];
for (int i = 0, len = strList.length; i < len; ++ i) {
floatList[i] = Float.parseFloat(strList[i]);
}
return floatList;
}
// 实体类型 -> 数据库类型
public String convertToDatabaseValue(float[] entityProperty) {
String str = "" + entityProperty[0];
for (int i = 1, len = entityProperty.length; i < len; ++ i) {
str += "," + entityProperty[i];
}
return str;
}
}
这样就可以读取直接操作 float[] 进行读写,不需要每次都手动转为 TEXT(对应 Java 中的 String)或者解析 TEXT 了。
比如加密,比如缓存,比如懒加载。。。有空再补吧