GreenDao —— 简单快速操作 Android SQLite 数据库

GreenDao —— 简单快速操作 Android SQLite 数据库

GreenDao 是轻量快速的 SQLite 数据库 ORM 解决方案。(greenDAO is a light & fast ORM solution that maps objects to SQLite databases.

GreenDao 层次

ORM(Object-Relationl Mapping)用于在关系型数据库与对象之间做一个映射。可以使数据库操作想对象一样使用,而避开使用复杂的SQL语句交互。

GreenDao 特点:

  • 性能强大。(可能是 Android 平台最快的 ORM 框架)
  • 简易便捷的 API
  • 开销小
  • 依赖体积小
  • 支持数据库加密
  • 强大的社区支持

此前接触 Android 的 SQLite 数据库操作,有感于直接使用 SQLite 繁琐且低效,使用 Android 官方的 Room 也感觉效果不佳。最后选择 GreenDao 总算满足预期。

GreenDao 环境配置

1. Project 下的 build.gradle 增加插件支持


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

2. app 下的 build.gradle 增加插件依赖


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 数据库对象实体

1. 创建数据库实体

创建实体需要了解 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;
}

2. 编译后,生成完善的数据库实体方法

编译后可以看到 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;
    }
}

自动生成的方法:

  1. 无参构造函数
  2. 有参构造函数
  3. getter / setter 方法

除此之外,还生成了 DaoMaster,DaoSession,UserBeanDao (GroupBean 对应生成 GroupBeanDao ) 等文件。
下面来介绍各自的功能。

3. DaoMaster,DaoSession,Dao 文件

文件 描述 相应文件
DaoMaster 保存数据库对象(SQLiteDatabase) DaoMaster
DaoSession 管理所有的 Dao 对象 DaoSession
Dao 数据访问对象(Data Access Object),可以通过 Dao 操作数据实体 UserBeanDao、GroupBeanDao
Entity 数据实体(每个实体对应数据库内的一个表) UserBean、GroupBean

通过 GreenDao 操作数据库。

1. 初始化数据库


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 对每个数据库实体进行处理。

2. 插入数据

以以下数据为例:

组织(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);
}

3. 删除数据


// 删除单个数据
getDaoSession().getUserBeanDao().delete(userBean);

// 删除多个数据 (userBeanList 类型为 List<UserBean>)
getDaoSession().getUserBeanDao().deleteInTx(userBeanList);

// 删除所有数据
getDaoSession().getUserBeanDao().deleteAll();

4. 查找数据


// 查询 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<UserBean> 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 设置表与表之间的关系进行直接访问的查询。
但是大数据量时效率没有使用以上方法快。

5. 更改数据


// 更改单个数据
getDaoSession().getUserBeanDao().update(userBean);

// 更改多个数据 (userBeanList 类型为 List<UserBean>)
getDaoSession().getUserBeanDao().updateInTx(userBeanList);

其它

1. 类型转换

SQLite 数据库的数据类型有限(甚至不支持float),而作为对象则允许所有java的类型(数组、各种类等)。
因此在 GreenDao 中支持类型转换(从数据库数据类型转换为实体的数据类型),方便对实体进行修改查询。

请在对应的成员中加入 @Convert ,如:


// 以实体的 float[] 与 数据库的 TEXT 类型转换为例
@Convert(converter = ConvertFloatArrayToString.class, columnType = String.class)
private float[] featureData;

转换方法:


public class ConvertFloatArrayToString implements PropertyConverter<float[], String> {
    // 数据库类型 -> 实体类型
    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 了。

2. 其它的其它

比如加密,比如缓存,比如懒加载。。。有空再补吧

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